shelving 1.131.0 → 1.133.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/api/Resource.js +2 -2
- package/db/ValidationProvider.d.ts +6 -5
- package/db/ValidationProvider.js +27 -27
- package/error/UnauthorizedError.d.ts +1 -1
- package/error/UnauthorizedError.js +1 -1
- package/package.json +1 -1
- package/schema/ColorSchema.d.ts +3 -4
- package/schema/ColorSchema.js +2 -2
- package/schema/DataSchema.d.ts +1 -1
- package/schema/EmailSchema.d.ts +3 -3
- package/schema/EmailSchema.js +4 -5
- package/schema/ItemSchema.d.ts +33 -0
- package/schema/ItemSchema.js +21 -0
- package/schema/KeySchema.d.ts +4 -1
- package/schema/KeySchema.js +9 -3
- package/schema/LinkSchema.d.ts +2 -2
- package/schema/LinkSchema.js +5 -5
- package/schema/NumberSchema.js +1 -1
- package/schema/PhoneSchema.d.ts +2 -2
- package/schema/PhoneSchema.js +2 -2
- package/schema/SlugSchema.d.ts +1 -1
- package/schema/SlugSchema.js +2 -3
- package/schema/StringSchema.d.ts +3 -32
- package/schema/StringSchema.js +4 -35
- package/schema/TextSchema.d.ts +40 -0
- package/schema/TextSchema.js +47 -0
- package/schema/index.d.ts +2 -0
- package/schema/index.js +2 -0
- package/util/string.d.ts +4 -4
- package/util/string.js +4 -4
- package/util/validate.d.ts +7 -13
- package/util/validate.js +37 -45
package/api/Resource.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ValidationError } from "../error/ValidationError.js";
|
|
2
2
|
import { Feedback } from "../feedback/Feedback.js";
|
|
3
|
-
import {
|
|
3
|
+
import { UNDEFINED } from "../util/validate.js";
|
|
4
4
|
/**
|
|
5
5
|
* An abstract API resource definition, used to specify types for e.g. serverless functions..
|
|
6
6
|
*
|
|
@@ -15,7 +15,7 @@ export class Resource {
|
|
|
15
15
|
payload;
|
|
16
16
|
/** Result validator. */
|
|
17
17
|
result;
|
|
18
|
-
constructor(name, payload =
|
|
18
|
+
constructor(name, payload = UNDEFINED, result = UNDEFINED) {
|
|
19
19
|
this.name = name;
|
|
20
20
|
this.payload = payload;
|
|
21
21
|
this.result = result;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataSchema,
|
|
1
|
+
import type { DataSchema, DataSchemas } from "../schema/DataSchema.js";
|
|
2
2
|
import type { DataKey, Database } from "../util/data.js";
|
|
3
3
|
import type { ItemQuery, Items, OptionalItem } from "../util/item.js";
|
|
4
4
|
import type { Sourceable } from "../util/source.js";
|
|
@@ -7,8 +7,8 @@ import type { AsyncProvider, Provider } from "./Provider.js";
|
|
|
7
7
|
import { AsyncThroughProvider, ThroughProvider } from "./ThroughProvider.js";
|
|
8
8
|
/** Validate a synchronous source provider (source can have any type because validation guarantees the type). */
|
|
9
9
|
export declare class ValidationProvider<T extends Database> extends ThroughProvider<T> implements Provider<T>, Sourceable<Provider<T>> {
|
|
10
|
-
readonly schemas:
|
|
11
|
-
constructor(schemas:
|
|
10
|
+
readonly schemas: DataSchemas<T>;
|
|
11
|
+
constructor(schemas: DataSchemas<T>, source: Provider<T>);
|
|
12
12
|
/** Get a named schema. */
|
|
13
13
|
getSchema<K extends DataKey<T>>(collection: K): DataSchema<T[K]>;
|
|
14
14
|
getItem<K extends DataKey<T>>(collection: K, id: string): OptionalItem<T[K]>;
|
|
@@ -26,8 +26,9 @@ export declare class ValidationProvider<T extends Database> extends ThroughProvi
|
|
|
26
26
|
}
|
|
27
27
|
/** Validate an asynchronous source provider (source can have any type because validation guarantees the type). */
|
|
28
28
|
export declare class AsyncValidationProvider<T extends Database> extends AsyncThroughProvider<T> implements AsyncProvider<T>, Sourceable<AsyncProvider<T>> {
|
|
29
|
-
readonly schemas:
|
|
30
|
-
constructor(schemas:
|
|
29
|
+
readonly schemas: DataSchemas<T>;
|
|
30
|
+
constructor(schemas: DataSchemas<T>, source: AsyncProvider<T>);
|
|
31
|
+
/** Get a named data schema for this database. */
|
|
31
32
|
getSchema<K extends DataKey<T>>(collection: K): DataSchema<T[K]>;
|
|
32
33
|
getItem<K extends DataKey<T>>(collection: K, id: string): Promise<OptionalItem<T[K]>>;
|
|
33
34
|
getItemSequence<K extends DataKey<T>>(collection: K, id: string): AsyncIterable<OptionalItem<T[K]>>;
|
package/db/ValidationProvider.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import { ValidationError } from "../error/ValidationError.js";
|
|
1
|
+
import { ValidationError as ConflictError } from "../error/ValidationError.js";
|
|
2
2
|
import { Feedback } from "../feedback/Feedback.js";
|
|
3
|
+
import { KEY } from "../schema/KeySchema.js";
|
|
3
4
|
import { updateData } from "../util/update.js";
|
|
4
|
-
import {
|
|
5
|
+
import { validateData } from "../util/validate.js";
|
|
5
6
|
import { AsyncThroughProvider, ThroughProvider } from "./ThroughProvider.js";
|
|
6
|
-
// Constants.
|
|
7
|
-
const VALIDATION_CONTEXT_GET = { action: "get", id: true };
|
|
8
|
-
const VALIDATION_CONTEXT_ADD = { action: "add" };
|
|
9
|
-
const VALIDATION_CONTEXT_SET = { action: "set" };
|
|
10
|
-
const VALIDATION_CONTEXT_UPDATE = { action: "update", partial: true };
|
|
11
7
|
/** Validate a synchronous source provider (source can have any type because validation guarantees the type). */
|
|
12
8
|
export class ValidationProvider extends ThroughProvider {
|
|
13
9
|
schemas;
|
|
@@ -28,13 +24,13 @@ export class ValidationProvider extends ThroughProvider {
|
|
|
28
24
|
yield _validateItem(collection, unsafeItem, schema);
|
|
29
25
|
}
|
|
30
26
|
addItem(collection, data) {
|
|
31
|
-
return super.addItem(collection,
|
|
27
|
+
return super.addItem(collection, validateData(data, this.getSchema(collection).props));
|
|
32
28
|
}
|
|
33
29
|
setItem(collection, id, data) {
|
|
34
|
-
super.setItem(collection, id,
|
|
30
|
+
super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
|
|
35
31
|
}
|
|
36
32
|
updateItem(collection, id, updates) {
|
|
37
|
-
|
|
33
|
+
validateData(updateData({}, updates), this.getSchema(collection).props, true);
|
|
38
34
|
super.updateItem(collection, id, updates);
|
|
39
35
|
}
|
|
40
36
|
deleteItem(collection, id) {
|
|
@@ -50,7 +46,7 @@ export class ValidationProvider extends ThroughProvider {
|
|
|
50
46
|
super.setQuery(collection, query, this.getSchema(collection).validate(data));
|
|
51
47
|
}
|
|
52
48
|
updateQuery(collection, query, updates) {
|
|
53
|
-
|
|
49
|
+
validateData(updateData({}, updates), this.getSchema(collection).props, true);
|
|
54
50
|
super.updateQuery(collection, query, updates);
|
|
55
51
|
}
|
|
56
52
|
deleteQuery(collection, query) {
|
|
@@ -69,6 +65,7 @@ export class AsyncValidationProvider extends AsyncThroughProvider {
|
|
|
69
65
|
super(source);
|
|
70
66
|
this.schemas = schemas;
|
|
71
67
|
}
|
|
68
|
+
/** Get a named data schema for this database. */
|
|
72
69
|
getSchema(collection) {
|
|
73
70
|
return this.schemas[collection];
|
|
74
71
|
}
|
|
@@ -81,13 +78,13 @@ export class AsyncValidationProvider extends AsyncThroughProvider {
|
|
|
81
78
|
yield _validateItem(collection, unsafeItem, schema);
|
|
82
79
|
}
|
|
83
80
|
addItem(collection, data) {
|
|
84
|
-
return super.addItem(collection,
|
|
81
|
+
return super.addItem(collection, validateData(data, this.getSchema(collection).props));
|
|
85
82
|
}
|
|
86
83
|
setItem(collection, id, data) {
|
|
87
|
-
return super.setItem(collection, id,
|
|
84
|
+
return super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
|
|
88
85
|
}
|
|
89
86
|
updateItem(collection, id, updates) {
|
|
90
|
-
|
|
87
|
+
validateData(updateData({}, updates), this.getSchema(collection).props, true);
|
|
91
88
|
return super.updateItem(collection, id, updates);
|
|
92
89
|
}
|
|
93
90
|
deleteItem(collection, id) {
|
|
@@ -105,46 +102,49 @@ export class AsyncValidationProvider extends AsyncThroughProvider {
|
|
|
105
102
|
yield _validateItems(collection, unsafeItems, schema);
|
|
106
103
|
}
|
|
107
104
|
setQuery(collection, query, data) {
|
|
108
|
-
return super.setQuery(collection, query,
|
|
105
|
+
return super.setQuery(collection, query, validateData(data, this.getSchema(collection).props));
|
|
109
106
|
}
|
|
110
107
|
updateQuery(collection, query, updates) {
|
|
111
|
-
|
|
108
|
+
validateData(updateData({}, updates), this.getSchema(collection).props, true);
|
|
112
109
|
return super.updateQuery(collection, query, updates);
|
|
113
110
|
}
|
|
114
111
|
deleteQuery(collection, query) {
|
|
115
112
|
return super.deleteQuery(collection, query);
|
|
116
113
|
}
|
|
117
114
|
}
|
|
118
|
-
function _validateItem(collection,
|
|
119
|
-
if (!
|
|
115
|
+
function _validateItem(collection, unsafeItem, schema) {
|
|
116
|
+
if (!unsafeItem)
|
|
120
117
|
return undefined;
|
|
121
118
|
try {
|
|
122
|
-
return
|
|
119
|
+
return validateData(unsafeItem, { id: KEY, ...schema.props });
|
|
123
120
|
}
|
|
124
121
|
catch (thrown) {
|
|
125
122
|
if (!(thrown instanceof Feedback))
|
|
126
123
|
throw thrown;
|
|
127
|
-
throw new
|
|
124
|
+
throw new ConflictError(`Invalid data for "${collection}"`, thrown.message);
|
|
128
125
|
}
|
|
129
126
|
}
|
|
130
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Validate a set of entities for this query reference.
|
|
129
|
+
* @throws `ConflictError` if one or more items did not validate (conflict because the program is not in an expected state).
|
|
130
|
+
*/
|
|
131
131
|
function _validateItems(collection, unsafeEntities, schema) {
|
|
132
|
-
return Array.from(_yieldValidItems(collection, unsafeEntities, schema));
|
|
132
|
+
return Array.from(_yieldValidItems(collection, unsafeEntities, { id: KEY, ...schema.props }));
|
|
133
133
|
}
|
|
134
|
-
function* _yieldValidItems(collection, unsafeEntities,
|
|
134
|
+
function* _yieldValidItems(collection, unsafeEntities, validators) {
|
|
135
135
|
let invalid = false;
|
|
136
136
|
const messages = {};
|
|
137
|
-
for (const
|
|
137
|
+
for (const unsafeItem of unsafeEntities) {
|
|
138
138
|
try {
|
|
139
|
-
yield
|
|
139
|
+
yield validateData(unsafeItem, validators);
|
|
140
140
|
}
|
|
141
141
|
catch (thrown) {
|
|
142
142
|
if (!(thrown instanceof Feedback))
|
|
143
143
|
throw thrown;
|
|
144
144
|
invalid = true;
|
|
145
|
-
messages[
|
|
145
|
+
messages[unsafeItem.id] = thrown.message;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
if (invalid)
|
|
149
|
-
throw new
|
|
149
|
+
throw new ConflictError(`Invalid data for "${collection}"`, messages);
|
|
150
150
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EnhancedError } from "./EnhancedError.js";
|
|
2
2
|
/** Thrown if an operation failed because the user is logged in but does not have sufficient privileges to access something. */
|
|
3
3
|
export declare class UnauthorizedError extends EnhancedError {
|
|
4
|
-
readonly code =
|
|
4
|
+
readonly code = 401;
|
|
5
5
|
constructor(message?: string, context?: unknown);
|
|
6
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EnhancedError } from "./EnhancedError.js";
|
|
2
2
|
/** Thrown if an operation failed because the user is logged in but does not have sufficient privileges to access something. */
|
|
3
3
|
export class UnauthorizedError extends EnhancedError {
|
|
4
|
-
code =
|
|
4
|
+
code = 401;
|
|
5
5
|
constructor(message = "Unauthorized", context) {
|
|
6
6
|
super(message, context);
|
|
7
7
|
}
|
package/package.json
CHANGED
package/schema/ColorSchema.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { StringSchema } from "./StringSchema.js";
|
|
1
|
+
import { TextSchema, type TextSchemaOptions } from "./TextSchema.js";
|
|
3
2
|
/**
|
|
4
3
|
* Define a valid color hex string, e.g `#00CCFF`
|
|
5
4
|
*
|
|
@@ -9,8 +8,8 @@ import { StringSchema } from "./StringSchema.js";
|
|
|
9
8
|
*
|
|
10
9
|
* Colors are limited to 512 characters (this can be changed with `max`), but generally these won't be data: URIs so this is a reasonable limit.
|
|
11
10
|
*/
|
|
12
|
-
export declare class ColorSchema extends
|
|
13
|
-
constructor({ title, value, ...options }: Omit<
|
|
11
|
+
export declare class ColorSchema extends TextSchema {
|
|
12
|
+
constructor({ title, value, ...options }: Omit<TextSchemaOptions, "type" | "min" | "max" | "multiline" | "match">);
|
|
14
13
|
sanitize(insaneString: string): string;
|
|
15
14
|
}
|
|
16
15
|
/** Valid color hex string, e.g. `#00CCFF` (required because empty string is invalid). */
|
package/schema/ColorSchema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OPTIONAL } from "./OptionalSchema.js";
|
|
2
|
-
import {
|
|
2
|
+
import { TextSchema } from "./TextSchema.js";
|
|
3
3
|
const COLOR_REGEXP = /^#[0-9A-F]{6}$/;
|
|
4
4
|
const NOT_HEX_REGEXP = /[^0-9A-F]/g;
|
|
5
5
|
/**
|
|
@@ -11,7 +11,7 @@ const NOT_HEX_REGEXP = /[^0-9A-F]/g;
|
|
|
11
11
|
*
|
|
12
12
|
* Colors are limited to 512 characters (this can be changed with `max`), but generally these won't be data: URIs so this is a reasonable limit.
|
|
13
13
|
*/
|
|
14
|
-
export class ColorSchema extends
|
|
14
|
+
export class ColorSchema extends TextSchema {
|
|
15
15
|
constructor({ title = "Color", value = "#000000", ...options }) {
|
|
16
16
|
super({
|
|
17
17
|
title,
|
package/schema/DataSchema.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare class DataSchema<T extends Data> extends Schema<T> {
|
|
|
16
16
|
validate(unsafeValue?: unknown): T;
|
|
17
17
|
}
|
|
18
18
|
/** Set of named data schemas. */
|
|
19
|
-
export type
|
|
19
|
+
export type DataSchemas<T extends Database> = {
|
|
20
20
|
[K in keyof T]: DataSchema<T[K]>;
|
|
21
21
|
};
|
|
22
22
|
/** Valid data object with specifed properties. */
|
package/schema/EmailSchema.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StringSchemaOptions } from "./StringSchema.js";
|
|
2
|
-
import {
|
|
2
|
+
import { TextSchema } from "./TextSchema.js";
|
|
3
3
|
/**
|
|
4
4
|
* Define a valid email address.
|
|
5
5
|
*
|
|
@@ -17,9 +17,9 @@ import { StringSchema } from "./StringSchema.js";
|
|
|
17
17
|
* - Up to 10 segments of up to 63 characters each, separated by `.`
|
|
18
18
|
* - TLD is a segment of 2-63 characters, possibly in `xn--` international format.
|
|
19
19
|
*/
|
|
20
|
-
export declare class EmailSchema extends
|
|
20
|
+
export declare class EmailSchema extends TextSchema {
|
|
21
21
|
constructor({ title, ...options }: Omit<StringSchemaOptions, "type" | "min" | "max" | "match" | "multiline">);
|
|
22
|
-
sanitize(
|
|
22
|
+
sanitize(str: string): string;
|
|
23
23
|
}
|
|
24
24
|
/** Valid email, e.g. `test@test.com` */
|
|
25
25
|
export declare const EMAIL: EmailSchema;
|
package/schema/EmailSchema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OPTIONAL } from "./OptionalSchema.js";
|
|
2
|
-
import {
|
|
2
|
+
import { TextSchema } from "./TextSchema.js";
|
|
3
3
|
const R_MATCH = /^[a-z0-9](?:[a-zA-Z0-9._+-]{0,62}[a-zA-Z0-9])?@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.){1,3}(?:[a-z]{2,63}|xn--[a-z0-9-]{0,58}[a-z0-9])$/;
|
|
4
4
|
/**
|
|
5
5
|
* Define a valid email address.
|
|
@@ -18,7 +18,7 @@ const R_MATCH = /^[a-z0-9](?:[a-zA-Z0-9._+-]{0,62}[a-zA-Z0-9])?@(?:[a-z0-9](?:[a
|
|
|
18
18
|
* - Up to 10 segments of up to 63 characters each, separated by `.`
|
|
19
19
|
* - TLD is a segment of 2-63 characters, possibly in `xn--` international format.
|
|
20
20
|
*/
|
|
21
|
-
export class EmailSchema extends
|
|
21
|
+
export class EmailSchema extends TextSchema {
|
|
22
22
|
constructor({ title = "Email", ...options }) {
|
|
23
23
|
super({
|
|
24
24
|
title,
|
|
@@ -30,9 +30,8 @@ export class EmailSchema extends StringSchema {
|
|
|
30
30
|
multiline: false,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
sanitize(
|
|
34
|
-
|
|
35
|
-
return typeof sanitizedString === "string" ? sanitizedString.toLowerCase() : sanitizedString;
|
|
33
|
+
sanitize(str) {
|
|
34
|
+
return super.sanitize(str).toLowerCase();
|
|
36
35
|
}
|
|
37
36
|
}
|
|
38
37
|
/** Valid email, e.g. `test@test.com` */
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Data, Database } from "../util/data.js";
|
|
2
|
+
import type { Item } from "../util/item.js";
|
|
3
|
+
import type { Validators } from "../util/validate.js";
|
|
4
|
+
import { DataSchema } from "./DataSchema.js";
|
|
5
|
+
import type { KeySchema } from "./KeySchema.js";
|
|
6
|
+
import { type OptionalSchema } from "./OptionalSchema.js";
|
|
7
|
+
import type { SchemaOptions } from "./Schema.js";
|
|
8
|
+
/** Allowed options for `ItemSchema` */
|
|
9
|
+
export interface ItemSchemaOptions<T extends Data> extends SchemaOptions {
|
|
10
|
+
readonly id?: KeySchema | undefined;
|
|
11
|
+
readonly props: Validators<T>;
|
|
12
|
+
readonly value?: Partial<Item<T>> | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Validate an item object. */
|
|
15
|
+
export declare class ItemSchema<T extends Data> extends DataSchema<Item<T>> {
|
|
16
|
+
constructor({ id, props, ...options }: ItemSchemaOptions<T>);
|
|
17
|
+
}
|
|
18
|
+
/** Set of named item schemas. */
|
|
19
|
+
export type ItemSchemas<T extends Database> = {
|
|
20
|
+
[K in keyof T]: DataSchema<Item<T[K]>>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Valid item object with specifed properties.
|
|
24
|
+
* - An `Item` is a `Data` object with a string `.id` prop.
|
|
25
|
+
* - Optional validator for `.id` must be a `KeySchema` and defaults to `KEY`.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ITEM: <T extends Data>(props: Validators<T> | DataSchema<T>, id?: KeySchema) => ItemSchema<Item<T>>;
|
|
28
|
+
/**
|
|
29
|
+
* Valid item object or `null`.
|
|
30
|
+
* - An `Item` is a `Data` object with a string `.id` prop.
|
|
31
|
+
* - Optional validator for `.id` must be a `KeySchema` and defaults to `KEY`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const OPTIONAL_ITEM: <T extends Data>(props: Validators<T> | DataSchema<T>, id?: KeySchema) => OptionalSchema<Item<T>>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DataSchema } from "./DataSchema.js";
|
|
2
|
+
import { KEY } from "./KeySchema.js";
|
|
3
|
+
import { OPTIONAL } from "./OptionalSchema.js";
|
|
4
|
+
/** Validate an item object. */
|
|
5
|
+
export class ItemSchema extends DataSchema {
|
|
6
|
+
constructor({ id = KEY, props, ...options }) {
|
|
7
|
+
super({ props: { id, ...props }, ...options });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Valid item object with specifed properties.
|
|
12
|
+
* - An `Item` is a `Data` object with a string `.id` prop.
|
|
13
|
+
* - Optional validator for `.id` must be a `KeySchema` and defaults to `KEY`.
|
|
14
|
+
*/
|
|
15
|
+
export const ITEM = (props, id) => props instanceof DataSchema ? new ItemSchema({ id, ...props }) : new ItemSchema({ props, id });
|
|
16
|
+
/**
|
|
17
|
+
* Valid item object or `null`.
|
|
18
|
+
* - An `Item` is a `Data` object with a string `.id` prop.
|
|
19
|
+
* - Optional validator for `.id` must be a `KeySchema` and defaults to `KEY`.
|
|
20
|
+
*/
|
|
21
|
+
export const OPTIONAL_ITEM = (props, id) => OPTIONAL(ITEM(props, id));
|
package/schema/KeySchema.d.ts
CHANGED
|
@@ -2,11 +2,14 @@ import { StringSchema, type StringSchemaOptions } from "./StringSchema.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* Define a valid database key.
|
|
4
4
|
*
|
|
5
|
+
* - Characters that are not a-z, A-Z, 0-9 are removed.
|
|
5
6
|
* - Default minimum key length is 1 character.
|
|
6
|
-
* - Default maximum key length is
|
|
7
|
+
* - Default maximum key length is 32 characters.
|
|
8
|
+
* - 32 characters is enough for UUIDs, as the 4 `-` hyphens are removed.
|
|
7
9
|
*/
|
|
8
10
|
export declare class KeySchema extends StringSchema {
|
|
9
11
|
constructor({ min, max, ...options }: StringSchemaOptions);
|
|
12
|
+
sanitize(str: string): string;
|
|
10
13
|
}
|
|
11
14
|
/** Valid database key. */
|
|
12
15
|
export declare const KEY: KeySchema;
|
package/schema/KeySchema.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { OPTIONAL } from "./OptionalSchema.js";
|
|
2
2
|
import { StringSchema } from "./StringSchema.js";
|
|
3
|
+
const R_NOT_CHAR = /[^a-zA-Z0-9]/g;
|
|
3
4
|
/**
|
|
4
5
|
* Define a valid database key.
|
|
5
6
|
*
|
|
7
|
+
* - Characters that are not a-z, A-Z, 0-9 are removed.
|
|
6
8
|
* - Default minimum key length is 1 character.
|
|
7
|
-
* - Default maximum key length is
|
|
9
|
+
* - Default maximum key length is 32 characters.
|
|
10
|
+
* - 32 characters is enough for UUIDs, as the 4 `-` hyphens are removed.
|
|
8
11
|
*/
|
|
9
12
|
export class KeySchema extends StringSchema {
|
|
10
|
-
constructor({ min = 1, max =
|
|
13
|
+
constructor({ min = 1, max = 32, ...options }) {
|
|
11
14
|
super({ min, max, ...options });
|
|
12
15
|
}
|
|
16
|
+
sanitize(str) {
|
|
17
|
+
return str.replace(R_NOT_CHAR, "");
|
|
18
|
+
}
|
|
13
19
|
}
|
|
14
20
|
/** Valid database key. */
|
|
15
|
-
export const KEY = new KeySchema({});
|
|
21
|
+
export const KEY = new KeySchema({ title: "ID" });
|
|
16
22
|
/** Valid optional database key. */
|
|
17
23
|
export const OPTIONAL_KEY = OPTIONAL(KEY);
|
package/schema/LinkSchema.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ImmutableArray } from "../util/array.js";
|
|
2
2
|
import { type AbsoluteLink } from "../util/link.js";
|
|
3
3
|
import type { StringSchemaOptions } from "./StringSchema.js";
|
|
4
|
-
import {
|
|
4
|
+
import { TextSchema } from "./TextSchema.js";
|
|
5
5
|
/** Allowed options for `LinkSchema` */
|
|
6
6
|
export interface LinkSchemaOptions extends Omit<StringSchemaOptions, "type" | "min" | "max" | "multiline"> {
|
|
7
7
|
readonly base?: AbsoluteLink | undefined;
|
|
@@ -14,7 +14,7 @@ export interface LinkSchemaOptions extends Omit<StringSchemaOptions, "type" | "m
|
|
|
14
14
|
* - URLs are limited to 512 characters, but generally these won't be data: URIs so this is a reasonable limit.
|
|
15
15
|
* - Falsy values are converted to `""` empty string.
|
|
16
16
|
*/
|
|
17
|
-
export declare class LinkSchema extends
|
|
17
|
+
export declare class LinkSchema extends TextSchema {
|
|
18
18
|
readonly base: AbsoluteLink | undefined;
|
|
19
19
|
readonly schemes: ImmutableArray<string> | undefined;
|
|
20
20
|
readonly hosts: ImmutableArray<string> | undefined;
|
package/schema/LinkSchema.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
2
|
import { getOptionalLinkURL } from "../util/link.js";
|
|
3
3
|
import { OPTIONAL } from "./OptionalSchema.js";
|
|
4
|
-
import {
|
|
4
|
+
import { TextSchema } from "./TextSchema.js";
|
|
5
5
|
/**
|
|
6
6
|
* Type of `StringSchema` that defines a valid URL link.
|
|
7
7
|
* - Checks URL scheme against a whitelist (always), and checks URL domain against a whitelist (optional).
|
|
8
8
|
* - URLs are limited to 512 characters, but generally these won't be data: URIs so this is a reasonable limit.
|
|
9
9
|
* - Falsy values are converted to `""` empty string.
|
|
10
10
|
*/
|
|
11
|
-
export class LinkSchema extends
|
|
11
|
+
export class LinkSchema extends TextSchema {
|
|
12
12
|
base;
|
|
13
13
|
schemes;
|
|
14
14
|
hosts;
|
|
@@ -27,10 +27,10 @@ export class LinkSchema extends StringSchema {
|
|
|
27
27
|
}
|
|
28
28
|
// Override to clean the URL using builtin helper functions and check the schemes and hosts against the whitelists.
|
|
29
29
|
validate(unsafeValue) {
|
|
30
|
-
const
|
|
31
|
-
const url = getOptionalLinkURL(
|
|
30
|
+
const str = super.validate(unsafeValue);
|
|
31
|
+
const url = getOptionalLinkURL(str, this.base, this.schemes, this.hosts);
|
|
32
32
|
if (!url)
|
|
33
|
-
throw new ValueFeedback(
|
|
33
|
+
throw new ValueFeedback(str ? "Invalid format" : "Required", str);
|
|
34
34
|
return url.href;
|
|
35
35
|
}
|
|
36
36
|
}
|
package/schema/NumberSchema.js
CHANGED
|
@@ -26,7 +26,7 @@ export class NumberSchema extends Schema {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
/** Valid number, e.g. `2048.12345` or `0` zero. */
|
|
29
|
-
export const NUMBER = new NumberSchema({});
|
|
29
|
+
export const NUMBER = new NumberSchema({ title: "Number" });
|
|
30
30
|
/** Valid optional number, e.g. `2048.12345` or `0` zero, or `null` */
|
|
31
31
|
export const OPTIONAL_NUMBER = OPTIONAL(NUMBER);
|
|
32
32
|
/** Valid integer number, e.g. `2048` or `0` zero. */
|
package/schema/PhoneSchema.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { StringSchemaOptions } from "./StringSchema.js";
|
|
2
|
-
import {
|
|
2
|
+
import { TextSchema } from "./TextSchema.js";
|
|
3
3
|
/**
|
|
4
4
|
* Type of `StringSchema` that defines a valid phone number.
|
|
5
5
|
* - Multiple string formats are automatically converted to E.164 format (starting with `+` plus).
|
|
6
6
|
* - Falsy values are converted to `""` empty string.
|
|
7
7
|
*/
|
|
8
|
-
export declare class PhoneSchema extends
|
|
8
|
+
export declare class PhoneSchema extends TextSchema {
|
|
9
9
|
constructor({ title, ...options }: StringSchemaOptions);
|
|
10
10
|
sanitize(insaneString: string): string;
|
|
11
11
|
}
|
package/schema/PhoneSchema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OPTIONAL } from "./OptionalSchema.js";
|
|
2
|
-
import {
|
|
2
|
+
import { TextSchema } from "./TextSchema.js";
|
|
3
3
|
// Valid phone number is max 16 digits made up of:
|
|
4
4
|
// - Country code (`+` plus character and 1-3 digits, e.g. `+44` or `+1`).
|
|
5
5
|
// - Subscriber number (5-12 digits — the Solomon Islands have five-digit phone numbers apparently).
|
|
@@ -9,7 +9,7 @@ const PHONE_REGEXP = /^\+[1-9][0-9]{0,2}[0-9]{5,12}$/;
|
|
|
9
9
|
* - Multiple string formats are automatically converted to E.164 format (starting with `+` plus).
|
|
10
10
|
* - Falsy values are converted to `""` empty string.
|
|
11
11
|
*/
|
|
12
|
-
export class PhoneSchema extends
|
|
12
|
+
export class PhoneSchema extends TextSchema {
|
|
13
13
|
constructor({ title = "Phone", ...options }) {
|
|
14
14
|
super({
|
|
15
15
|
title,
|
package/schema/SlugSchema.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { StringSchema, type StringSchemaOptions } from "./StringSchema.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare class SlugSchema extends StringSchema {
|
|
10
10
|
constructor(options: Omit<StringSchemaOptions, "min" | "max" | "multiline">);
|
|
11
|
-
sanitize(
|
|
11
|
+
sanitize(str: string): string;
|
|
12
12
|
}
|
|
13
13
|
/** Valid slug, e.g. `this-is-a-slug` */
|
|
14
14
|
export declare const SLUG: SlugSchema;
|
package/schema/SlugSchema.js
CHANGED
|
@@ -14,11 +14,10 @@ export class SlugSchema extends StringSchema {
|
|
|
14
14
|
...options,
|
|
15
15
|
min: 2,
|
|
16
16
|
max: 32,
|
|
17
|
-
multiline: false,
|
|
18
17
|
});
|
|
19
18
|
}
|
|
20
|
-
sanitize(
|
|
21
|
-
return getSlug(
|
|
19
|
+
sanitize(str) {
|
|
20
|
+
return getSlug(str);
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
/** Valid slug, e.g. `this-is-a-slug` */
|
package/schema/StringSchema.d.ts
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import type { SchemaOptions } from "./Schema.js";
|
|
2
2
|
import { Schema } from "./Schema.js";
|
|
3
|
-
/** `type=""` prop for HTML `<input />` tags that are relevant for strings. */
|
|
4
|
-
export type HtmlInputType = "text" | "password" | "color" | "date" | "email" | "number" | "tel" | "search" | "url";
|
|
5
3
|
/** Function that sanitizes a string. */
|
|
6
4
|
export type Sanitizer = (str: string) => string;
|
|
7
5
|
/** Options for `StringSchema` */
|
|
8
6
|
export interface StringSchemaOptions extends SchemaOptions {
|
|
9
7
|
readonly value?: string | undefined;
|
|
10
|
-
readonly type?: HtmlInputType | undefined;
|
|
11
8
|
readonly min?: number | undefined;
|
|
12
9
|
readonly max?: number | undefined;
|
|
13
|
-
readonly match?: RegExp | undefined;
|
|
14
|
-
readonly sanitizer?: Sanitizer | undefined;
|
|
15
|
-
readonly multiline?: boolean | undefined;
|
|
16
10
|
}
|
|
17
11
|
/**
|
|
18
12
|
* Schema that defines a valid string.
|
|
19
13
|
*
|
|
20
|
-
* Ensures value is string and optionally enforces min/max length, whether to trim whitespace, and regex match format.
|
|
21
|
-
* Doesn't allow `null` to mean no value — empty string is the equivalent for StringSchema (because it means we'll never accidentally get `"null"` by converting the `null` to string).
|
|
22
|
-
*
|
|
23
|
-
* Defaults to a single line text string (newlines are stripped). Use `multiline=true` to allow newlines.
|
|
24
|
-
*
|
|
25
14
|
* @example
|
|
26
15
|
* const schema = new StringSchema({ default: 'abc', required: true, min: 2, max: 6, match: /^[a-z0-9]+$/, trim: true });
|
|
27
16
|
* schema.validate('def'); // Returns 'def'
|
|
@@ -36,32 +25,14 @@ export interface StringSchemaOptions extends SchemaOptions {
|
|
|
36
25
|
*/
|
|
37
26
|
export declare class StringSchema extends Schema<string> {
|
|
38
27
|
readonly value: string;
|
|
39
|
-
readonly type: HtmlInputType;
|
|
40
28
|
readonly min: number;
|
|
41
29
|
readonly max: number;
|
|
42
|
-
|
|
43
|
-
readonly sanitizer: Sanitizer | undefined;
|
|
44
|
-
readonly multiline: boolean;
|
|
45
|
-
constructor({ type, min, max, match, sanitizer, multiline, value, ...options }: StringSchemaOptions);
|
|
30
|
+
constructor({ min, max, value, ...options }: StringSchemaOptions);
|
|
46
31
|
validate(unsafeValue?: unknown): string;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
* - Might be empty string if the string contained only invalid characters.
|
|
50
|
-
* - Applies `options.sanitizer` too (if it's set).
|
|
51
|
-
*/
|
|
52
|
-
sanitize(insaneString: string): string;
|
|
32
|
+
/** Sanitize the string by removing unwanted characters. */
|
|
33
|
+
sanitize(str: string): string;
|
|
53
34
|
}
|
|
54
35
|
/** Valid string, e.g. `Hello there!` */
|
|
55
36
|
export declare const STRING: StringSchema;
|
|
56
37
|
/** Valid string, `Hello there!`, with more than one character. */
|
|
57
38
|
export declare const REQUIRED_STRING: StringSchema;
|
|
58
|
-
/** Title string, e.g. `Title of something` */
|
|
59
|
-
export declare const TITLE: StringSchema;
|
|
60
|
-
/** Optional name string, e.g. `Title of something` or `null` */
|
|
61
|
-
export declare const OPTIONAL_TITLE: import("./OptionalSchema.js").OptionalSchema<string>;
|
|
62
|
-
/** Name string, e.g. `Name of Something` */
|
|
63
|
-
export declare const NAME: StringSchema;
|
|
64
|
-
/** Optional name string, e.g. `Name of Something` or `null` */
|
|
65
|
-
export declare const OPTIONAL_NAME: import("./OptionalSchema.js").OptionalSchema<string>;
|
|
66
|
-
/** Password string. */
|
|
67
|
-
export declare const PASSWORD: StringSchema;
|
package/schema/StringSchema.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
|
-
import { sanitizeLines, sanitizeString } from "../util/string.js";
|
|
3
|
-
import { OPTIONAL } from "./OptionalSchema.js";
|
|
4
2
|
import { Schema } from "./Schema.js";
|
|
5
3
|
/**
|
|
6
4
|
* Schema that defines a valid string.
|
|
7
5
|
*
|
|
8
|
-
* Ensures value is string and optionally enforces min/max length, whether to trim whitespace, and regex match format.
|
|
9
|
-
* Doesn't allow `null` to mean no value — empty string is the equivalent for StringSchema (because it means we'll never accidentally get `"null"` by converting the `null` to string).
|
|
10
|
-
*
|
|
11
|
-
* Defaults to a single line text string (newlines are stripped). Use `multiline=true` to allow newlines.
|
|
12
|
-
*
|
|
13
6
|
* @example
|
|
14
7
|
* const schema = new StringSchema({ default: 'abc', required: true, min: 2, max: 6, match: /^[a-z0-9]+$/, trim: true });
|
|
15
8
|
* schema.validate('def'); // Returns 'def'
|
|
@@ -23,20 +16,12 @@ import { Schema } from "./Schema.js";
|
|
|
23
16
|
* schema.validate('j'); // Throws 'Minimum 3 chaacters'
|
|
24
17
|
*/
|
|
25
18
|
export class StringSchema extends Schema {
|
|
26
|
-
type;
|
|
27
19
|
min;
|
|
28
20
|
max;
|
|
29
|
-
|
|
30
|
-
sanitizer;
|
|
31
|
-
multiline;
|
|
32
|
-
constructor({ type = "text", min = 0, max = Number.POSITIVE_INFINITY, match, sanitizer, multiline = false, value = "", ...options }) {
|
|
21
|
+
constructor({ min = 0, max = Number.POSITIVE_INFINITY, value = "", ...options }) {
|
|
33
22
|
super({ value, ...options });
|
|
34
|
-
this.type = type;
|
|
35
23
|
this.min = min;
|
|
36
24
|
this.max = max;
|
|
37
|
-
this.match = match;
|
|
38
|
-
this.sanitizer = sanitizer;
|
|
39
|
-
this.multiline = multiline;
|
|
40
25
|
}
|
|
41
26
|
validate(unsafeValue = this.value) {
|
|
42
27
|
const possibleString = typeof unsafeValue === "number" ? unsafeValue.toString() : unsafeValue;
|
|
@@ -47,30 +32,14 @@ export class StringSchema extends Schema {
|
|
|
47
32
|
throw new ValueFeedback(saneString ? `Minimum ${this.min} characters` : "Required", saneString);
|
|
48
33
|
if (saneString.length > this.max)
|
|
49
34
|
throw new ValueFeedback(`Maximum ${this.max} characters`, saneString);
|
|
50
|
-
if (this.match && !this.match.test(saneString))
|
|
51
|
-
throw new ValueFeedback(saneString ? "Invalid format" : "Required", saneString);
|
|
52
35
|
return saneString;
|
|
53
36
|
}
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
* - Applies `options.sanitizer` too (if it's set).
|
|
58
|
-
*/
|
|
59
|
-
sanitize(insaneString) {
|
|
60
|
-
return this.sanitizer ? this.sanitizer(insaneString) : this.multiline ? sanitizeLines(insaneString) : sanitizeString(insaneString);
|
|
37
|
+
/** Sanitize the string by removing unwanted characters. */
|
|
38
|
+
sanitize(str) {
|
|
39
|
+
return str;
|
|
61
40
|
}
|
|
62
41
|
}
|
|
63
42
|
/** Valid string, e.g. `Hello there!` */
|
|
64
43
|
export const STRING = new StringSchema({});
|
|
65
44
|
/** Valid string, `Hello there!`, with more than one character. */
|
|
66
45
|
export const REQUIRED_STRING = new StringSchema({ min: 1 });
|
|
67
|
-
/** Title string, e.g. `Title of something` */
|
|
68
|
-
export const TITLE = new StringSchema({ title: "Title", min: 1, max: 100 });
|
|
69
|
-
/** Optional name string, e.g. `Title of something` or `null` */
|
|
70
|
-
export const OPTIONAL_TITLE = OPTIONAL(TITLE);
|
|
71
|
-
/** Name string, e.g. `Name of Something` */
|
|
72
|
-
export const NAME = new StringSchema({ title: "Name", min: 1, max: 100 });
|
|
73
|
-
/** Optional name string, e.g. `Name of Something` or `null` */
|
|
74
|
-
export const OPTIONAL_NAME = OPTIONAL(NAME);
|
|
75
|
-
/** Password string. */
|
|
76
|
-
export const PASSWORD = new StringSchema({ title: "Password", min: 6, type: "password" });
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Sanitizer, StringSchema, type StringSchemaOptions } from "./StringSchema.js";
|
|
2
|
+
/** `type=""` prop for HTML `<input />` tags that are relevant for strings. */
|
|
3
|
+
export type TextSchemaType = "text" | "password" | "color" | "date" | "email" | "number" | "tel" | "search" | "url";
|
|
4
|
+
/** Options for `TextSchema` */
|
|
5
|
+
export interface TextSchemaOptions extends StringSchemaOptions {
|
|
6
|
+
readonly type?: TextSchemaType | undefined;
|
|
7
|
+
readonly match?: RegExp | undefined;
|
|
8
|
+
readonly multiline?: boolean | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Schema that defines a valid text string.
|
|
12
|
+
*
|
|
13
|
+
* Ensures value is string and optionally enforces min/max length, whether to trim whitespace, and regex match format.
|
|
14
|
+
* Doesn't allow `null` to mean no value — empty string is the equivalent for StringSchema (because it means we'll never accidentally get `"null"` by converting the `null` to string).
|
|
15
|
+
*
|
|
16
|
+
* Defaults to a single line text string (newlines are stripped). Use `multiline=true` to allow newlines.
|
|
17
|
+
*/
|
|
18
|
+
export declare class TextSchema extends StringSchema {
|
|
19
|
+
readonly type: TextSchemaType;
|
|
20
|
+
readonly match: RegExp | undefined;
|
|
21
|
+
readonly sanitizer: Sanitizer | undefined;
|
|
22
|
+
readonly multiline: boolean;
|
|
23
|
+
constructor({ type, match, multiline, ...options }: TextSchemaOptions);
|
|
24
|
+
validate(unsafeValue?: unknown): string;
|
|
25
|
+
sanitize(str: string): string;
|
|
26
|
+
}
|
|
27
|
+
/** Valid text, e.g. `Hello there!` */
|
|
28
|
+
export declare const TEXT: TextSchema;
|
|
29
|
+
/** Valid text, `Hello there!`, with more than one character. */
|
|
30
|
+
export declare const REQUIRED_TEXT: TextSchema;
|
|
31
|
+
/** Title string, e.g. `Title of something` */
|
|
32
|
+
export declare const TITLE: TextSchema;
|
|
33
|
+
/** Optional name string, e.g. `Title of something` or `null` */
|
|
34
|
+
export declare const OPTIONAL_TITLE: import("./OptionalSchema.js").OptionalSchema<string>;
|
|
35
|
+
/** Name string, e.g. `Name of Something` */
|
|
36
|
+
export declare const NAME: TextSchema;
|
|
37
|
+
/** Optional name string, e.g. `Name of Something` or `null` */
|
|
38
|
+
export declare const OPTIONAL_NAME: import("./OptionalSchema.js").OptionalSchema<string>;
|
|
39
|
+
/** Password string. */
|
|
40
|
+
export declare const PASSWORD: TextSchema;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
|
+
import { sanitizeMultilineText, sanitizeText } from "../util/string.js";
|
|
3
|
+
import { OPTIONAL } from "./OptionalSchema.js";
|
|
4
|
+
import { StringSchema } from "./StringSchema.js";
|
|
5
|
+
/**
|
|
6
|
+
* Schema that defines a valid text string.
|
|
7
|
+
*
|
|
8
|
+
* Ensures value is string and optionally enforces min/max length, whether to trim whitespace, and regex match format.
|
|
9
|
+
* Doesn't allow `null` to mean no value — empty string is the equivalent for StringSchema (because it means we'll never accidentally get `"null"` by converting the `null` to string).
|
|
10
|
+
*
|
|
11
|
+
* Defaults to a single line text string (newlines are stripped). Use `multiline=true` to allow newlines.
|
|
12
|
+
*/
|
|
13
|
+
export class TextSchema extends StringSchema {
|
|
14
|
+
type;
|
|
15
|
+
match;
|
|
16
|
+
sanitizer;
|
|
17
|
+
multiline;
|
|
18
|
+
constructor({ type = "text", match, multiline = false, ...options }) {
|
|
19
|
+
super(options);
|
|
20
|
+
this.type = type;
|
|
21
|
+
this.match = match;
|
|
22
|
+
this.multiline = multiline;
|
|
23
|
+
}
|
|
24
|
+
validate(unsafeValue = this.value) {
|
|
25
|
+
const str = super.validate(unsafeValue);
|
|
26
|
+
if (this.match && !this.match.test(str))
|
|
27
|
+
throw new ValueFeedback(str ? "Invalid format" : "Required", str);
|
|
28
|
+
return str;
|
|
29
|
+
}
|
|
30
|
+
sanitize(str) {
|
|
31
|
+
return this.multiline ? sanitizeMultilineText(str) : sanitizeText(str);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Valid text, e.g. `Hello there!` */
|
|
35
|
+
export const TEXT = new TextSchema({ title: "Text" });
|
|
36
|
+
/** Valid text, `Hello there!`, with more than one character. */
|
|
37
|
+
export const REQUIRED_TEXT = new TextSchema({ min: 1 });
|
|
38
|
+
/** Title string, e.g. `Title of something` */
|
|
39
|
+
export const TITLE = new TextSchema({ title: "Title", min: 1, max: 100 });
|
|
40
|
+
/** Optional name string, e.g. `Title of something` or `null` */
|
|
41
|
+
export const OPTIONAL_TITLE = OPTIONAL(TITLE);
|
|
42
|
+
/** Name string, e.g. `Name of Something` */
|
|
43
|
+
export const NAME = new TextSchema({ title: "Name", min: 1, max: 100 });
|
|
44
|
+
/** Optional name string, e.g. `Name of Something` or `null` */
|
|
45
|
+
export const OPTIONAL_NAME = OPTIONAL(NAME);
|
|
46
|
+
/** Password string. */
|
|
47
|
+
export const PASSWORD = new TextSchema({ title: "Password", min: 6, type: "password" });
|
package/schema/index.d.ts
CHANGED
|
@@ -9,12 +9,14 @@ export * from "./DictionarySchema.js";
|
|
|
9
9
|
export * from "./EmailSchema.js";
|
|
10
10
|
export * from "./EntitySchema.js";
|
|
11
11
|
export * from "./FileSchema.js";
|
|
12
|
+
export * from "./ItemSchema.js";
|
|
12
13
|
export * from "./KeySchema.js";
|
|
13
14
|
export * from "./LinkSchema.js";
|
|
14
15
|
export * from "./NumberSchema.js";
|
|
15
16
|
export * from "./PhoneSchema.js";
|
|
16
17
|
export * from "./SlugSchema.js";
|
|
17
18
|
export * from "./StringSchema.js";
|
|
19
|
+
export * from "./TextSchema.js";
|
|
18
20
|
export * from "./TimeSchema.js";
|
|
19
21
|
export * from "./ThroughSchema.js";
|
|
20
22
|
export * from "./OptionalSchema.js";
|
package/schema/index.js
CHANGED
|
@@ -9,12 +9,14 @@ export * from "./DictionarySchema.js";
|
|
|
9
9
|
export * from "./EmailSchema.js";
|
|
10
10
|
export * from "./EntitySchema.js";
|
|
11
11
|
export * from "./FileSchema.js";
|
|
12
|
+
export * from "./ItemSchema.js";
|
|
12
13
|
export * from "./KeySchema.js";
|
|
13
14
|
export * from "./LinkSchema.js";
|
|
14
15
|
export * from "./NumberSchema.js";
|
|
15
16
|
export * from "./PhoneSchema.js";
|
|
16
17
|
export * from "./SlugSchema.js";
|
|
17
18
|
export * from "./StringSchema.js";
|
|
19
|
+
export * from "./TextSchema.js";
|
|
18
20
|
export * from "./TimeSchema.js";
|
|
19
21
|
export * from "./ThroughSchema.js";
|
|
20
22
|
export * from "./OptionalSchema.js";
|
package/util/string.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export declare function getStringLength(str: string, min?: number, max?: number)
|
|
|
36
36
|
/** Concatenate an iterable set of strings together. */
|
|
37
37
|
export declare function joinStrings(strs: Iterable<string> & NotString, joiner?: string): string;
|
|
38
38
|
/**
|
|
39
|
-
* Sanitize a single
|
|
39
|
+
* Sanitize a single line of text.
|
|
40
40
|
* - Used when you're sanitising a single-line input, e.g. a title for something.
|
|
41
41
|
* - Remove allow control characters
|
|
42
42
|
* - Normalise runs of whitespace to one ` ` space,
|
|
@@ -44,9 +44,9 @@ export declare function joinStrings(strs: Iterable<string> & NotString, joiner?:
|
|
|
44
44
|
*
|
|
45
45
|
* @example santizeString("\x00Nice! "); // Returns `"Nice!"`
|
|
46
46
|
*/
|
|
47
|
-
export declare function
|
|
47
|
+
export declare function sanitizeText(str: string): string;
|
|
48
48
|
/**
|
|
49
|
-
* Sanitize
|
|
49
|
+
* Sanitize multiple lines of text.
|
|
50
50
|
* - Used when you're sanitising a multi-line input, e.g. a description for something.
|
|
51
51
|
* - Remove all control characters except `\n` newline.
|
|
52
52
|
* - Normalise weird characters like paragraph separator, line separator, `\t` tab, `\r` carriage return.
|
|
@@ -55,7 +55,7 @@ export declare function sanitizeString(str: string): string;
|
|
|
55
55
|
* - Allow spaces at the start of each line (for indentation) but trim the end of each line.
|
|
56
56
|
* - Trim excess newlines at the start and end of the string and runs of more than two newlines in a row.
|
|
57
57
|
*/
|
|
58
|
-
export declare function
|
|
58
|
+
export declare function sanitizeMultilineText(str: string): string;
|
|
59
59
|
/**
|
|
60
60
|
* Simplify a string by removing anything that isn't a number, letter, or space.
|
|
61
61
|
* - Normalizes the string by
|
package/util/string.js
CHANGED
|
@@ -65,7 +65,7 @@ export function joinStrings(strs, joiner = "") {
|
|
|
65
65
|
return getArray(strs).join(joiner);
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
|
-
* Sanitize a single
|
|
68
|
+
* Sanitize a single line of text.
|
|
69
69
|
* - Used when you're sanitising a single-line input, e.g. a title for something.
|
|
70
70
|
* - Remove allow control characters
|
|
71
71
|
* - Normalise runs of whitespace to one ` ` space,
|
|
@@ -73,14 +73,14 @@ export function joinStrings(strs, joiner = "") {
|
|
|
73
73
|
*
|
|
74
74
|
* @example santizeString("\x00Nice! "); // Returns `"Nice!"`
|
|
75
75
|
*/
|
|
76
|
-
export function
|
|
76
|
+
export function sanitizeText(str) {
|
|
77
77
|
return str
|
|
78
78
|
.replace(/[^\P{C}\s]/gu, "") // Strip control characters (except whitespace).
|
|
79
79
|
.replace(/\s+/gu, " ") // Normalise runs of whitespace to one ` ` space.
|
|
80
80
|
.trim(); // Trim whitespace from the start and end of the string.
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
|
-
* Sanitize
|
|
83
|
+
* Sanitize multiple lines of text.
|
|
84
84
|
* - Used when you're sanitising a multi-line input, e.g. a description for something.
|
|
85
85
|
* - Remove all control characters except `\n` newline.
|
|
86
86
|
* - Normalise weird characters like paragraph separator, line separator, `\t` tab, `\r` carriage return.
|
|
@@ -89,7 +89,7 @@ export function sanitizeString(str) {
|
|
|
89
89
|
* - Allow spaces at the start of each line (for indentation) but trim the end of each line.
|
|
90
90
|
* - Trim excess newlines at the start and end of the string and runs of more than two newlines in a row.
|
|
91
91
|
*/
|
|
92
|
-
export function
|
|
92
|
+
export function sanitizeMultilineText(str) {
|
|
93
93
|
return str
|
|
94
94
|
.replace(/[^\P{C}\s]/gu, "") // Strip control characters (except whitespace).
|
|
95
95
|
.replace(/\r\n?|\v|\x85|\u2028/g, "\n") // Normalise line separators to `\n` newline
|
package/util/validate.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ImmutableArray, PossibleArray } from "./array.js";
|
|
2
2
|
import type { Data } from "./data.js";
|
|
3
3
|
import type { ImmutableDictionary, PossibleDictionary } from "./dictionary.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { DeepPartial } from "./object.js";
|
|
5
5
|
/** Object that can validate an unknown value with its `validate()` method. */
|
|
6
6
|
export interface Validator<T> {
|
|
7
7
|
/**
|
|
@@ -58,19 +58,13 @@ export declare function validateDictionary<T>(unsafeDictionary: PossibleDictiona
|
|
|
58
58
|
* - Defined props in the object will be validated against the corresponding validator.
|
|
59
59
|
* - `undefined` props in the object will be set to the default value of that prop.
|
|
60
60
|
*
|
|
61
|
+
* @param partial Whether we're validating a partial match, or not.
|
|
61
62
|
* @return Valid object.
|
|
62
63
|
* @throw Feedback if one or more props did not validate.
|
|
63
64
|
* - `feedback.details` will contain an entry for each invalid item (keyed by their count in the input iterable).
|
|
64
65
|
*/
|
|
65
|
-
export declare function validateData<T extends Data>(unsafeData: Data, validators: Validators<T
|
|
66
|
-
|
|
67
|
-
export declare
|
|
68
|
-
|
|
69
|
-
export declare
|
|
70
|
-
readonly id: true;
|
|
71
|
-
}): Item<T>;
|
|
72
|
-
export declare function validateWithContext<T extends Data>(unsafeValue: unknown, validator: Validator<T>, context: Data & {
|
|
73
|
-
readonly partial: true;
|
|
74
|
-
}): Partial<T>;
|
|
75
|
-
export declare function validateWithContext<T>(unsafeValue: unknown, validator: Validator<T>, context: Data): T;
|
|
76
|
-
export declare const UNDEFINED_VALIDATOR: Validator<undefined>;
|
|
66
|
+
export declare function validateData<T extends Data>(unsafeData: Data, validators: Validators<T>, partial: true): DeepPartial<T>;
|
|
67
|
+
export declare function validateData<T extends Data>(unsafeData: Data, validators: Validators<T>, partial?: false): T;
|
|
68
|
+
export declare const UNDEFINED: Validator<undefined>;
|
|
69
|
+
export declare const NULL: Validator<null>;
|
|
70
|
+
export declare const UNKNOWN: Validator<unknown>;
|
package/util/validate.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ValidationError } from "../error/ValidationError.js";
|
|
2
2
|
import { Feedback } from "../feedback/Feedback.js";
|
|
3
3
|
import { ValueFeedbacks } from "../feedback/Feedbacks.js";
|
|
4
|
-
import {
|
|
4
|
+
import { isArray } from "./array.js";
|
|
5
5
|
import { getDataProps } from "./data.js";
|
|
6
6
|
import { getDictionaryItems } from "./dictionary.js";
|
|
7
|
+
import { PASSTHROUGH } from "./function.js";
|
|
7
8
|
import { isIterable } from "./iterate.js";
|
|
9
|
+
import { getNull } from "./null.js";
|
|
8
10
|
import { getUndefined } from "./undefined.js";
|
|
9
11
|
/** Get value that validates against a given `Validator`, or throw `ValidationError` */
|
|
10
12
|
export function getValid(unsafeValue, validator) {
|
|
@@ -108,53 +110,43 @@ export function validateDictionary(unsafeDictionary, validator) {
|
|
|
108
110
|
throw new ValueFeedbacks(messages, unsafeDictionary);
|
|
109
111
|
return changed || isIterable(unsafeDictionary) ? safeDictionary : unsafeDictionary;
|
|
110
112
|
}
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* - `undefined` props in the object will be set to the default value of that prop.
|
|
115
|
-
*
|
|
116
|
-
* @return Valid object.
|
|
117
|
-
* @throw Feedback if one or more props did not validate.
|
|
118
|
-
* - `feedback.details` will contain an entry for each invalid item (keyed by their count in the input iterable).
|
|
119
|
-
*/
|
|
120
|
-
export function validateData(unsafeData, validators) {
|
|
121
|
-
const { partial = false, id = false } = getValidationContext();
|
|
113
|
+
/** Keep track of whether we're doing a deep-partial match or not. */
|
|
114
|
+
let isDeeplyPartial = false;
|
|
115
|
+
export function validateData(unsafeData, validators, partial = isDeeplyPartial) {
|
|
122
116
|
let valid = true;
|
|
123
117
|
let changed = true;
|
|
124
|
-
const safeData =
|
|
118
|
+
const safeData = {};
|
|
125
119
|
const messages = {};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
120
|
+
try {
|
|
121
|
+
isDeeplyPartial = partial;
|
|
122
|
+
for (const [key, validator] of getDataProps(validators)) {
|
|
123
|
+
const unsafeValue = unsafeData[key];
|
|
124
|
+
if (unsafeValue === undefined && partial)
|
|
125
|
+
continue; // Silently skip `undefined` props if in partial mode.
|
|
126
|
+
try {
|
|
127
|
+
const safeValue = validator.validate(unsafeValue);
|
|
128
|
+
safeData[key] = safeValue;
|
|
129
|
+
if (!changed && safeValue !== unsafeValue)
|
|
130
|
+
changed = true;
|
|
131
|
+
}
|
|
132
|
+
catch (thrown) {
|
|
133
|
+
if (!(thrown instanceof Feedback))
|
|
134
|
+
throw thrown;
|
|
135
|
+
messages[key] = thrown.message;
|
|
136
|
+
valid = false;
|
|
137
|
+
}
|
|
141
138
|
}
|
|
139
|
+
if (!valid)
|
|
140
|
+
throw new ValueFeedbacks(messages, unsafeData);
|
|
141
|
+
return changed ? safeData : unsafeData;
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
isDeeplyPartial = false;
|
|
142
145
|
}
|
|
143
|
-
if (!valid)
|
|
144
|
-
throw new ValueFeedbacks(messages, unsafeData);
|
|
145
|
-
return changed ? safeData : unsafeData;
|
|
146
|
-
}
|
|
147
|
-
/** Store a list of current contexts. */
|
|
148
|
-
const CONTEXTS = [{}];
|
|
149
|
-
/** Get the current validation context. */
|
|
150
|
-
export function getValidationContext() {
|
|
151
|
-
return getLastItem(CONTEXTS);
|
|
152
|
-
}
|
|
153
|
-
export function validateWithContext(unsafeValue, validator, context) {
|
|
154
|
-
CONTEXTS.push(context);
|
|
155
|
-
const validValue = validator.validate(unsafeValue);
|
|
156
|
-
CONTEXTS.pop();
|
|
157
|
-
return validValue;
|
|
158
146
|
}
|
|
159
|
-
// Undefined validator always returns undefined
|
|
160
|
-
export const
|
|
147
|
+
// Undefined validator always returns `undefined`
|
|
148
|
+
export const UNDEFINED = { validate: getUndefined };
|
|
149
|
+
// Null validator always returns `null`
|
|
150
|
+
export const NULL = { validate: getNull };
|
|
151
|
+
// Unknown validator always passes through its input value as `unknown`
|
|
152
|
+
export const UNKNOWN = { validate: PASSTHROUGH };
|