shelving 1.123.0 → 1.124.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/db/MemoryProvider.d.ts +1 -1
- package/db/MemoryProvider.js +12 -12
- package/db/QueryStore.d.ts +3 -15
- package/db/QueryStore.js +2 -27
- package/feedback/Feedbacks.js +2 -3
- package/markup/options.d.ts +7 -3
- package/markup/options.js +2 -1
- package/markup/rule.js +2 -2
- package/markup/rules.d.ts +2 -2
- package/markup/rules.js +6 -6
- package/package.json +1 -1
- package/react/createDataContext.d.ts +0 -6
- package/react/createDataContext.js +10 -14
- package/schema/DateSchema.js +1 -1
- package/schema/LinkSchema.d.ts +4 -1
- package/schema/LinkSchema.js +12 -10
- package/schema/TimeSchema.js +1 -1
- package/store/ArrayStore.d.ts +12 -1
- package/store/ArrayStore.js +21 -1
- package/util/date.d.ts +13 -10
- package/util/date.js +12 -10
- package/util/path.d.ts +14 -11
- package/util/path.js +30 -11
- package/util/time.d.ts +10 -12
- package/util/time.js +11 -18
- package/util/units.js +1 -1
- package/util/url.d.ts +36 -7
- package/util/url.js +44 -15
package/db/MemoryProvider.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare class MemoryTable<T extends Data> {
|
|
|
40
40
|
/** Times data was last updated. */
|
|
41
41
|
protected readonly _times: Map<string, number>;
|
|
42
42
|
/** Deferred sequence of next values. */
|
|
43
|
-
readonly
|
|
43
|
+
readonly next: DeferredSequence<void>;
|
|
44
44
|
getItemTime(id: string): number | undefined;
|
|
45
45
|
getItem(id: string): OptionalItem<T>;
|
|
46
46
|
getItemSequence(id: string): AsyncIterable<OptionalItem<T>>;
|
package/db/MemoryProvider.js
CHANGED
|
@@ -84,7 +84,7 @@ export class MemoryTable {
|
|
|
84
84
|
/** Times data was last updated. */
|
|
85
85
|
_times = new Map();
|
|
86
86
|
/** Deferred sequence of next values. */
|
|
87
|
-
|
|
87
|
+
next = new DeferredSequence();
|
|
88
88
|
getItemTime(id) {
|
|
89
89
|
return this._times.get(id);
|
|
90
90
|
}
|
|
@@ -95,7 +95,7 @@ export class MemoryTable {
|
|
|
95
95
|
let lastValue = this.getItem(id);
|
|
96
96
|
yield lastValue;
|
|
97
97
|
while (true) {
|
|
98
|
-
await this.
|
|
98
|
+
await this.next;
|
|
99
99
|
const nextValue = this.getItem(id);
|
|
100
100
|
if (nextValue !== lastValue) {
|
|
101
101
|
yield nextValue;
|
|
@@ -108,7 +108,7 @@ export class MemoryTable {
|
|
|
108
108
|
if (typeof lastTime === "number")
|
|
109
109
|
yield this.getItem(id);
|
|
110
110
|
while (true) {
|
|
111
|
-
await this.
|
|
111
|
+
await this.next;
|
|
112
112
|
const nextTime = this._times.get(id);
|
|
113
113
|
if (nextTime !== lastTime) {
|
|
114
114
|
if (typeof nextTime === "number")
|
|
@@ -129,7 +129,7 @@ export class MemoryTable {
|
|
|
129
129
|
if (this._data.get(id) !== item) {
|
|
130
130
|
this._data.set(id, item);
|
|
131
131
|
this._times.set(id, Date.now());
|
|
132
|
-
this.
|
|
132
|
+
this.next.resolve();
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
async *setItemSequence(id, sequence) {
|
|
@@ -147,7 +147,7 @@ export class MemoryTable {
|
|
|
147
147
|
if (this._data.has(id)) {
|
|
148
148
|
this._data.delete(id);
|
|
149
149
|
this._times.set(id, Date.now());
|
|
150
|
-
this.
|
|
150
|
+
this.next.resolve();
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
getQueryTime(query) {
|
|
@@ -163,10 +163,10 @@ export class MemoryTable {
|
|
|
163
163
|
let lastItems = this.getQuery(query);
|
|
164
164
|
yield lastItems;
|
|
165
165
|
while (true) {
|
|
166
|
-
await this.
|
|
166
|
+
await this.next;
|
|
167
167
|
const nextItems = this.getQuery(query);
|
|
168
168
|
if (!isArrayEqual(lastItems, nextItems)) {
|
|
169
|
-
yield
|
|
169
|
+
yield nextItems;
|
|
170
170
|
lastItems = nextItems;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
@@ -177,7 +177,7 @@ export class MemoryTable {
|
|
|
177
177
|
if (typeof lastTime === "number")
|
|
178
178
|
yield this.getQuery(query);
|
|
179
179
|
while (true) {
|
|
180
|
-
await this.
|
|
180
|
+
await this.next;
|
|
181
181
|
const nextTime = this._times.get(key);
|
|
182
182
|
if (lastTime !== nextTime) {
|
|
183
183
|
if (typeof nextTime === "number")
|
|
@@ -195,7 +195,7 @@ export class MemoryTable {
|
|
|
195
195
|
if (changed) {
|
|
196
196
|
const key = _getQueryKey(query);
|
|
197
197
|
this._times.set(key, Date.now());
|
|
198
|
-
this.
|
|
198
|
+
this.next.resolve();
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
updateQuery(query, updates) {
|
|
@@ -207,7 +207,7 @@ export class MemoryTable {
|
|
|
207
207
|
if (count) {
|
|
208
208
|
const key = _getQueryKey(query);
|
|
209
209
|
this._times.set(key, Date.now());
|
|
210
|
-
this.
|
|
210
|
+
this.next.resolve();
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
deleteQuery(query) {
|
|
@@ -219,7 +219,7 @@ export class MemoryTable {
|
|
|
219
219
|
if (count) {
|
|
220
220
|
const key = _getQueryKey(query);
|
|
221
221
|
this._times.set(key, Date.now());
|
|
222
|
-
this.
|
|
222
|
+
this.next.resolve();
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
setItems(items, query) {
|
|
@@ -228,7 +228,7 @@ export class MemoryTable {
|
|
|
228
228
|
if (query) {
|
|
229
229
|
const key = _getQueryKey(query);
|
|
230
230
|
this._times.set(key, Date.now());
|
|
231
|
-
this.
|
|
231
|
+
this.next.resolve();
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
async *setItemsSequence(sequence, query) {
|
package/db/QueryStore.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { MemoryProvider } from "./MemoryProvider.js";
|
|
2
2
|
import type { AbstractProvider } from "./Provider.js";
|
|
3
3
|
import type { DataKey, Database } from "../util/data.js";
|
|
4
|
-
import type { Item, ItemQuery
|
|
4
|
+
import type { Item, ItemQuery } from "../util/item.js";
|
|
5
5
|
import type { Stop } from "../util/start.js";
|
|
6
|
+
import { ArrayStore } from "../store/ArrayStore.js";
|
|
6
7
|
import { BooleanStore } from "../store/BooleanStore.js";
|
|
7
|
-
import { Store } from "../store/Store.js";
|
|
8
8
|
/** Store a set of multiple items. */
|
|
9
|
-
export declare class QueryStore<T extends Database, K extends DataKey<T>> extends
|
|
9
|
+
export declare class QueryStore<T extends Database, K extends DataKey<T>> extends ArrayStore<Item<T[K]>> {
|
|
10
10
|
readonly provider: AbstractProvider<T>;
|
|
11
11
|
readonly collection: K;
|
|
12
12
|
readonly query: ItemQuery<T[K]>;
|
|
@@ -15,18 +15,6 @@ export declare class QueryStore<T extends Database, K extends DataKey<T>> extend
|
|
|
15
15
|
/** Can more items be loaded after the current result. */
|
|
16
16
|
get hasMore(): boolean;
|
|
17
17
|
private _hasMore;
|
|
18
|
-
/** Get the first item in this store or `null` if this query has no items. */
|
|
19
|
-
get optionalFirst(): OptionalItem<T[K]>;
|
|
20
|
-
/** Get the last item in this store or `null` if this query has no items. */
|
|
21
|
-
get optionalLast(): OptionalItem<T[K]>;
|
|
22
|
-
/** Get the first item in this store. */
|
|
23
|
-
get first(): Item<T[K]>;
|
|
24
|
-
/** Get the last item in this store. */
|
|
25
|
-
get last(): Item<T[K]>;
|
|
26
|
-
/** Does the document have at least one result. */
|
|
27
|
-
get exists(): boolean;
|
|
28
|
-
/** Get the number of items matching this query. */
|
|
29
|
-
get count(): number;
|
|
30
18
|
constructor(collection: K, query: ItemQuery<T[K]>, provider: AbstractProvider<T>, memory?: MemoryProvider<T>);
|
|
31
19
|
/** Refresh this store from the source provider. */
|
|
32
20
|
refresh(provider?: AbstractProvider<T>): void;
|
package/db/QueryStore.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import { ArrayStore } from "../store/ArrayStore.js";
|
|
1
2
|
import { BooleanStore } from "../store/BooleanStore.js";
|
|
2
|
-
import { Store } from "../store/Store.js";
|
|
3
|
-
import { getFirstItem, getLastItem, getOptionalFirstItem, getOptionalLastItem } from "../util/array.js";
|
|
4
3
|
import { NONE } from "../util/constants.js";
|
|
5
4
|
import { getAfterQuery, getLimit } from "../util/query.js";
|
|
6
5
|
import { runSequence } from "../util/sequence.js";
|
|
7
6
|
/** Store a set of multiple items. */
|
|
8
|
-
export class QueryStore extends
|
|
7
|
+
export class QueryStore extends ArrayStore {
|
|
9
8
|
provider;
|
|
10
9
|
collection;
|
|
11
10
|
query;
|
|
@@ -16,30 +15,6 @@ export class QueryStore extends Store {
|
|
|
16
15
|
return this._hasMore;
|
|
17
16
|
}
|
|
18
17
|
_hasMore = false;
|
|
19
|
-
/** Get the first item in this store or `null` if this query has no items. */
|
|
20
|
-
get optionalFirst() {
|
|
21
|
-
return getOptionalFirstItem(this.value);
|
|
22
|
-
}
|
|
23
|
-
/** Get the last item in this store or `null` if this query has no items. */
|
|
24
|
-
get optionalLast() {
|
|
25
|
-
return getOptionalLastItem(this.value);
|
|
26
|
-
}
|
|
27
|
-
/** Get the first item in this store. */
|
|
28
|
-
get first() {
|
|
29
|
-
return getFirstItem(this.value);
|
|
30
|
-
}
|
|
31
|
-
/** Get the last item in this store. */
|
|
32
|
-
get last() {
|
|
33
|
-
return getLastItem(this.value);
|
|
34
|
-
}
|
|
35
|
-
/** Does the document have at least one result. */
|
|
36
|
-
get exists() {
|
|
37
|
-
return !!this.value.length;
|
|
38
|
-
}
|
|
39
|
-
/** Get the number of items matching this query. */
|
|
40
|
-
get count() {
|
|
41
|
-
return this.value.length;
|
|
42
|
-
}
|
|
43
18
|
constructor(collection, query, provider, memory) {
|
|
44
19
|
const time = memory?.getQueryTime(collection, query);
|
|
45
20
|
const items = memory?.getQuery(collection, query) || [];
|
package/feedback/Feedbacks.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { indent } from "../util/debug.js";
|
|
2
1
|
import { getProp } from "../util/object.js";
|
|
3
2
|
import { mapDictionary } from "../util/transform.js";
|
|
4
3
|
import { Feedback } from "./Feedback.js";
|
|
@@ -11,8 +10,8 @@ export class Feedbacks extends Feedback {
|
|
|
11
10
|
return mapDictionary(this.feedbacks, getProp, "message");
|
|
12
11
|
}
|
|
13
12
|
constructor(feedbacks, value) {
|
|
14
|
-
|
|
13
|
+
const first = Object.values(feedbacks)[0];
|
|
14
|
+
super(first?.message || "Unknown error", value);
|
|
15
15
|
this.feedbacks = feedbacks;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
const _mapMessages = ([name, { message }]) => `${name}:${indent(message)}`;
|
package/markup/options.d.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import type { MarkupRules } from "./rule.js";
|
|
2
|
+
import type { ImmutableArray } from "../util/array.js";
|
|
3
|
+
import type { AbsoluteURI } from "../util/url.js";
|
|
2
4
|
/** The current parsing options (represents the current state of the parsing). */
|
|
3
5
|
export type MarkupOptions = {
|
|
4
6
|
/** The active list of parsing rules. */
|
|
5
7
|
readonly rules: MarkupRules;
|
|
6
8
|
/** The initial context to start parsing in (rules may render their children with a different context). */
|
|
7
9
|
readonly context: string;
|
|
8
|
-
/** Set the base URL that any relative links will be relative to (defaults to `window.location.href`, if undefined then relative links won't work). */
|
|
9
|
-
readonly url: string | undefined;
|
|
10
10
|
/** Set the `rel=""` property used for any links (e.g. `rel="nofollow ugc"`). */
|
|
11
11
|
readonly rel: string | undefined;
|
|
12
|
+
/** Set the base URL that any relative links will be relative to (defaults to `window.location.href`, if undefined then relative links won't work). */
|
|
13
|
+
readonly base: AbsoluteURI | undefined;
|
|
12
14
|
/** Valid URL schemes/protocols for links (including trailing commas), defaults to `[`http:`, `https:`]` */
|
|
13
|
-
readonly schemes: string
|
|
15
|
+
readonly schemes: ImmutableArray<string>;
|
|
16
|
+
/** Valid URL hosts for links (including trailing commas) */
|
|
17
|
+
readonly hosts: ImmutableArray<string> | undefined;
|
|
14
18
|
};
|
|
15
19
|
/** Default options */
|
|
16
20
|
export declare const MARKUP_OPTIONS: MarkupOptions;
|
package/markup/options.js
CHANGED
package/markup/rule.js
CHANGED
|
@@ -57,10 +57,10 @@ export class LinkRegExpMarkupRule {
|
|
|
57
57
|
match(input, options) {
|
|
58
58
|
const match = this.regexp.exec(input);
|
|
59
59
|
if (match) {
|
|
60
|
-
const { schemes,
|
|
60
|
+
const { schemes, base, hosts } = options;
|
|
61
61
|
const { 0: { length }, index, groups: { href, title }, } = match;
|
|
62
62
|
const url = getOptionalURL(href, base);
|
|
63
|
-
if (url && schemes.includes(url.protocol))
|
|
63
|
+
if (url && schemes.includes(url.protocol) && (!hosts || !hosts.includes(url.host)))
|
|
64
64
|
return { index, length, ...this.render(title?.trim() || formatURL(url), url.href, options) };
|
|
65
65
|
}
|
|
66
66
|
}
|
package/markup/rules.d.ts
CHANGED
|
@@ -61,13 +61,13 @@ export declare function renderLinkRule(title: string, href: string, { rel }: Mar
|
|
|
61
61
|
context: string;
|
|
62
62
|
};
|
|
63
63
|
/**
|
|
64
|
-
* Autolinked URL starts with `http:` or `https:`
|
|
64
|
+
* Autolinked URL starts with `http:` or `https:` (any scheme in `options.schemes`) and matches an unlimited number of non-space characters.
|
|
65
65
|
* - If followed by space and then text in `()` round or `[]` square brackets that will be used as the title, e.g. `http://google.com/maps (Google Maps)` or `http://google.com/maps [Google Maps]` (this syntax is from Todoist and maybe other things too).
|
|
66
66
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
67
67
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
68
68
|
* - For security only schemes that appear in `options.schemes` will match (defaults to `http:` and `https:`).
|
|
69
69
|
*/
|
|
70
|
-
export declare const
|
|
70
|
+
export declare const AUTOLINK_RULE: LinkRegExpMarkupRule;
|
|
71
71
|
/**
|
|
72
72
|
* Markdown-style link.
|
|
73
73
|
* - Link in standard Markdown format, e.g. `[Google Maps](http://google.com/maps)`
|
package/markup/rules.js
CHANGED
|
@@ -149,13 +149,13 @@ export function renderLinkRule(title, href, { rel }) {
|
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
|
-
* Autolinked URL starts with `http:` or `https:`
|
|
152
|
+
* Autolinked URL starts with `http:` or `https:` (any scheme in `options.schemes`) and matches an unlimited number of non-space characters.
|
|
153
153
|
* - If followed by space and then text in `()` round or `[]` square brackets that will be used as the title, e.g. `http://google.com/maps (Google Maps)` or `http://google.com/maps [Google Maps]` (this syntax is from Todoist and maybe other things too).
|
|
154
154
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
155
155
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
156
156
|
* - For security only schemes that appear in `options.schemes` will match (defaults to `http:` and `https:`).
|
|
157
157
|
*/
|
|
158
|
-
export const
|
|
158
|
+
export const AUTOLINK_RULE = new LinkRegExpMarkupRule(getRegExp(/(?<href>[a-z]+:\S+)(?: +(?:\((?<title>[^)]*?)\)))?/), //
|
|
159
159
|
renderLinkRule, ["inline", "list"]);
|
|
160
160
|
/**
|
|
161
161
|
* Markdown-style link.
|
|
@@ -165,7 +165,7 @@ renderLinkRule, ["inline", "list"]);
|
|
|
165
165
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
166
166
|
* - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
|
|
167
167
|
*/
|
|
168
|
-
export const LINK_RULE = new LinkRegExpMarkupRule(getRegExp(/\[(?<title>[^\]]*?)\]
|
|
168
|
+
export const LINK_RULE = new LinkRegExpMarkupRule(getRegExp(/\[(?<title>[^\]]*?)\] *\((?<href>[^)]*?)\)/), //
|
|
169
169
|
renderLinkRule, ["inline", "list"]);
|
|
170
170
|
/**
|
|
171
171
|
* Inline code.
|
|
@@ -237,7 +237,7 @@ export const MARKUP_RULES = [
|
|
|
237
237
|
FENCED_CODE_RULE,
|
|
238
238
|
PARAGRAPH_RULE,
|
|
239
239
|
LINK_RULE,
|
|
240
|
-
|
|
240
|
+
AUTOLINK_RULE,
|
|
241
241
|
CODE_RULE,
|
|
242
242
|
INLINE_RULE,
|
|
243
243
|
LINEBREAK_RULE,
|
|
@@ -257,7 +257,7 @@ export const MARKUP_RULES_BLOCK = [
|
|
|
257
257
|
/** Subset of markup rules that work in an inline context. */
|
|
258
258
|
export const MARKUP_RULES_INLINE = [
|
|
259
259
|
LINK_RULE,
|
|
260
|
-
|
|
260
|
+
AUTOLINK_RULE,
|
|
261
261
|
CODE_RULE,
|
|
262
262
|
INLINE_RULE,
|
|
263
263
|
LINEBREAK_RULE,
|
|
@@ -269,7 +269,7 @@ export const MARKUP_RULES_SHORTFORM = [
|
|
|
269
269
|
ORDERED_RULE,
|
|
270
270
|
PARAGRAPH_RULE,
|
|
271
271
|
LINK_RULE,
|
|
272
|
-
|
|
272
|
+
AUTOLINK_RULE,
|
|
273
273
|
CODE_RULE,
|
|
274
274
|
INLINE_RULE,
|
|
275
275
|
LINEBREAK_RULE,
|
package/package.json
CHANGED
|
@@ -6,12 +6,6 @@ import type { Optional } from "../util/optional.js";
|
|
|
6
6
|
import { ItemStore } from "../db/ItemStore.js";
|
|
7
7
|
import { QueryStore } from "../db/QueryStore.js";
|
|
8
8
|
export interface DataContext<T extends Database> {
|
|
9
|
-
/** Get an `ItemStore` for the specified collection item in the current `DataProvider` context. */
|
|
10
|
-
useCacheItem<K extends DataKey<T>>(this: void, collection: K, id: string): ItemStore<T, K>;
|
|
11
|
-
useCacheItem<K extends DataKey<T>>(this: void, collection: Optional<K>, id: Optional<string>): ItemStore<T, K> | undefined;
|
|
12
|
-
/** Get an `QueryStore` for the specified collection query in the current `DataProvider` context. */
|
|
13
|
-
useCacheQuery<K extends DataKey<T>>(this: void, collection: K, query: ItemQuery<T[K]>): QueryStore<T, K>;
|
|
14
|
-
useCacheQuery<K extends DataKey<T>>(this: void, collection: Optional<K>, query: Optional<ItemQuery<T[K]>>): QueryStore<T, K> | undefined;
|
|
15
9
|
/** Get an `ItemStore` for the specified collection item in the current `DataProvider` context and subscribe to any changes in it. */
|
|
16
10
|
useItem<K extends DataKey<T>>(this: void, collection: K, id: string): ItemStore<T, K>;
|
|
17
11
|
useItem<K extends DataKey<T>>(this: void, collection: Optional<K>, id: Optional<string>): ItemStore<T, K> | undefined;
|
|
@@ -12,21 +12,17 @@ export function createDataContext(provider) {
|
|
|
12
12
|
const { CacheContext, useCache } = createCacheContext(); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
13
13
|
// If this provider is backed by an in-memory cache, pass it to the `ItemStore` and `QueryStore` instances we create.
|
|
14
14
|
const memory = getOptionalSource(CacheProvider, provider)?.memory;
|
|
15
|
-
const useCacheItem = (collection, id) => {
|
|
16
|
-
const cache = useCache();
|
|
17
|
-
const key = collection && id && `${collection}/${id}`;
|
|
18
|
-
return key ? cache.get(key) || setMapItem(cache, key, new ItemStore(collection, id, provider, memory)) : undefined;
|
|
19
|
-
};
|
|
20
|
-
const useCacheQuery = (collection, query) => {
|
|
21
|
-
const cache = useCache();
|
|
22
|
-
const key = collection && query && `${collection}?${JSON.stringify(query)}`;
|
|
23
|
-
return key ? cache.get(key) || setMapItem(cache, key, new QueryStore(collection, query, provider, memory)) : undefined;
|
|
24
|
-
};
|
|
25
15
|
return {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
useItem: (collection, id) => {
|
|
17
|
+
const cache = useCache();
|
|
18
|
+
const key = collection && id && `${collection}/${id}`;
|
|
19
|
+
return useStore(key ? cache.get(key) || setMapItem(cache, key, new ItemStore(collection, id, provider, memory)) : undefined);
|
|
20
|
+
},
|
|
21
|
+
useQuery: (collection, query) => {
|
|
22
|
+
const cache = useCache();
|
|
23
|
+
const key = collection && query && `${collection}?${JSON.stringify(query)}`;
|
|
24
|
+
return useStore(key ? cache.get(key) || setMapItem(cache, key, new QueryStore(collection, query, provider, memory)) : undefined);
|
|
25
|
+
},
|
|
30
26
|
DataContext: CacheContext,
|
|
31
27
|
};
|
|
32
28
|
}
|
package/schema/DateSchema.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Schema } from "./Schema.js";
|
|
|
6
6
|
export class DateSchema extends Schema {
|
|
7
7
|
min;
|
|
8
8
|
max;
|
|
9
|
-
constructor({ min
|
|
9
|
+
constructor({ min, max, title = "Date", value = "now", ...options }) {
|
|
10
10
|
super({ title, value, ...options });
|
|
11
11
|
this.min = getOptionalDate(min);
|
|
12
12
|
this.max = getOptionalDate(max);
|
package/schema/LinkSchema.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { StringSchemaOptions } from "./StringSchema.js";
|
|
2
2
|
import type { ImmutableArray } from "../util/array.js";
|
|
3
|
+
import { type AbsoluteURI, type PossibleURL } from "../util/url.js";
|
|
3
4
|
import { StringSchema } from "./StringSchema.js";
|
|
4
5
|
/** Allowed options for `LinkSchema` */
|
|
5
6
|
export interface LinkSchemaOptions extends Omit<StringSchemaOptions, "type" | "min" | "max" | "multiline"> {
|
|
7
|
+
readonly base?: PossibleURL | undefined;
|
|
6
8
|
readonly schemes?: ImmutableArray<string> | undefined;
|
|
7
9
|
readonly hosts?: ImmutableArray<string> | undefined;
|
|
8
10
|
}
|
|
@@ -13,9 +15,10 @@ export interface LinkSchemaOptions extends Omit<StringSchemaOptions, "type" | "m
|
|
|
13
15
|
* - Falsy values are converted to `""` empty string.
|
|
14
16
|
*/
|
|
15
17
|
export declare class LinkSchema extends StringSchema {
|
|
18
|
+
readonly base: AbsoluteURI | undefined;
|
|
16
19
|
readonly schemes: ImmutableArray<string>;
|
|
17
20
|
readonly hosts: ImmutableArray<string> | undefined;
|
|
18
|
-
constructor({ schemes, hosts, title, ...options }: LinkSchemaOptions);
|
|
21
|
+
constructor({ base, schemes, hosts, title, ...options }: LinkSchemaOptions);
|
|
19
22
|
validate(unsafeValue: unknown): string;
|
|
20
23
|
}
|
|
21
24
|
/** Valid link, e.g. `https://www.google.com` */
|
package/schema/LinkSchema.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Feedback } from "../feedback/Feedback.js";
|
|
2
|
-
import { getOptionalURL } from "../util/url.js";
|
|
2
|
+
import { getOptionalAbsoluteURI, getOptionalURL } from "../util/url.js";
|
|
3
3
|
import { OPTIONAL } from "./OptionalSchema.js";
|
|
4
4
|
import { StringSchema } from "./StringSchema.js";
|
|
5
5
|
/**
|
|
@@ -9,9 +9,10 @@ import { StringSchema } from "./StringSchema.js";
|
|
|
9
9
|
* - Falsy values are converted to `""` empty string.
|
|
10
10
|
*/
|
|
11
11
|
export class LinkSchema extends StringSchema {
|
|
12
|
+
base;
|
|
12
13
|
schemes;
|
|
13
14
|
hosts;
|
|
14
|
-
constructor({ schemes = ["http:", "https:"], hosts, title = "Link", ...options }) {
|
|
15
|
+
constructor({ base, schemes = ["http:", "https:"], hosts, title = "Link", ...options }) {
|
|
15
16
|
super({
|
|
16
17
|
title,
|
|
17
18
|
...options,
|
|
@@ -20,20 +21,21 @@ export class LinkSchema extends StringSchema {
|
|
|
20
21
|
max: 512,
|
|
21
22
|
multiline: false,
|
|
22
23
|
});
|
|
24
|
+
this.base = getOptionalAbsoluteURI(base);
|
|
23
25
|
this.schemes = schemes;
|
|
24
26
|
this.hosts = hosts;
|
|
25
27
|
}
|
|
26
|
-
// Override to clean the URL using
|
|
28
|
+
// Override to clean the URL using builtin helper functions and check the schemes and hosts against the whitelists.
|
|
27
29
|
validate(unsafeValue) {
|
|
28
30
|
const unsafeString = super.validate(unsafeValue);
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
31
|
+
const url = getOptionalURL(super.sanitize(unsafeString), this.base);
|
|
32
|
+
if (!url)
|
|
31
33
|
throw new Feedback(unsafeString ? "Invalid format" : "Required", unsafeString);
|
|
32
|
-
if (!this.schemes.includes(
|
|
33
|
-
throw new Feedback(`Scheme "${
|
|
34
|
-
if (this.hosts && !this.hosts.includes(
|
|
35
|
-
throw new Feedback(`Domain "${
|
|
36
|
-
return
|
|
34
|
+
if (!this.schemes.includes(url.protocol))
|
|
35
|
+
throw new Feedback(`Scheme "${url.protocol}" is not allowed`, unsafeString);
|
|
36
|
+
if (this.hosts && !this.hosts.includes(url.host))
|
|
37
|
+
throw new Feedback(`Domain "${url.host}" is not allowed`, unsafeString);
|
|
38
|
+
return url.href;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
/** Valid link, e.g. `https://www.google.com` */
|
package/schema/TimeSchema.js
CHANGED
|
@@ -12,7 +12,7 @@ export class TimeSchema extends Schema {
|
|
|
12
12
|
* - Note: `<input type="time">` elements expect `step=""` to be in _seconds_ so you need to multiply this by `1000`
|
|
13
13
|
*/
|
|
14
14
|
step;
|
|
15
|
-
constructor({ min
|
|
15
|
+
constructor({ min, max, step = 60, title = "Time", value = "now", ...options }) {
|
|
16
16
|
super({ title, value, ...options });
|
|
17
17
|
this.min = getOptionalTime(min);
|
|
18
18
|
this.max = getOptionalTime(max);
|
package/store/ArrayStore.d.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import type { ImmutableArray } from "../util/array.js";
|
|
2
|
+
import type { NONE } from "../util/constants.js";
|
|
2
3
|
import { Store } from "./Store.js";
|
|
3
4
|
/** Store an array. */
|
|
4
5
|
export declare class ArrayStore<T> extends Store<ImmutableArray<T>> implements Iterable<T> {
|
|
5
|
-
constructor(value?: ImmutableArray<T
|
|
6
|
+
constructor(value?: ImmutableArray<T> | typeof NONE, time?: number);
|
|
7
|
+
/** Get the first item in this store or `null` if this query has no items. */
|
|
8
|
+
get optionalFirst(): T | undefined;
|
|
9
|
+
/** Get the last item in this store or `null` if this query has no items. */
|
|
10
|
+
get optionalLast(): T | undefined;
|
|
11
|
+
/** Get the first item in this store. */
|
|
12
|
+
get first(): T;
|
|
13
|
+
/** Get the last item in this store. */
|
|
14
|
+
get last(): T;
|
|
15
|
+
/** Does the document have at least one result. */
|
|
16
|
+
get exists(): boolean;
|
|
6
17
|
/** Get the length of the current value of this store. */
|
|
7
18
|
get count(): number;
|
|
8
19
|
/** Add items to this array. */
|
package/store/ArrayStore.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
|
-
import { omitArrayItems, toggleArrayItems, withArrayItems } from "../util/array.js";
|
|
1
|
+
import { getFirstItem, getLastItem, getOptionalFirstItem, getOptionalLastItem, omitArrayItems, toggleArrayItems, withArrayItems } from "../util/array.js";
|
|
2
2
|
import { Store } from "./Store.js";
|
|
3
3
|
/** Store an array. */
|
|
4
4
|
export class ArrayStore extends Store {
|
|
5
5
|
constructor(value = [], time) {
|
|
6
6
|
super(value, time);
|
|
7
7
|
}
|
|
8
|
+
/** Get the first item in this store or `null` if this query has no items. */
|
|
9
|
+
get optionalFirst() {
|
|
10
|
+
return getOptionalFirstItem(this.value);
|
|
11
|
+
}
|
|
12
|
+
/** Get the last item in this store or `null` if this query has no items. */
|
|
13
|
+
get optionalLast() {
|
|
14
|
+
return getOptionalLastItem(this.value);
|
|
15
|
+
}
|
|
16
|
+
/** Get the first item in this store. */
|
|
17
|
+
get first() {
|
|
18
|
+
return getFirstItem(this.value);
|
|
19
|
+
}
|
|
20
|
+
/** Get the last item in this store. */
|
|
21
|
+
get last() {
|
|
22
|
+
return getLastItem(this.value);
|
|
23
|
+
}
|
|
24
|
+
/** Does the document have at least one result. */
|
|
25
|
+
get exists() {
|
|
26
|
+
return !!this.value.length;
|
|
27
|
+
}
|
|
8
28
|
/** Get the length of the current value of this store. */
|
|
9
29
|
get count() {
|
|
10
30
|
return this.value.length;
|
package/util/date.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Optional } from "./optional.js";
|
|
1
2
|
/** Things that converted to dates. */
|
|
2
3
|
export type PossibleDate = Date | number | string;
|
|
3
4
|
/** Is a value a date? */
|
|
@@ -5,26 +6,28 @@ export declare function isDate(value: unknown): value is Date;
|
|
|
5
6
|
/** Assert that a value is a `Date` instance. */
|
|
6
7
|
export declare function assertDate(value: unknown): asserts value is Date;
|
|
7
8
|
/**
|
|
8
|
-
* Convert an unknown value to a valid `Date` instance, or `
|
|
9
|
+
* Convert an unknown value to a valid `Date` instance, or return `undefined` if it couldn't be converted.
|
|
9
10
|
* - Note: `Date` instances can be invalid (e.g. `new Date("blah blah").getTime()` returns `NaN`). These are detected and will always return `null`
|
|
10
11
|
*
|
|
11
12
|
* Conversion rules:
|
|
12
|
-
* - `Date` instance returns unchanged (BUT if the date isn't valid, `
|
|
13
|
-
* - `null` or `
|
|
14
|
-
* - `undefined` returns the current date (e.g. `new Date()`).
|
|
13
|
+
* - `Date` instance returns unchanged (BUT if the date isn't valid, `undefined` is returned).
|
|
14
|
+
* - `null` or `undefined` or `""` empty string returns `undefined`
|
|
15
15
|
* - The string `"now"` returns the current date (e.g. `new Date()`).
|
|
16
16
|
* - The string `"today"` returns the current date at midnight (e.g. `getMidnight()`).
|
|
17
17
|
* - The string `"tomorrow"` returns tomorrow's date at midnight (e.g. `addDays(getMidnight(), 1)`).
|
|
18
18
|
* - The string `"yesterday"` returns yesterday's date at midnight (e.g. `addDays(getMidnight(), 1)`).
|
|
19
19
|
* - Strings (e.g. `"2003-09-12"` or `"2003 feb 20:09"`) return the corresponding date (using `new Date(string)`).
|
|
20
20
|
* - Numbers are return the corresponding date (using `new Date(number)`, i.e. milliseconds since 01/01/1970).
|
|
21
|
-
* - Anything else
|
|
21
|
+
* - Anything else returns `undefined`
|
|
22
22
|
*
|
|
23
|
-
* @param possible Any value that we want to parse as a valid date.
|
|
23
|
+
* @param possible Any value that we want to parse as a valid date (defaults to `undefined`).
|
|
24
24
|
* @returns `Date` instance if the value could be converted to a valid date, and `null` if not.
|
|
25
25
|
*/
|
|
26
|
-
export declare function getOptionalDate(possible
|
|
27
|
-
/**
|
|
26
|
+
export declare function getOptionalDate(possible: unknown): Date | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Convert a possible date to a `Date` instance, or throw `ValueError` if it couldn't be converted.
|
|
29
|
+
* @param possible Any value that we want to parse as a valid date (defaults to `"now"`).
|
|
30
|
+
*/
|
|
28
31
|
export declare function getDate(possible?: PossibleDate): Date;
|
|
29
32
|
/** Convert an unknown value to a timestamp (milliseconds past Unix epoch), or `undefined` if it couldn't be converted. */
|
|
30
33
|
export declare function getOptionalTimestamp(possible: unknown): number | undefined;
|
|
@@ -33,7 +36,7 @@ export declare function getTimestamp(possible?: PossibleDate): number;
|
|
|
33
36
|
/** Convert an unknown value to a YMD date string like "2015-09-12", or `undefined` if it couldn't be converted. */
|
|
34
37
|
export declare function getOptionalYMD(possible: unknown): string | undefined;
|
|
35
38
|
/** Convert a `Date` instance to a YMD string like "2015-09-12", or throw `ValueError` if it couldn't be converted. */
|
|
36
|
-
export declare function getYMD(possible
|
|
39
|
+
export declare function getYMD(possible: PossibleDate): string;
|
|
37
40
|
/** List of day-of-week strings. */
|
|
38
41
|
export declare const DAYS: readonly ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
39
42
|
/** Type listing day-of-week strings. */
|
|
@@ -76,4 +79,4 @@ export declare function isToday(target: PossibleDate, current?: PossibleDate): b
|
|
|
76
79
|
/** Format a date in the browser locale. */
|
|
77
80
|
export declare function formatDate(date: PossibleDate): string;
|
|
78
81
|
/** Format an optional time as a string. */
|
|
79
|
-
export declare function formatOptionalDate(date
|
|
82
|
+
export declare function formatOptionalDate(date: Optional<PossibleDate>): string | undefined;
|
package/util/date.js
CHANGED
|
@@ -9,26 +9,25 @@ export function assertDate(value) {
|
|
|
9
9
|
throw new ValueError(`Must be date`, value);
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Convert an unknown value to a valid `Date` instance, or `
|
|
12
|
+
* Convert an unknown value to a valid `Date` instance, or return `undefined` if it couldn't be converted.
|
|
13
13
|
* - Note: `Date` instances can be invalid (e.g. `new Date("blah blah").getTime()` returns `NaN`). These are detected and will always return `null`
|
|
14
14
|
*
|
|
15
15
|
* Conversion rules:
|
|
16
|
-
* - `Date` instance returns unchanged (BUT if the date isn't valid, `
|
|
17
|
-
* - `null` or `
|
|
18
|
-
* - `undefined` returns the current date (e.g. `new Date()`).
|
|
16
|
+
* - `Date` instance returns unchanged (BUT if the date isn't valid, `undefined` is returned).
|
|
17
|
+
* - `null` or `undefined` or `""` empty string returns `undefined`
|
|
19
18
|
* - The string `"now"` returns the current date (e.g. `new Date()`).
|
|
20
19
|
* - The string `"today"` returns the current date at midnight (e.g. `getMidnight()`).
|
|
21
20
|
* - The string `"tomorrow"` returns tomorrow's date at midnight (e.g. `addDays(getMidnight(), 1)`).
|
|
22
21
|
* - The string `"yesterday"` returns yesterday's date at midnight (e.g. `addDays(getMidnight(), 1)`).
|
|
23
22
|
* - Strings (e.g. `"2003-09-12"` or `"2003 feb 20:09"`) return the corresponding date (using `new Date(string)`).
|
|
24
23
|
* - Numbers are return the corresponding date (using `new Date(number)`, i.e. milliseconds since 01/01/1970).
|
|
25
|
-
* - Anything else
|
|
24
|
+
* - Anything else returns `undefined`
|
|
26
25
|
*
|
|
27
|
-
* @param possible Any value that we want to parse as a valid date.
|
|
26
|
+
* @param possible Any value that we want to parse as a valid date (defaults to `undefined`).
|
|
28
27
|
* @returns `Date` instance if the value could be converted to a valid date, and `null` if not.
|
|
29
28
|
*/
|
|
30
|
-
export function getOptionalDate(possible
|
|
31
|
-
if (possible === null)
|
|
29
|
+
export function getOptionalDate(possible) {
|
|
30
|
+
if (possible === undefined || possible === null || possible === "")
|
|
32
31
|
return undefined;
|
|
33
32
|
if (possible === "now")
|
|
34
33
|
return new Date();
|
|
@@ -46,8 +45,11 @@ export function getOptionalDate(possible = "now") {
|
|
|
46
45
|
return date;
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Convert a possible date to a `Date` instance, or throw `ValueError` if it couldn't be converted.
|
|
50
|
+
* @param possible Any value that we want to parse as a valid date (defaults to `"now"`).
|
|
51
|
+
*/
|
|
52
|
+
export function getDate(possible = "now") {
|
|
51
53
|
const date = getOptionalDate(possible);
|
|
52
54
|
if (!date)
|
|
53
55
|
throw new ValueError(`Invalid date`, possible);
|
package/util/path.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Optional } from "./optional.js";
|
|
1
2
|
/** Absolute path starts with `/` slash. */
|
|
2
3
|
export type AbsolutePath = `/` | `/${string}`;
|
|
3
4
|
/** Relative path starts with `./` or `../` */
|
|
@@ -9,23 +10,25 @@ export declare function isAbsolutePath(path: string): path is AbsolutePath;
|
|
|
9
10
|
/** Is a string path an absolute path? */
|
|
10
11
|
export declare function isRelativePath(path: string): path is RelativePath;
|
|
11
12
|
/**
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
13
|
+
* Resolve a relative or absolute path and return the path, or `undefined` if not a valid path.
|
|
14
|
+
* - Uses `new URL` to do path processing, so URL strings e.g.
|
|
15
|
+
* - Returned paths are cleaned with `cleanPath()` so runs of slashes and trailing slashes are removed.
|
|
16
|
+
*
|
|
17
|
+
* @param possible Absolute path e.g. `/a/b/c`, relative path e.g. `./a` or `b` or `../c`, URL string e.g. `http://shax.com/a/b/c`, or `URL` instance.
|
|
18
|
+
* @param base Absolute path used for resolving relative paths in `possible`
|
|
19
|
+
* @return Absolute path with a leading trailing slash, e.g. `/a/c/b`
|
|
16
20
|
*/
|
|
17
|
-
export declare function
|
|
18
|
-
export declare function cleanPath(path: string): string;
|
|
21
|
+
export declare function getOptionalPath(possible: Optional<string | URL>, base?: AbsolutePath | undefined): AbsolutePath | undefined;
|
|
19
22
|
/**
|
|
20
|
-
* Resolve
|
|
21
|
-
* -
|
|
23
|
+
* Resolve a relative or absolute path and return the path, or throw `ValueError` if not a valid path.
|
|
24
|
+
* - Internally uses `new URL` to do path processing but shouldn't ever reveal that fact.
|
|
22
25
|
* - Returned paths are cleaned with `cleanPath()` so runs of slashes and trailing slashes are removed.
|
|
23
26
|
*
|
|
24
|
-
* @param
|
|
25
|
-
* @param base Absolute path
|
|
27
|
+
* @param possible Absolute path e.g. `/a/b/c`, relative path e.g. `./a` or `b` or `../c`, URL string e.g. `http://shax.com/a/b/c`, or `URL` instance.
|
|
28
|
+
* @param base Absolute path used for resolving relative paths in `possible`
|
|
26
29
|
* @return Absolute path with a leading trailing slash, e.g. `/a/c/b`
|
|
27
30
|
*/
|
|
28
|
-
export declare function getPath(
|
|
31
|
+
export declare function getPath(possible: string, base?: AbsolutePath): AbsolutePath;
|
|
29
32
|
/** Is a target path active? */
|
|
30
33
|
export declare function isPathActive(target: AbsolutePath, current: AbsolutePath): boolean;
|
|
31
34
|
/** Is a target path proud (i.e. is the current path, or is a child of the current path)? */
|
package/util/path.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ValueError } from "../error/ValueError.js";
|
|
2
|
+
import { notOptional } from "./optional.js";
|
|
2
3
|
/** Is a string path an absolute path? */
|
|
3
4
|
export function isAbsolutePath(path) {
|
|
4
5
|
return path.startsWith("/");
|
|
@@ -7,29 +8,47 @@ export function isAbsolutePath(path) {
|
|
|
7
8
|
export function isRelativePath(path) {
|
|
8
9
|
return path.startsWith("./") || path.startsWith("../");
|
|
9
10
|
}
|
|
10
|
-
|
|
11
|
+
function _cleanPath(path) {
|
|
11
12
|
return path
|
|
12
13
|
.replace(/[/\\]+/g, "/") // Normalise slashes.
|
|
13
14
|
.replace(/(?!^)\/$/g, ""); // Trailing slashes.
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
|
-
* Resolve
|
|
17
|
+
* Resolve a relative or absolute path and return the path, or `undefined` if not a valid path.
|
|
17
18
|
* - Uses `new URL` to do path processing, so URL strings e.g.
|
|
18
19
|
* - Returned paths are cleaned with `cleanPath()` so runs of slashes and trailing slashes are removed.
|
|
19
20
|
*
|
|
20
|
-
* @param
|
|
21
|
-
* @param base Absolute path
|
|
21
|
+
* @param possible Absolute path e.g. `/a/b/c`, relative path e.g. `./a` or `b` or `../c`, URL string e.g. `http://shax.com/a/b/c`, or `URL` instance.
|
|
22
|
+
* @param base Absolute path used for resolving relative paths in `possible`
|
|
22
23
|
* @return Absolute path with a leading trailing slash, e.g. `/a/c/b`
|
|
23
24
|
*/
|
|
24
|
-
export function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
export function getOptionalPath(possible, base = "/") {
|
|
26
|
+
if (notOptional(possible)) {
|
|
27
|
+
try {
|
|
28
|
+
const { pathname, search, hash } = new URL(possible, `http://j.com${base}/`);
|
|
29
|
+
if (isAbsolutePath(pathname))
|
|
30
|
+
return `${_cleanPath(pathname)}${search}${hash}`;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
//
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a relative or absolute path and return the path, or throw `ValueError` if not a valid path.
|
|
39
|
+
* - Internally uses `new URL` to do path processing but shouldn't ever reveal that fact.
|
|
40
|
+
* - Returned paths are cleaned with `cleanPath()` so runs of slashes and trailing slashes are removed.
|
|
41
|
+
*
|
|
42
|
+
* @param possible Absolute path e.g. `/a/b/c`, relative path e.g. `./a` or `b` or `../c`, URL string e.g. `http://shax.com/a/b/c`, or `URL` instance.
|
|
43
|
+
* @param base Absolute path used for resolving relative paths in `possible`
|
|
44
|
+
* @return Absolute path with a leading trailing slash, e.g. `/a/c/b`
|
|
45
|
+
*/
|
|
46
|
+
export function getPath(possible, base) {
|
|
47
|
+
const path = getOptionalPath(possible, base);
|
|
48
|
+
if (!path)
|
|
49
|
+
throw new ValueError("Invalid URL", possible);
|
|
50
|
+
return path;
|
|
51
|
+
}
|
|
33
52
|
/** Is a target path active? */
|
|
34
53
|
export function isPathActive(target, current) {
|
|
35
54
|
return target === current;
|
package/util/time.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { Optional } from "./optional.js";
|
|
1
2
|
/** Class representing a time in the day in 24 hour format in the user's current locale. */
|
|
2
3
|
export declare class Time {
|
|
3
4
|
/** Make a new `Time` instance from a time string. */
|
|
4
|
-
static from(possible
|
|
5
|
+
static from(possible: unknown): Time | undefined;
|
|
5
6
|
readonly time: number;
|
|
6
7
|
constructor(time: number);
|
|
7
8
|
/** Get the number of hours in this time. */
|
|
@@ -28,27 +29,24 @@ export declare class Time {
|
|
|
28
29
|
valueOf(): number;
|
|
29
30
|
toString(): string;
|
|
30
31
|
}
|
|
31
|
-
/** Regular expression that matches a time in ISO 8601 format. */
|
|
32
|
-
export declare const TIME_REGEXP: RegExp;
|
|
33
32
|
/** Things that converted to times. */
|
|
34
33
|
export type PossibleTime = Time | Date | number | string;
|
|
35
34
|
/** Is an unknown value a `Time` instance. */
|
|
36
35
|
export declare function isTime(value: unknown): value is Time;
|
|
37
36
|
/**
|
|
38
|
-
* Convert a value to a `Time` instance or `undefined`
|
|
37
|
+
* Convert a value to a `Time` instance, or return `undefined` if it couldn't be converted.
|
|
39
38
|
* - Works with possible dates, e.g. `now` or `Date` or `2022-09-12 18:32` or `19827263567`
|
|
40
39
|
* - Works with time strings, e.g. `18:32` or `23:59:59.999`
|
|
40
|
+
*
|
|
41
|
+
* @param possible Any value that we want to parse as a valid time (defaults to `undefined`).
|
|
41
42
|
*/
|
|
42
43
|
export declare function getOptionalTime(possible: unknown): Time | undefined;
|
|
43
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Convert a possible date to a `Time` instance, or throw `ValueError` if it couldn't be converted (defaults to `"now"`).
|
|
46
|
+
* @param possible Any value that we want to parse as a valid time (defaults to `"now"`).
|
|
47
|
+
*/
|
|
44
48
|
export declare function getTime(possible?: PossibleTime): Time;
|
|
45
|
-
/** Get the time as in `hh:mm` format (hours, minutes), e.g. `13.59` */
|
|
46
|
-
export declare function getShortTime(time?: PossibleTime): string;
|
|
47
|
-
/** Get the time in `hh:mm:ss` format (hours, minutes seconds), e.g. `13.16.19.123` */
|
|
48
|
-
export declare function getMediumTime(time?: PossibleTime): string;
|
|
49
|
-
/** Get this time in `hh:mm:ss.fff` format (ISO 8601 compatible, hours, minutes, seconds, milliseconds), e.g. `13:16:19.123` */
|
|
50
|
-
export declare function getLongTime(time?: PossibleTime): string;
|
|
51
49
|
/** Format a time as a string based on the browser locale settings. */
|
|
52
50
|
export declare function formatTime(time?: PossibleTime, precision?: 2 | 3 | 4 | 5 | 6): string;
|
|
53
51
|
/** Format an optional time as a string based on the browser locale settings. */
|
|
54
|
-
export declare function formatOptionalTime(time
|
|
52
|
+
export declare function formatOptionalTime(time: Optional<PossibleTime>, precision?: 2 | 3 | 4 | 5 | 6): string | undefined;
|
package/util/time.js
CHANGED
|
@@ -5,8 +5,8 @@ import { wrapNumber } from "./number.js";
|
|
|
5
5
|
/** Class representing a time in the day in 24 hour format in the user's current locale. */
|
|
6
6
|
export class Time {
|
|
7
7
|
/** Make a new `Time` instance from a time string. */
|
|
8
|
-
static from(possible
|
|
9
|
-
if (possible === null)
|
|
8
|
+
static from(possible) {
|
|
9
|
+
if (possible === undefined || possible === null || possible === "")
|
|
10
10
|
return undefined;
|
|
11
11
|
if (isTime(possible))
|
|
12
12
|
return possible;
|
|
@@ -85,38 +85,31 @@ function _pad(num, size) {
|
|
|
85
85
|
return num.toString(10).padStart(size, "0000");
|
|
86
86
|
}
|
|
87
87
|
/** Regular expression that matches a time in ISO 8601 format. */
|
|
88
|
-
|
|
88
|
+
const TIME_REGEXP = /([0-9]+):([0-9]+)(?::([0-9]+)(?:.([0-9]+))?)?/;
|
|
89
89
|
/** Is an unknown value a `Time` instance. */
|
|
90
90
|
export function isTime(value) {
|
|
91
91
|
return value instanceof Time;
|
|
92
92
|
}
|
|
93
93
|
/**
|
|
94
|
-
* Convert a value to a `Time` instance or `undefined`
|
|
94
|
+
* Convert a value to a `Time` instance, or return `undefined` if it couldn't be converted.
|
|
95
95
|
* - Works with possible dates, e.g. `now` or `Date` or `2022-09-12 18:32` or `19827263567`
|
|
96
96
|
* - Works with time strings, e.g. `18:32` or `23:59:59.999`
|
|
97
|
+
*
|
|
98
|
+
* @param possible Any value that we want to parse as a valid time (defaults to `undefined`).
|
|
97
99
|
*/
|
|
98
100
|
export function getOptionalTime(possible) {
|
|
99
101
|
return Time.from(possible);
|
|
100
102
|
}
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Convert a possible date to a `Time` instance, or throw `ValueError` if it couldn't be converted (defaults to `"now"`).
|
|
105
|
+
* @param possible Any value that we want to parse as a valid time (defaults to `"now"`).
|
|
106
|
+
*/
|
|
107
|
+
export function getTime(possible = "now") {
|
|
103
108
|
const time = getOptionalTime(possible);
|
|
104
109
|
if (!time)
|
|
105
110
|
throw new ValueError(`Invalid time`, possible);
|
|
106
111
|
return time;
|
|
107
112
|
}
|
|
108
|
-
/** Get the time as in `hh:mm` format (hours, minutes), e.g. `13.59` */
|
|
109
|
-
export function getShortTime(time) {
|
|
110
|
-
return getTime(time).long;
|
|
111
|
-
}
|
|
112
|
-
/** Get the time in `hh:mm:ss` format (hours, minutes seconds), e.g. `13.16.19.123` */
|
|
113
|
-
export function getMediumTime(time) {
|
|
114
|
-
return getTime(time).long;
|
|
115
|
-
}
|
|
116
|
-
/** Get this time in `hh:mm:ss.fff` format (ISO 8601 compatible, hours, minutes, seconds, milliseconds), e.g. `13:16:19.123` */
|
|
117
|
-
export function getLongTime(time) {
|
|
118
|
-
return getTime(time).long;
|
|
119
|
-
}
|
|
120
113
|
/** Format a time as a string based on the browser locale settings. */
|
|
121
114
|
export function formatTime(time, precision = 2) {
|
|
122
115
|
return getTime(time).format(precision);
|
package/util/units.js
CHANGED
|
@@ -211,7 +211,7 @@ export const AREA_UNITS = new UnitList({
|
|
|
211
211
|
export const VOLUME_UNITS = new UnitList({
|
|
212
212
|
// Metric.
|
|
213
213
|
"milliliter": { abbr: "ml" },
|
|
214
|
-
"liter": { to: { milliliter: 1000 } },
|
|
214
|
+
"liter": { abbr: "ltr", to: { milliliter: 1000 } },
|
|
215
215
|
"cubic-centimeter": { abbr: "cm³", to: { milliliter: 1 } },
|
|
216
216
|
"cubic-meter": { abbr: "m³", to: { milliliter: MILLION } },
|
|
217
217
|
// US.
|
package/util/url.d.ts
CHANGED
|
@@ -1,13 +1,42 @@
|
|
|
1
1
|
import { type Optional } from "./optional.js";
|
|
2
|
+
import { type AbsolutePath } from "./path.js";
|
|
2
3
|
/** Things that can be converted to a URL instance. */
|
|
3
4
|
export type PossibleURL = string | URL;
|
|
4
|
-
/** Is an unknown value a URL? */
|
|
5
|
+
/** Is an unknown value a URL object? */
|
|
5
6
|
export declare function isURL(value: unknown): value is URL;
|
|
6
|
-
/** Assert that an unknown value is a URL. */
|
|
7
|
+
/** Assert that an unknown value is a URL object. */
|
|
7
8
|
export declare function assertURL(value: unknown): asserts value is URL;
|
|
8
|
-
/** Convert a possible URL to a URL or return `
|
|
9
|
-
export declare function getOptionalURL(
|
|
9
|
+
/** Convert a possible URL to a URL or return `undefined` if conversion fails. */
|
|
10
|
+
export declare function getOptionalURL(possible: Optional<PossibleURL>, base?: AbsoluteURI | AbsoluteURL | undefined): URL | undefined;
|
|
10
11
|
/** Convert a possible URL to a URL but throw `ValueError` if conversion fails. */
|
|
11
|
-
export declare function getURL(
|
|
12
|
-
/** Just get important part of a URL, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
13
|
-
export declare function formatURL(
|
|
12
|
+
export declare function getURL(possible: PossibleURL, base?: AbsoluteURI | AbsoluteURL): URL;
|
|
13
|
+
/** Just get the important part of a URL, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
14
|
+
export declare function formatURL(possible: PossibleURL, base?: AbsoluteURL | AbsoluteURI): string;
|
|
15
|
+
/**
|
|
16
|
+
* Absolute URL is a URL that has an absolute `href` and `pathname` properties.
|
|
17
|
+
* - e.g. `http://x.com/a/b` is an absolute URL because `.pathname` will be `/a/b`
|
|
18
|
+
* - e.g. `http://x.com` is an absolute URL because `.pathname` will be `/`
|
|
19
|
+
* - e.g. `mailto:me@gmail.com` is _not_ an absolute URL because `.pathname` will be `"me@gmail.com"` which does not start with `/` slash.
|
|
20
|
+
*/
|
|
21
|
+
export type AbsoluteURL = URL & {
|
|
22
|
+
href: AbsoluteURI;
|
|
23
|
+
pathname: AbsolutePath;
|
|
24
|
+
};
|
|
25
|
+
/** Is an unknown value an absolute URL object? */
|
|
26
|
+
export declare function isAbsoluteURL(value: unknown): value is AbsoluteURL;
|
|
27
|
+
/** Convert a possible URL to a URL or return `undefined` if conversion fails. */
|
|
28
|
+
export declare function getOptionalAbsoluteURL(possible: Optional<PossibleURL>, base?: AbsoluteURL | AbsoluteURI): AbsoluteURL | undefined;
|
|
29
|
+
/** Convert a possible URL to a URL or return `undefined` if conversion fails. */
|
|
30
|
+
export declare function getAbsoluteURL(possible: Optional<PossibleURL>, base?: AbsoluteURL | AbsoluteURI): AbsoluteURL | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Absolute URI string starting with e.g. `https://`
|
|
33
|
+
* - Indicates a URI with `://` e.g. `https://` and `ftp://` and `file://`
|
|
34
|
+
* - Does not allow non-heirarchical URIs like `mailto:me@gmail.com`
|
|
35
|
+
*/
|
|
36
|
+
export type AbsoluteURI = `${string}://${string}`;
|
|
37
|
+
/** Relative URI string starts with `./` or `../` or `/` */
|
|
38
|
+
export type RelativeURI = `.` | `./${string}` | `..` | `../${string}` | `/` | `/${string}`;
|
|
39
|
+
/** Convert a possible URL to a absolute URI string or return `undefined` if conversion fails. */
|
|
40
|
+
export declare function getOptionalAbsoluteURI(possible: Optional<PossibleURL>, base?: AbsoluteURL | AbsoluteURI): AbsoluteURI | undefined;
|
|
41
|
+
/** Convert a possible URL to an absolute URI string. */
|
|
42
|
+
export declare function getAbsoluteURI(possible: PossibleURL, base?: AbsoluteURL | AbsoluteURI): AbsoluteURI;
|
package/util/url.js
CHANGED
|
@@ -1,37 +1,66 @@
|
|
|
1
1
|
import { ValueError } from "../error/ValueError.js";
|
|
2
2
|
import { notOptional } from "./optional.js";
|
|
3
|
-
|
|
3
|
+
import { isAbsolutePath } from "./path.js";
|
|
4
|
+
/** Is an unknown value a URL object? */
|
|
4
5
|
export function isURL(value) {
|
|
5
6
|
return value instanceof URL;
|
|
6
7
|
}
|
|
7
|
-
/** Assert that an unknown value is a URL. */
|
|
8
|
+
/** Assert that an unknown value is a URL object. */
|
|
8
9
|
export function assertURL(value) {
|
|
9
10
|
if (!isURL(value))
|
|
10
11
|
throw new ValueError("Invalid URL", value);
|
|
11
12
|
}
|
|
12
|
-
/** Convert a possible URL to a URL or return `
|
|
13
|
-
export function getOptionalURL(
|
|
14
|
-
if (notOptional(
|
|
15
|
-
if (isURL(url))
|
|
16
|
-
return url;
|
|
13
|
+
/** Convert a possible URL to a URL or return `undefined` if conversion fails. */
|
|
14
|
+
export function getOptionalURL(possible, base = _BASE) {
|
|
15
|
+
if (notOptional(possible)) {
|
|
17
16
|
try {
|
|
18
|
-
return new URL(
|
|
17
|
+
return isURL(possible) ? possible : new URL(possible, base);
|
|
19
18
|
}
|
|
20
19
|
catch (e) {
|
|
21
20
|
//
|
|
22
21
|
}
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
|
-
const
|
|
24
|
+
const _BASE = typeof document === "object" ? document.baseURI : undefined;
|
|
26
25
|
/** Convert a possible URL to a URL but throw `ValueError` if conversion fails. */
|
|
27
|
-
export function getURL(
|
|
28
|
-
const url = getOptionalURL(
|
|
26
|
+
export function getURL(possible, base) {
|
|
27
|
+
const url = getOptionalURL(possible, base);
|
|
29
28
|
if (!url)
|
|
30
|
-
throw new ValueError("Invalid URL",
|
|
29
|
+
throw new ValueError("Invalid URL", possible);
|
|
31
30
|
return url;
|
|
32
31
|
}
|
|
33
|
-
/** Just get important part of a URL, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
34
|
-
export function formatURL(
|
|
35
|
-
const { host, pathname } = getURL(
|
|
32
|
+
/** Just get the important part of a URL, e.g. `http://shax.com/test?uid=129483` → `shax.com/test` */
|
|
33
|
+
export function formatURL(possible, base) {
|
|
34
|
+
const { host, pathname } = getURL(possible, base);
|
|
36
35
|
return `${host}${pathname.length > 1 ? pathname : ""}`;
|
|
37
36
|
}
|
|
37
|
+
/** Is an unknown value an absolute URL object? */
|
|
38
|
+
export function isAbsoluteURL(value) {
|
|
39
|
+
// Is an absolute URL if it's a URL and has both a protocol (e.g. `http:`) and a path starting with `/`
|
|
40
|
+
// Heirarchical URIs like `http:` and `ftp:` will always have pathname starting with `/`
|
|
41
|
+
return isURL(value) && !!value.protocol && isAbsolutePath(value.pathname);
|
|
42
|
+
}
|
|
43
|
+
/** Convert a possible URL to a URL or return `undefined` if conversion fails. */
|
|
44
|
+
export function getOptionalAbsoluteURL(possible, base) {
|
|
45
|
+
const url = getOptionalURL(possible, base);
|
|
46
|
+
if (isAbsoluteURL(url))
|
|
47
|
+
return url;
|
|
48
|
+
}
|
|
49
|
+
/** Convert a possible URL to a URL or return `undefined` if conversion fails. */
|
|
50
|
+
export function getAbsoluteURL(possible, base) {
|
|
51
|
+
const url = getOptionalAbsoluteURL(possible, base);
|
|
52
|
+
if (!url)
|
|
53
|
+
throw new ValueError("Invalid absolute URL", possible);
|
|
54
|
+
return url;
|
|
55
|
+
}
|
|
56
|
+
/** Convert a possible URL to a absolute URI string or return `undefined` if conversion fails. */
|
|
57
|
+
export function getOptionalAbsoluteURI(possible, base) {
|
|
58
|
+
return getOptionalAbsoluteURL(possible, base)?.href;
|
|
59
|
+
}
|
|
60
|
+
/** Convert a possible URL to an absolute URI string. */
|
|
61
|
+
export function getAbsoluteURI(possible, base) {
|
|
62
|
+
const link = getOptionalAbsoluteURI(possible, base);
|
|
63
|
+
if (!link)
|
|
64
|
+
throw new ValueError("Invalid link", possible);
|
|
65
|
+
return link;
|
|
66
|
+
}
|