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 +6 -6
- package/schema/DateSchema.js +2 -2
- package/schema/DateTimeSchema.d.ts +6 -1
- package/schema/DateTimeSchema.js +6 -1
- package/schema/TimeSchema.js +2 -2
- package/util/date.d.ts +12 -8
- package/util/date.js +35 -18
- package/util/http.d.ts +4 -20
- package/util/http.js +16 -28
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"state-management",
|
|
12
12
|
"query-builder"
|
|
13
13
|
],
|
|
14
|
-
"version": "1.
|
|
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.
|
|
62
|
-
"@types/bun": "^1.2.
|
|
63
|
-
"@types/react": "^19.1.
|
|
64
|
-
"@types/react-dom": "^19.1.
|
|
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.
|
|
68
|
+
"typescript": "^5.9.3"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@google-cloud/firestore": ">=7.0.0",
|
package/schema/DateSchema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
|
-
import { getDate,
|
|
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
|
|
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
|
-
/**
|
|
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;
|
package/schema/DateTimeSchema.js
CHANGED
|
@@ -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
|
-
/**
|
|
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);
|
package/schema/TimeSchema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ValueFeedback } from "../feedback/Feedback.js";
|
|
2
|
-
import { getDate,
|
|
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
|
|
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
|
|
48
|
-
export declare function
|
|
49
|
-
/** Convert a possible `Date` instance to a YMD string like "2015-09-
|
|
50
|
-
export declare function
|
|
51
|
-
/** Convert an unknown value to a
|
|
52
|
-
export declare function
|
|
53
|
-
/** Convert a possible `Date` instance to
|
|
54
|
-
export declare function
|
|
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(`${
|
|
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
|
-
|
|
96
|
-
|
|
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
|
|
112
|
+
return _datetime(date);
|
|
100
113
|
}
|
|
101
|
-
/** Convert a possible `Date` instance to a YMD string like "2015-09-
|
|
102
|
-
export function
|
|
103
|
-
return
|
|
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
|
-
|
|
106
|
-
|
|
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
|
|
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
|
|
115
|
-
export function
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
43
|
-
|
|
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(
|
|
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(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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.
|