shelving 1.153.0 → 1.154.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.153.0",
14
+ "version": "1.154.1",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -58,14 +58,14 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@biomejs/biome": "^1.9.4",
61
- "@google-cloud/firestore": "^7.11.3",
62
- "@types/bun": "^1.2.20",
63
- "@types/react": "^19.1.11",
64
- "@types/react-dom": "^19.1.7",
61
+ "@google-cloud/firestore": "^7.11.6",
62
+ "@types/bun": "^1.2.23",
63
+ "@types/react": "^19.1.16",
64
+ "@types/react-dom": "^19.1.9",
65
65
  "firebase": "^11.10.0",
66
66
  "react": "^19.1.1",
67
67
  "react-dom": "^19.1.1",
68
- "typescript": "^5.9.2"
68
+ "typescript": "^5.9.3"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@google-cloud/firestore": ">=7.0.0",
@@ -1,5 +1,5 @@
1
1
  import { ValueFeedback } from "../feedback/Feedback.js";
2
- import { getDate, requireYMD } from "../util/date.js";
2
+ import { getDate, requireDateString } from "../util/date.js";
3
3
  import { formatDate } from "../util/format.js";
4
4
  import { NULLABLE } from "./NullableSchema.js";
5
5
  import { Schema } from "./Schema.js";
@@ -24,7 +24,7 @@ export class DateSchema extends Schema {
24
24
  return this.stringify(date);
25
25
  }
26
26
  stringify(value) {
27
- return requireYMD(value);
27
+ return requireDateString(value);
28
28
  }
