shelving 1.132.0 → 1.134.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/CodedError.d.ts +11 -0
- package/error/CodedError.js +18 -0
- package/error/ConflictError.d.ts +2 -2
- package/error/ConflictError.js +3 -2
- package/error/ConnectionError.d.ts +2 -2
- package/error/ConnectionError.js +3 -2
- package/error/ForbiddenError.d.ts +2 -2
- package/error/ForbiddenError.js +3 -2
- package/error/NotFoundError.d.ts +2 -2
- package/error/NotFoundError.js +3 -2
- package/error/NotImplementedError.d.ts +2 -2
- package/error/NotImplementedError.js +3 -2
- package/error/UnauthorizedError.d.ts +3 -3
- package/error/UnauthorizedError.js +4 -3
- package/error/ValidationError.d.ts +2 -2
- package/error/ValidationError.js +3 -2
- package/error/index.d.ts +1 -1
- package/error/index.js +1 -1
- package/package.json +1 -1
- package/schema/DataSchema.d.ts +1 -1
- package/schema/ItemSchema.d.ts +33 -0
- package/schema/ItemSchema.js +21 -0
- package/schema/index.d.ts +1 -0
- package/schema/index.js +1 -0
- package/util/validate.d.ts +7 -13
- package/util/validate.js +37 -45
- package/error/EnhancedError.d.ts +0 -10
- package/error/EnhancedError.js +0 -16
- package/error/ThroughError.d.ts +0 -7
- package/error/ThroughError.js +0 -13
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
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error with a `.code` field and a `.details` field to provide context about what went wrong.
|
|
3
|
+
* - Code should be a `number` in the range of 400-599
|
|
4
|
+
* - Details are appended to `.message` in the format `message: debuggedContext`
|
|
5
|
+
* - Details are converted to string using `debug()`
|
|
6
|
+
*/
|
|
7
|
+
export declare class CodedError extends Error {
|
|
8
|
+
readonly code: number;
|
|
9
|
+
readonly details: unknown;
|
|
10
|
+
constructor(message: string, details?: unknown);
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { debug } from "../util/debug.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error with a `.code` field and a `.details` field to provide context about what went wrong.
|
|
4
|
+
* - Code should be a `number` in the range of 400-599
|
|
5
|
+
* - Details are appended to `.message` in the format `message: debuggedContext`
|
|
6
|
+
* - Details are converted to string using `debug()`
|
|
7
|
+
*/
|
|
8
|
+
export class CodedError extends Error {
|
|
9
|
+
code = 500;
|
|
10
|
+
details;
|
|
11
|
+
constructor(message, details) {
|
|
12
|
+
const debugged = details !== undefined ? debug(details, 2) : "";
|
|
13
|
+
super(debugged.length ? `${message}: ${debugged}` : message);
|
|
14
|
+
this.details = details;
|
|
15
|
+
Error.captureStackTrace(this, CodedError);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
CodedError.prototype.name = "CodedError";
|
package/error/ConflictError.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if the state of the program is not correct to execute a given operation. */
|
|
3
|
-
export declare class ConflictError extends
|
|
3
|
+
export declare class ConflictError extends CodedError {
|
|
4
4
|
readonly code = 509;
|
|
5
5
|
constructor(message?: string, context?: unknown);
|
|
6
6
|
}
|
package/error/ConflictError.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if the state of the program is not correct to execute a given operation. */
|
|
3
|
-
export class ConflictError extends
|
|
3
|
+
export class ConflictError extends CodedError {
|
|
4
4
|
code = 509;
|
|
5
5
|
constructor(message = "Conflict", context) {
|
|
6
6
|
super(message, context);
|
|
7
|
+
Error.captureStackTrace(this, ConflictError);
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
ConflictError.prototype.name = "ConflictError";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if e.g. a user's internet connection fails. */
|
|
3
|
-
export declare class ConnectionError extends
|
|
3
|
+
export declare class ConnectionError extends CodedError {
|
|
4
4
|
constructor(message?: string, context?: unknown);
|
|
5
5
|
}
|
package/error/ConnectionError.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if e.g. a user's internet connection fails. */
|
|
3
|
-
export class ConnectionError extends
|
|
3
|
+
export class ConnectionError extends CodedError {
|
|
4
4
|
constructor(message = "Connection error", context) {
|
|
5
5
|
super(message, context);
|
|
6
|
+
Error.captureStackTrace(this, ConnectionError);
|
|
6
7
|
}
|
|
7
8
|
}
|
|
8
9
|
ConnectionError.prototype.name = "ConnectionError";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if an operation failed because the user is logged in but does not have sufficient privileges to access something. */
|
|
3
|
-
export declare class ForbiddenError extends
|
|
3
|
+
export declare class ForbiddenError extends CodedError {
|
|
4
4
|
readonly code = 403;
|
|
5
5
|
constructor(message?: string, context?: unknown);
|
|
6
6
|
}
|
package/error/ForbiddenError.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if an operation failed because the user is logged in but does not have sufficient privileges to access something. */
|
|
3
|
-
export class ForbiddenError extends
|
|
3
|
+
export class ForbiddenError extends CodedError {
|
|
4
4
|
code = 403;
|
|
5
5
|
constructor(message = "Forbidden", context) {
|
|
6
6
|
super(message, context);
|
|
7
|
+
Error.captureStackTrace(this, ForbiddenError);
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
ForbiddenError.prototype.name = "ForbiddenError";
|
package/error/NotFoundError.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if if a value is required but doesn't exist. */
|
|
3
|
-
export declare class NotFoundError extends
|
|
3
|
+
export declare class NotFoundError extends CodedError {
|
|
4
4
|
readonly code = 404;
|
|
5
5
|
constructor(message?: string, context?: unknown);
|
|
6
6
|
}
|
package/error/NotFoundError.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if if a value is required but doesn't exist. */
|
|
3
|
-
export class NotFoundError extends
|
|
3
|
+
export class NotFoundError extends CodedError {
|
|
4
4
|
code = 404;
|
|
5
5
|
constructor(message = "Not found", context) {
|
|
6
6
|
super(message, context);
|
|
7
|
+
Error.captureStackTrace(this, NotFoundError);
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
NotFoundError.prototype.name = "NotFoundError";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Error thrown when functionality is called but is not implemented by an interface. */
|
|
3
|
-
export declare class NotImplementedError extends
|
|
3
|
+
export declare class NotImplementedError extends CodedError {
|
|
4
4
|
readonly code = 501;
|
|
5
5
|
constructor(message?: string, value?: unknown);
|
|
6
6
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Error thrown when functionality is called but is not implemented by an interface. */
|
|
3
|
-
export class NotImplementedError extends
|
|
3
|
+
export class NotImplementedError extends CodedError {
|
|
4
4
|
code = 501;
|
|
5
5
|
constructor(message = "Not implemented", value) {
|
|
6
6
|
super(message, value);
|
|
7
|
+
Error.captureStackTrace(this, NotImplementedError);
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
NotImplementedError.prototype.name = "NotImplementedError";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if an operation failed because the user is logged in but does not have sufficient privileges to access something. */
|
|
3
|
-
export declare class UnauthorizedError extends
|
|
4
|
-
readonly code =
|
|
3
|
+
export declare class UnauthorizedError extends CodedError {
|
|
4
|
+
readonly code = 401;
|
|
5
5
|
constructor(message?: string, context?: unknown);
|
|
6
6
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Thrown if an operation failed because the user is logged in but does not have sufficient privileges to access something. */
|
|
3
|
-
export class UnauthorizedError extends
|
|
4
|
-
code =
|
|
3
|
+
export class UnauthorizedError extends CodedError {
|
|
4
|
+
code = 401;
|
|
5
5
|
constructor(message = "Unauthorized", context) {
|
|
6
6
|
super(message, context);
|
|
7
|
+
Error.captureStackTrace(this, UnauthorizedError);
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
UnauthorizedError.prototype.name = "UnauthorizedError";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Error thrown when a value isn't valid. */
|
|
3
|
-
export declare class ValidationError extends
|
|
3
|
+
export declare class ValidationError extends CodedError {
|
|
4
4
|
readonly code = 422;
|
|
5
5
|
constructor(message?: string, context?: unknown);
|
|
6
6
|
}
|
package/error/ValidationError.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CodedError } from "./CodedError.js";
|
|
2
2
|
/** Error thrown when a value isn't valid. */
|
|
3
|
-
export class ValidationError extends
|
|
3
|
+
export class ValidationError extends CodedError {
|
|
4
4
|
code = 422;
|
|
5
5
|
constructor(message = "Invalid value", context) {
|
|
6
6
|
super(message, context);
|
|
7
|
+
Error.captureStackTrace(this, ValidationError);
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
ValidationError.prototype.name = "ValidationError";
|
package/error/index.d.ts
CHANGED
package/error/index.js
CHANGED
package/package.json
CHANGED
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. */
|
|
@@ -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/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ 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";
|
package/schema/index.js
CHANGED
|
@@ -9,6 +9,7 @@ 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";
|
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 };
|
package/error/EnhancedError.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error with a `.context` field to provide information about what went wrong.
|
|
3
|
-
* - Context is appended to `.message` in the format `message: debuggedContext`
|
|
4
|
-
* - Context is converted to a string using `debug()`
|
|
5
|
-
*/
|
|
6
|
-
export declare class EnhancedError extends Error {
|
|
7
|
-
readonly code: number;
|
|
8
|
-
readonly context: unknown;
|
|
9
|
-
constructor(message: string, context?: unknown);
|
|
10
|
-
}
|
package/error/EnhancedError.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { debug } from "../util/debug.js";
|
|
2
|
-
/**
|
|
3
|
-
* Error with a `.context` field to provide information about what went wrong.
|
|
4
|
-
* - Context is appended to `.message` in the format `message: debuggedContext`
|
|
5
|
-
* - Context is converted to a string using `debug()`
|
|
6
|
-
*/
|
|
7
|
-
export class EnhancedError extends Error {
|
|
8
|
-
code = 500;
|
|
9
|
-
context;
|
|
10
|
-
constructor(message, context) {
|
|
11
|
-
const debugged = context !== undefined ? debug(context, 2) : "";
|
|
12
|
-
super(debugged.length ? `${message}: ${debugged}` : message);
|
|
13
|
-
this.context = context;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
EnhancedError.prototype.name = "EnhancedError";
|
package/error/ThroughError.d.ts
DELETED
package/error/ThroughError.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { debug } from "../util/debug.js";
|
|
2
|
-
/**
|
|
3
|
-
* Thrown to wrap an error with another error.
|
|
4
|
-
* - Merges the message and stack of the previous message.
|
|
5
|
-
*/
|
|
6
|
-
export class ThroughError extends Error {
|
|
7
|
-
constructor(message, cause) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.cause = cause;
|
|
10
|
-
this.stack = `${this.stack || ""}\nCause: ${cause instanceof Error ? cause.stack || "" : debug(cause)}`;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
ThroughError.prototype.name = "ThroughError";
|