shelving 1.132.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ValidationError } from "../error/ValidationError.js";
2
2
  import { Feedback } from "../feedback/Feedback.js";
3
- import { UNDEFINED_VALIDATOR } from "../util/validate.js";
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 = UNDEFINED_VALIDATOR, result = UNDEFINED_VALIDATOR) {
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, DatabaseSchemas } from "../schema/DataSchema.js";
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: DatabaseSchemas<T>;
11
- constructor(schemas: DatabaseSchemas<T>, source: Provider<T>);
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: DatabaseSchemas<T>;
30
- constructor(schemas: DatabaseSchemas<T>, source: AsyncProvider<T>);
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]>>;
@@ -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 { validateWithContext } from "../util/validate.js";
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, validateWithContext(data, this.getSchema(collection), VALIDATION_CONTEXT_ADD));
27
+ return super.addItem(collection, validateData(data, this.getSchema(collection).props));
32
28
  }
33
29
  setItem(collection, id, data) {
34
- super.setItem(collection, id, validateWithContext(data, this.getSchema(collection), VALIDATION_CONTEXT_SET));
30
+ super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
35
31
  }
36
32
  updateItem(collection, id, updates) {
37
- validateWithContext(updateData({}, updates), this.getSchema(collection), VALIDATION_CONTEXT_UPDATE);
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
- validateWithContext(updateData({}, updates), this.getSchema(collection), VALIDATION_CONTEXT_UPDATE);
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, validateWithContext(data, this.getSchema(collection), VALIDATION_CONTEXT_ADD));
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, validateWithContext(data, this.getSchema(collection), VALIDATION_CONTEXT_SET));
84
+ return super.setItem(collection, id, validateData(data, this.getSchema(collection).props));
88
85
  }
89
86
  updateItem(collection, id, updates) {
90
- validateWithContext(updateData({}, updates), this.getSchema(collection), VALIDATION_CONTEXT_UPDATE);
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, validateWithContext(data, this.getSchema(collection), VALIDATION_CONTEXT_SET));
105
+ return super.setQuery(collection, query, validateData(data, this.getSchema(collection).props));
109
106
  }
110
107
  updateQuery(collection, query, updates) {
111
- validateWithContext(updateData({}, updates), this.getSchema(collection), VALIDATION_CONTEXT_UPDATE);
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, unsafeEntity, schema) {
119
- if (!unsafeEntity)
115
+ function _validateItem(collection, unsafeItem, schema) {
116
+ if (!unsafeItem)
120
117
  return undefined;
121
118
  try {
122
- return validateWithContext(unsafeEntity, schema, VALIDATION_CONTEXT_GET);
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 ValidationError(`Invalid data for "${collection}"`, thrown.message);
124
+ throw new ConflictError(`Invalid data for "${collection}"`, thrown.message);
128
125
  }
129
126
  }
130
- /** Validate a set of entities for this query reference. */
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, schema) {
134
+ function* _yieldValidItems(collection, unsafeEntities, validators) {
135
135
  let invalid = false;
136
136
  const messages = {};
137
- for (const unsafeEntity of unsafeEntities) {
137
+ for (const unsafeItem of unsafeEntities) {
138
138
  try {
139
- yield validateWithContext(unsafeEntity, schema, VALIDATION_CONTEXT_GET);
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[unsafeEntity.id] = thrown.message;
145
+ messages[unsafeItem.id] = thrown.message;
146
146
  }
147
147
  }
148
148
  if (invalid)
149
- throw new ValidationError(`Invalid data for "${collection}"`, messages);
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 = 403;
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 = 403;
4
+ code = 401;
5
5
  constructor(message = "Unauthorized", context) {
6
6
  super(message, context);
7
7
  }
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.132.0",
14
+ "version": "1.133.0",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -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 DatabaseSchemas<T extends Database> = {
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";
@@ -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 { Item } from "./item.js";
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>): T;
66
- /** Get the current validation context. */
67
- export declare function getValidationContext(): Data;
68
- /** Validate a unknown value with a validator, and supply a context that can be read during the validation process. */
69
- export declare function validateWithContext<T extends Data>(unsafeValue: Item<Data>, validator: Validator<T>, context: Data & {
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 { getLastItem, isArray } from "./array.js";
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
- * Validate a data object with a set of validators.
113
- * - Defined props in the object will be validated against the corresponding validator.
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 = id && typeof unsafeData.id === "string" ? { id: unsafeData.id } : {};
118
+ const safeData = {};
125
119
  const messages = {};
126
- for (const [key, validator] of getDataProps(validators)) {
127
- const unsafeValue = unsafeData[key];
128
- if (unsafeValue === undefined && partial)
129
- continue; // Silently skip `undefined` props if in partial mode.
130
- try {
131
- const safeValue = validator.validate(unsafeValue);
132
- safeData[key] = safeValue;
133
- if (!changed && safeValue !== unsafeValue)
134
- changed = true;
135
- }
136
- catch (thrown) {
137
- if (!(thrown instanceof Feedback))
138
- throw thrown;
139
- messages[key] = thrown.message;
140
- valid = false;
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 UNDEFINED_VALIDATOR = { validate: getUndefined };
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 };