29
29
  format(value) {
30
30
  return formatDate(value);
@@ -2,7 +2,12 @@ import { DateSchema, type DateSchemaOptions } from "./DateSchema.js";
2
2
  /** Allowed options for `DateSchema` */
3
3
  export interface DateTimeSchemaOptions extends DateSchemaOptions {
4
4
  }
5
- /** Define a valid date in ISO 8601 format, e.g. `2005-09-12T18:15:00.000Z` */
5
+ /**
6
+ * Define a valid UTC date in ISO 8601 format, e.g. `2005-09-12T18:15:00.000Z`
7
+ * - The date includes the `Z` suffix to indicate UTC time, this ensures consistent transfer of the date between client and server.
8
+ * - If you wish to define an _abstract_ date without a timezone, e.g. a birthday or anniversary, use `DateSchema` instead.
9
+ * - If you wish to define an _abstract_ time without a timezone, e.g. a daily alarm, use `TimeSchema` instead.
10
+ */
6
11
  export declare class DateTimeSchema extends DateSchema {
7
12
  format(value: Date): string;
8
13
  stringify(value: Date): string;
@@ -1,7 +1,12 @@
1
1
  import { formatDateTime } from "../util/format.js";
2
2
  import { DateSchema } from "./DateSchema.js";
3
3
  import { NULLABLE } from "./NullableSchema.js";
4
- /** Define a valid date in ISO 8601 format, e.g. `2005-09-12T18:15:00.000Z` */
4
+ /**
5
+ * Define a valid UTC date in ISO 8601 format, e.g. `2005-09-12T18:15:00.000Z`
6
+ * - The date includes the `Z` suffix to indicate UTC time, this ensures consistent transfer of the date between client and server.
7
+ * - If you wish to define an _abstract_ date without a timezone, e.g. a birthday or anniversary, use `DateSchema` instead.
8
+ * - If you wish to define an _abstract_ time without a timezone, e.g. a daily alarm, use `TimeSchema` instead.
9
+ */
5
10
  export class DateTimeSchema extends DateSchema {
6
11
  format(value) {
7
12
  return formatDateTime(value);
@@ -1,5 +1,5 @@
1
1
  import { ValueFeedback } from "../feedback/Feedback.js";
2
- import { getDate, requireTime } from "../util/date.js";
2
+ import { getDate, requireTimeString } from "../util/date.js";
3
3
  import { formatTime } from "../util/format.js";
4
4
  import { roundStep } from "../util/number.js";
5
5
  import { DateSchema } from "./DateSchema.js";
@@ -24,7 +24,7 @@ export class TimeSchema extends DateSchema {
24
24
  throw new ValueFeedback(`Maximum ${formatTime(this.max)}`, rounded);
25
25
  if (this.min && rounded < this.min)
26
26
  throw new ValueFeedback(`Minimum ${formatTime(this.min)}`, rounded);
27
- return requireTime(rounded);
27
+ return requireTimeString(rounded);
28
28
  }
29
29
  }
30
30
  /** Valid time, e.g. `2005-09-12` (required because falsy values are invalid). */
package/util/date.d.ts CHANGED
@@ -44,14 +44,18 @@ export declare function requireDate(value?: PossibleDate, caller?: AnyCaller): D
44
44
  export declare function getTimestamp(value?: unknown): number | undefined;
45
45
  /** Convert a possible date to a timestamp (milliseconds past Unix epoch), or throw `RequiredError` if it couldn't be converted. */
46
46
  export declare function requireTimestamp(value?: PossibleDate): number;
47
- /** Convert an unknown value to a YMD date string like "2015-09-12", or `undefined` if it couldn't be converted. */
48
- export declare function getYMD(value?: unknown): string | undefined;
49
- /** Convert a possible `Date` instance to a YMD string like "2015-09-12", or throw `RequiredError` if it couldn't be converted. */
50
- export declare function requireYMD(value?: PossibleDate, caller?: AnyCaller): string;
51
- /** Convert an unknown value to a HMS time string like "18:32:00", or `undefined` if it couldn't be converted. */
52
- export declare function getTime(value?: unknown): string | undefined;
53
- /** Convert a possible `Date` instance to an HMS string like "18:32:00", or throw `RequiredError` if it couldn't be converted. */
54
- export declare function requireTime(value?: PossibleDate, caller?: AnyCaller): string;
47
+ /** Convert an unknown value to a local date string like "2015-09-12T18:30:00", or `undefined` if it couldn't be converted. */
48
+ export declare function getDateTimeString(value?: unknown): string | undefined;
49
+ /** Convert a possible `Date` instance to a local YMD string like "2015-09-12T18:30:00", or throw `RequiredError` if it couldn't be converted. */
50
+ export declare function requireDateTimeString(value?: PossibleDate, caller?: AnyCaller): string;
51
+ /** Convert an unknown value to a local date string like "2015-09-12", or `undefined` if it couldn't be converted. */
52
+ export declare function getDateString(value?: unknown): string | undefined;
53
+ /** Convert a possible `Date` instance to a local date string like "2015-09-12", or throw `RequiredError` if it couldn't be converted. */
54
+ export declare function requireDateString(value?: PossibleDate, caller?: AnyCaller): string;
55
+ /** Convert an unknown value to a local time string like "18:32:00", or `undefined` if it couldn't be converted. */
56
+ export declare function getTimeString(value?: unknown): string | undefined;
57
+ /** Convert a possible `Date` instance to local time string like "18:32:00", or throw `RequiredError` if it couldn't be converted. */
58
+ export declare function requireTimeString(value?: PossibleDate, caller?: AnyCaller): string;
55
59
  /** List of day-of-week strings. */
56
60
  export declare const DAYS: readonly ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
57
61
  /** Type listing day-of-week strings. */
package/util/date.js CHANGED
@@ -46,7 +46,7 @@ export function getDate(value) {
46
46
  const date = new Date(value);
47
47
  if (Number.isFinite(date.getTime()))
48
48
  return date;
49
- const time = new Date(`${requireYMD()}T${value}`);
49
+ const time = new Date(`${requireDateString()}T${value}`);
50
50
  if (Number.isFinite(time.getTime()))
51
51
  return time;
52
52
  }
@@ -92,31 +92,48 @@ export function getTimestamp(value) {
92
92
  export function requireTimestamp(value) {
93
93
  return requireDate(value, requireTimestamp).getTime();
94
94
  }
95
- /** Convert an unknown value to a YMD date string like "2015-09-12", or `undefined` if it couldn't be converted. */
96
- export function getYMD(value) {
95
+ // Helpers.
96
+ function _pad(num, length = 2) {
97
+ return num.toString().padStart(length, "0");
98
+ }
99
+ function _date(date) {
100
+ return `${_pad(date.getFullYear(), 4)}-${_pad(date.getMonth() + 1)}-${_pad(date.getDate())}`;
101
+ }
102
+ function _time(date) {
103
+ return `${_pad(date.getHours())}:${_pad(date.getMinutes())}:${_pad(date.getSeconds())}`;
104
+ }
105
+ function _datetime(date) {
106
+ return `${_date(date)}T${_time(date)}`;
107
+ }
108
+ /** Convert an unknown value to a local date string like "2015-09-12T18:30:00", or `undefined` if it couldn't be converted. */
109
+ export function getDateTimeString(value) {
97
110
  const date = getDate(value);
98
111
  if (date)
99
- return _ymd(date);
112
+ return _datetime(date);
100
113
  }
101
- /** Convert a possible `Date` instance to a YMD string like "2015-09-12", or throw `RequiredError` if it couldn't be converted. */
102
- export function requireYMD(value, caller = requireYMD) {
103
- return _ymd(requireDate(value, caller));
114
+ /** Convert a possible `Date` instance to a local YMD string like "2015-09-12T18:30:00", or throw `RequiredError` if it couldn't be converted. */
115
+ export function requireDateTimeString(value, caller = requireDateTimeString) {
116
+ return _datetime(requireDate(value, caller));
104
117
  }
105
- function _ymd(date) {
106
- return date.toISOString().slice(0, 10);
107
- }
108
- /** Convert an unknown value to a HMS time string like "18:32:00", or `undefined` if it couldn't be converted. */
109
- export function getTime(value) {
118
+ /** Convert an unknown value to a local date string like "2015-09-12", or `undefined` if it couldn't be converted. */
119
+ export function getDateString(value) {
110
120
  const date = getDate(value);
111
121
  if (date)
112
- return _hms(date);
122
+ return _date(date);
123
+ }
124
+ /** Convert a possible `Date` instance to a local date string like "2015-09-12", or throw `RequiredError` if it couldn't be converted. */
125
+ export function requireDateString(value, caller = requireDateString) {
126
+ return _date(requireDate(value, caller));
113
127
  }
114
- /** Convert a possible `Date` instance to an HMS string like "18:32:00", or throw `RequiredError` if it couldn't be converted. */
115
- export function requireTime(value, caller = requireTime) {
116
- return _hms(requireDate(value, caller));
128
+ /** Convert an unknown value to a local time string like "18:32:00", or `undefined` if it couldn't be converted. */
129
+ export function getTimeString(value) {
130
+ const date = getDate(value);
131
+ if (date)
132
+ return _time(date);
117
133
  }
118
- function _hms(date) {
119
- return date.toISOString().slice(11, 19);
134
+ /** Convert a possible `Date` instance to local time string like "18:32:00", or throw `RequiredError` if it couldn't be converted. */
135
+ export function requireTimeString(value, caller = requireTimeString) {
136
+ return _time(requireDate(value, caller));
120
137
  }
121
138
  /** List of day-of-week strings. */
122
139
  export const DAYS = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
package/util/http.d.ts CHANGED
@@ -9,41 +9,25 @@ export declare function _getMessageContent(message: Request | Response, MessageE
9
9
  /**
10
10
  * Get the body content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
11
11
  *
12
+ * @returns undefined If the request method is `GET` or `HEAD` (these request methods have no body).
12
13
  * @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
13
14
  * @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
14
15
  * @returns string If content type is `text/plain` or anything else (including `""` empty string if it's empty).
15
16
  *
16
17
  * @throws RequestError if the content is not `text/plain`, or `application/json` with valid JSON.
17
18
  */
18
- export declare function getRequestContent(message: Request, caller?: AnyCaller): Promise<unknown>;
19
+ export declare function getRequestContent(request: Request, caller?: AnyCaller): Promise<unknown>;
19
20
  /**
20
21
  * Get the body content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
21
22
  *
23
+ * @returns undefined If the request status is `204 No Content` (this response has no body).
22
24
  * @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
23
25
  * @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
24
26
  * @returns string If content type is `text/plain` or anything else (including `""` empty string if it's empty).
25
27
  *
26
28
  * @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
27
29
  */
28
- export declare function getResponseContent(message: Response, caller?: AnyCaller): Promise<unknown>;
29
- /**
30
- * Get the body content of an HTTP `Request` as JSON, or throw `RequestError` if the content could not be parsed.
31
- * - Doesn't check the `Content-Type` header, so it can be used for any request.
32
- *
33
- * @returns unknown The parsed JSON content of the request body, or `undefined` if the body is empty.
34
- *
35
- * @throws RequestError if the content is not valid JSON.
36
- */
37
- export declare function getRequestJSON(message: Request, caller?: AnyCaller): Promise<unknown>;
38
- /**
39
- * Get the body content of an HTTP `Response` as JSON, or throw `ResponseError` if the content could not be parsed.
40
- * - Doesn't check the `Content-Type` header, so it can be used for any response.
41
- *
42
- * @returns unknown The parsed JSON content of the response body, or `undefined` if the body is empty.
43
- *
44
- * @throws RequestError if the content is not valid JSON.
45
- */
46
- export declare function getResponseJSON(message: Response, caller?: AnyCaller): Promise<unknown>;
30
+ export declare function getResponseContent(response: Response, caller?: AnyCaller): Promise<unknown>;
47
31
  /**
48
32
  * Get an HTTP `Response` for an unknown value.
49
33
  *
package/util/http.js CHANGED
@@ -24,59 +24,47 @@ export async function _getMessageFormData(message, MessageError, caller) {
24
24
  }
25
25
  export function _getMessageContent(message, MessageError, caller) {
26
26
  const type = message.headers.get("Content-Type");
27
+ if (!type || type?.startsWith("text/"))
28
+ return message.text();
27
29
  if (type?.startsWith("application/json"))
28
30
  return _getMessageJSON(message, MessageError, caller);
29
31
  if (type?.startsWith("multipart/form-data"))
30
32
  return _getMessageFormData(message, MessageError, caller);
31
- return message.text();
33
+ throw new MessageError("Content-Type must be text/*, application/json, or multipart/form-data", { received: type, caller });
32
34
  }
33
35
  /**
34
36
  * Get the body content of an HTTP `Request` based on its content type, or throw `RequestError` if the content could not be parsed.
35
37
  *
38
+ * @returns undefined If the request method is `GET` or `HEAD` (these request methods have no body).
36
39
  * @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
37
40
  * @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
38
41
  * @returns string If content type is `text/plain` or anything else (including `""` empty string if it's empty).
39
42
  *
40
43
  * @throws RequestError if the content is not `text/plain`, or `application/json` with valid JSON.
41
44
  */
42
- export function getRequestContent(message, caller = getRequestContent) {
43
- if (message.method === "GET" || message.method === "HEAD")
45
+ export function getRequestContent(request, caller = getRequestContent) {
46
+ const { method } = request;
47
+ // The HTTP/1.1 RFC 7231 does not forbid sending a body in GET or HEAD requests, but it is uncommon and not recommended because many servers, proxies, and caches may ignore or mishandle it.
48
+ if (method === "GET" || method === "HEAD")
44
49
  return Promise.resolve(undefined);
45
- return _getMessageContent(message, RequestError, caller);
50
+ return _getMessageContent(request, RequestError, caller);
46
51
  }
47
52
  /**
48
53
  * Get the body content of an HTTP `Response` based on its content type, or throw `ResponseError` if the content could not be parsed.
49
54
  *
55
+ * @returns undefined If the request status is `204 No Content` (this response has no body).
50
56
  * @returns unknown If content type is `application/json` and has valid JSON (including `undefined` if the content is empty).
51
57
  * @returns unknown If content type is `multipart/form-data` then convert it to a simple `Data` object.
52
58
  * @returns string If content type is `text/plain` or anything else (including `""` empty string if it's empty).
53
59
  *
54
60
  * @throws RequestError if the content is not `text/plain` or `application/json` with valid JSON.
55
61
  */
56
- export function getResponseContent(message, caller = getResponseContent) {
57
- return _getMessageContent(message, ResponseError, caller);
58
- }
59
- /**
60
- * Get the body content of an HTTP `Request` as JSON, or throw `RequestError` if the content could not be parsed.
61
- * - Doesn't check the `Content-Type` header, so it can be used for any request.
62
- *
63
- * @returns unknown The parsed JSON content of the request body, or `undefined` if the body is empty.
64
- *
65
- * @throws RequestError if the content is not valid JSON.
66
- */
67
- export function getRequestJSON(message, caller = getRequestJSON) {
68
- return _getMessageJSON(message, RequestError, caller);
69
- }
70
- /**
71
- * Get the body content of an HTTP `Response` as JSON, or throw `ResponseError` if the content could not be parsed.
72
- * - Doesn't check the `Content-Type` header, so it can be used for any response.
73
- *
74
- * @returns unknown The parsed JSON content of the response body, or `undefined` if the body is empty.
75
- *
76
- * @throws RequestError if the content is not valid JSON.
77
- */
78
- export function getResponseJSON(message, caller = getResponseJSON) {
79
- return _getMessageJSON(message, ResponseError, caller);
62
+ export function getResponseContent(response, caller = getResponseContent) {
63
+ const { status } = response;
64
+ // RFC 7230 Section 3.3.3: A server MUST NOT send a Content-Length header field in any response with a status code of 1xx (Informational), 204 (No Content), or 304 (Not Modified).
65
+ if ((status >= 100 && status < 200) || status === 204 || status === 304)
66
+ return Promise.resolve(undefined);
67
+ return _getMessageContent(response, ResponseError, caller);
80
68
  }
81
69
  /**
82
70
  * Get an HTTP `Response` for an unknown value.