shelving 1.51.3 → 1.53.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/Database.js +4 -4
- package/db/errors.d.ts +5 -5
- package/db/errors.js +10 -10
- package/markup/rules.d.ts +3 -3
- package/markup/rules.js +18 -14
- package/package.json +5 -5
- package/provider/ErrorProvider.d.ts +32 -0
- package/provider/ErrorProvider.js +175 -0
- package/provider/Provider.d.ts +1 -1
- package/provider/index.d.ts +1 -0
- package/provider/index.js +1 -0
- package/query/Query.js +1 -1
- package/query/Rules.js +1 -1
- package/react/useDocument.js +22 -10
- package/react/useQuery.js +19 -7
- package/util/jsx.d.ts +2 -2
package/db/Database.js
CHANGED
|
@@ -168,9 +168,9 @@ export class DatabaseQuery extends Query {
|
|
|
168
168
|
}
|
|
169
169
|
/** Get the data for a document from a result for that document. */
|
|
170
170
|
export function getQueryData(entries, ref) {
|
|
171
|
-
const
|
|
172
|
-
if (
|
|
173
|
-
return
|
|
171
|
+
const data = getQueryResult(entries, ref);
|
|
172
|
+
if (data)
|
|
173
|
+
return data;
|
|
174
174
|
throw new QueryRequiredError(ref);
|
|
175
175
|
}
|
|
176
176
|
/** Get the data for a document from a result for that document. */
|
|
@@ -178,7 +178,7 @@ export function getQueryResult(entries, ref) {
|
|
|
178
178
|
const first = getFirstItem(entries);
|
|
179
179
|
if (first)
|
|
180
180
|
return getDocumentData(first[1], ref.doc(first[0]));
|
|
181
|
-
|
|
181
|
+
return null;
|
|
182
182
|
}
|
|
183
183
|
/** A document reference within a specific database. */
|
|
184
184
|
export class DatabaseDocument {
|
package/db/errors.d.ts
CHANGED
|
@@ -7,16 +7,16 @@ export declare class DocumentRequiredError<T extends Data> extends RequiredError
|
|
|
7
7
|
ref: DatabaseDocument<T>;
|
|
8
8
|
constructor(ref: DatabaseDocument<T>);
|
|
9
9
|
}
|
|
10
|
-
/** Thrown if a query doesn't exist. */
|
|
11
|
-
export declare class QueryRequiredError<T extends Data> extends RequiredError {
|
|
12
|
-
ref: DatabaseQuery<T>;
|
|
13
|
-
constructor(ref: DatabaseQuery<T>);
|
|
14
|
-
}
|
|
15
10
|
/** Thrown if a document can't validate. */
|
|
16
11
|
export declare class DocumentValidationError<T extends Data> extends ValidationError {
|
|
17
12
|
ref: DatabaseDocument<T>;
|
|
18
13
|
constructor(ref: DatabaseDocument<T>, feedback: Feedback);
|
|
19
14
|
}
|
|
15
|
+
/** Thrown if a query doesn't exist. */
|
|
16
|
+
export declare class QueryRequiredError<T extends Data> extends RequiredError {
|
|
17
|
+
ref: DatabaseQuery<T>;
|
|
18
|
+
constructor(ref: DatabaseQuery<T>);
|
|
19
|
+
}
|
|
20
20
|
/** Thrown if a query can't validate a set of results. */
|
|
21
21
|
export declare class QueryValidationError<T extends Data> extends ValidationError {
|
|
22
22
|
ref: DatabaseQuery<T>;
|
package/db/errors.js
CHANGED
|
@@ -2,27 +2,27 @@ import { RequiredError, ValidationError } from "../error/index.js";
|
|
|
2
2
|
/** Thrown if a document doesn't exist. */
|
|
3
3
|
export class DocumentRequiredError extends RequiredError {
|
|
4
4
|
constructor(ref) {
|
|
5
|
-
super(`Document
|
|
5
|
+
super(`Document ${ref.toString()} does not exist`);
|
|
6
6
|
this.ref = ref;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
DocumentRequiredError.prototype.name = "DocumentRequiredError";
|
|
10
|
-
/** Thrown if a query doesn't exist. */
|
|
11
|
-
export class QueryRequiredError extends RequiredError {
|
|
12
|
-
constructor(ref) {
|
|
13
|
-
super(`Query "${ref.toString()}" has no results`);
|
|
14
|
-
this.ref = ref;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
QueryRequiredError.prototype.name = "QueryRequiredError";
|
|
18
10
|
/** Thrown if a document can't validate. */
|
|
19
11
|
export class DocumentValidationError extends ValidationError {
|
|
20
12
|
constructor(ref, feedback) {
|
|
21
|
-
super(`Invalid data for
|
|
13
|
+
super(`Invalid data for ${ref.toString()}`, feedback);
|
|
22
14
|
this.ref = ref;
|
|
23
15
|
}
|
|
24
16
|
}
|
|
25
17
|
DocumentValidationError.prototype.name = "DocumentValidationError";
|
|
18
|
+
/** Thrown if a query doesn't exist. */
|
|
19
|
+
export class QueryRequiredError extends RequiredError {
|
|
20
|
+
constructor(ref) {
|
|
21
|
+
super(`Query ${ref.toString()} has no results`);
|
|
22
|
+
this.ref = ref;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
QueryRequiredError.prototype.name = "QueryRequiredError";
|
|
26
26
|
/** Thrown if a query can't validate a set of results. */
|
|
27
27
|
export class QueryValidationError extends ValidationError {
|
|
28
28
|
constructor(ref, feedback) {
|
package/markup/rules.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare const FENCED_CODE_RULE: MarkupRule;
|
|
|
38
38
|
export declare const PARAGRAPH_RULE: MarkupRule;
|
|
39
39
|
/**
|
|
40
40
|
* Markdown-style link.
|
|
41
|
-
* - Link in standard Markdown format, e.g. `[http://google.com/maps
|
|
41
|
+
* - Link in standard Markdown format, e.g. `[Google Maps](http://google.com/maps)`
|
|
42
42
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
43
43
|
* - Does not need space before/after the link.
|
|
44
44
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
@@ -46,11 +46,11 @@ export declare const PARAGRAPH_RULE: MarkupRule;
|
|
|
46
46
|
*/
|
|
47
47
|
export declare const LINK_RULE: MarkupRule;
|
|
48
48
|
/**
|
|
49
|
-
* Autolinked URL starts with `http:` or `https:` and matches an unlimited number of non-space characters.
|
|
49
|
+
* Autolinked URL starts with `http:` or `https:` or `mailto:` (any scheme in `options.schemes`) and matches an unlimited number of non-space characters.
|
|
50
50
|
* - 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).
|
|
51
51
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
52
52
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
53
|
-
* - For security only schemes that appear in
|
|
53
|
+
* - For security only schemes that appear in `options.schemes` will match (defaults to `http:` and `https:`).
|
|
54
54
|
*/
|
|
55
55
|
export declare const AUTOLINK_RULE: MarkupRule;
|
|
56
56
|
/**
|
package/markup/rules.js
CHANGED
|
@@ -127,58 +127,62 @@ export const PARAGRAPH_RULE = {
|
|
|
127
127
|
};
|
|
128
128
|
/**
|
|
129
129
|
* Markdown-style link.
|
|
130
|
-
* - Link in standard Markdown format, e.g. `[http://google.com/maps
|
|
130
|
+
* - Link in standard Markdown format, e.g. `[Google Maps](http://google.com/maps)`
|
|
131
131
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
132
132
|
* - Does not need space before/after the link.
|
|
133
133
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
134
134
|
* - For security only `http://` or `https://` links will work (if invalid the unparsed text will be returned).
|
|
135
135
|
*/
|
|
136
136
|
export const LINK_RULE = {
|
|
137
|
-
// Custom matcher to check the URL against the allowed schemes.
|
|
138
137
|
regexp: /\[([^\]]*?)\]\(([^)]*?)\)/,
|
|
138
|
+
// Custom matcher to check the URL against the allowed schemes.
|
|
139
139
|
match: (content, { schemes, url: base }) => {
|
|
140
140
|
const matches = content.match(LINK_RULE.regexp);
|
|
141
|
-
if (matches
|
|
141
|
+
if (matches) {
|
|
142
142
|
const [, title = "", href = ""] = matches;
|
|
143
143
|
const url = toURL(href, base);
|
|
144
144
|
if (url && url.protocol && schemes.includes(url.protocol)) {
|
|
145
145
|
matches[1] = title.trim();
|
|
146
|
-
matches[2] = url.href;
|
|
146
|
+
matches[2] = url.href;
|
|
147
147
|
return matches;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
},
|
|
151
|
-
render: ([, title
|
|
152
|
-
type:
|
|
151
|
+
render: ([, title, href = ""], { rel }) => ({
|
|
152
|
+
type: "a",
|
|
153
153
|
key: null,
|
|
154
|
-
props: { children: title || formatUrl(href), href
|
|
154
|
+
props: { children: title || formatUrl(href), href, rel },
|
|
155
155
|
}),
|
|
156
156
|
contexts: ["inline", "list"],
|
|
157
157
|
childContext: "link",
|
|
158
158
|
};
|
|
159
159
|
/**
|
|
160
|
-
* Autolinked URL starts with `http:` or `https:` and matches an unlimited number of non-space characters.
|
|
160
|
+
* Autolinked URL starts with `http:` or `https:` or `mailto:` (any scheme in `options.schemes`) and matches an unlimited number of non-space characters.
|
|
161
161
|
* - 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).
|
|
162
162
|
* - If no title is specified a cleaned up version of the URL will be used, e.g. `google.com/maps`
|
|
163
163
|
* - If link is not valid (using `new URL(url)` then unparsed text will be returned.
|
|
164
|
-
* - For security only schemes that appear in
|
|
164
|
+
* - For security only schemes that appear in `options.schemes` will match (defaults to `http:` and `https:`).
|
|
165
165
|
*/
|
|
166
166
|
export const AUTOLINK_RULE = {
|
|
167
|
+
regexp: /([a-z]+:\S+)(?: +(?:\(([^)]*?)\)|\[([^\]]*?)\]))?/,
|
|
167
168
|
// Custom matcher to check the URL against the allowed schemes.
|
|
168
|
-
regexp: /([a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9]:\S+)(?: +(?:\(([^)]*?)\)|\[([^\]]*?)\]))?/,
|
|
169
169
|
match: (content, { schemes, url: base }) => {
|
|
170
170
|
const matches = content.match(AUTOLINK_RULE.regexp);
|
|
171
171
|
if (matches && typeof matches.index === "number") {
|
|
172
|
-
const [, href = "",
|
|
172
|
+
const [, href = "", roundTitle = "", squareTitle = ""] = matches;
|
|
173
173
|
const url = toURL(href, base);
|
|
174
174
|
if (url && url.protocol && schemes.includes(url.protocol)) {
|
|
175
|
-
matches[1] =
|
|
176
|
-
matches[2] =
|
|
175
|
+
matches[1] = url.href;
|
|
176
|
+
matches[2] = (roundTitle || squareTitle).trim();
|
|
177
177
|
return matches;
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
},
|
|
181
|
-
render:
|
|
181
|
+
render: ([, href = "", title], { rel }) => ({
|
|
182
|
+
type: "a",
|
|
183
|
+
key: null,
|
|
184
|
+
props: { children: title || formatUrl(href), href, rel },
|
|
185
|
+
}),
|
|
182
186
|
contexts: ["inline", "list"],
|
|
183
187
|
childContext: "link",
|
|
184
188
|
};
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"state-management",
|
|
12
12
|
"query-builder"
|
|
13
13
|
],
|
|
14
|
-
"version": "1.
|
|
14
|
+
"version": "1.53.0",
|
|
15
15
|
"repository": "https://github.com/dhoulb/shelving",
|
|
16
16
|
"author": "Dave Houlbrooke <dave@shax.com>",
|
|
17
17
|
"license": "0BSD",
|
|
@@ -63,13 +63,13 @@
|
|
|
63
63
|
"@types/jest": "^27.4.0",
|
|
64
64
|
"@types/react": "^17.0.39",
|
|
65
65
|
"@types/react-dom": "^17.0.11",
|
|
66
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
67
|
-
"@typescript-eslint/parser": "^5.
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
|
67
|
+
"@typescript-eslint/parser": "^5.12.0",
|
|
68
68
|
"eslint": "^8.9.0",
|
|
69
|
-
"eslint-config-prettier": "^8.
|
|
69
|
+
"eslint-config-prettier": "^8.4.0",
|
|
70
70
|
"eslint-plugin-import": "^2.25.4",
|
|
71
71
|
"eslint-plugin-prettier": "^4.0.0",
|
|
72
|
-
"firebase": "^9.6.
|
|
72
|
+
"firebase": "^9.6.7",
|
|
73
73
|
"jest": "^27.5.1",
|
|
74
74
|
"jest-ts-webcompat-resolver": "^1.0.0",
|
|
75
75
|
"prettier": "^2.5.1",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DatabaseDocument, DatabaseQuery } from "../db/Database.js";
|
|
2
|
+
import { Update } from "../update/Update.js";
|
|
3
|
+
import { Data, Result } from "../util/data.js";
|
|
4
|
+
import { Entries } from "../util/entry.js";
|
|
5
|
+
import { Observer, Unsubscriber } from "../util/observable.js";
|
|
6
|
+
import { ThroughProvider } from "./ThroughProvider.js";
|
|
7
|
+
/** Provider that wraps errors thrown from deeper providers in `DatabaseReadError` and `DatabaseWriteError` etc to make it easier to see what read/write caused the error. */
|
|
8
|
+
export declare class ErrorProvider extends ThroughProvider {
|
|
9
|
+
get<T extends Data>(ref: DatabaseDocument<T>): Result<T> | PromiseLike<Result<T>>;
|
|
10
|
+
subscribe<T extends Data>(ref: DatabaseDocument<T>, observer: Observer<Result<T>>): Unsubscriber;
|
|
11
|
+
add<T extends Data>(ref: DatabaseQuery<T>, data: T): string | PromiseLike<string>;
|
|
12
|
+
set<T extends Data>(ref: DatabaseDocument<T>, data: T): void | PromiseLike<void>;
|
|
13
|
+
update<T extends Data>(ref: DatabaseDocument<T>, updates: Update<T>): void | PromiseLike<void>;
|
|
14
|
+
delete<T extends Data>(ref: DatabaseDocument<T>): void | PromiseLike<void>;
|
|
15
|
+
getQuery<T extends Data>(ref: DatabaseQuery<T>): Entries<T> | PromiseLike<Entries<T>>;
|
|
16
|
+
subscribeQuery<T extends Data>(ref: DatabaseQuery<T>, observer: Observer<Entries<T>>): Unsubscriber;
|
|
17
|
+
setQuery<T extends Data>(ref: DatabaseQuery<T>, data: T): number | PromiseLike<number>;
|
|
18
|
+
updateQuery<T extends Data>(ref: DatabaseQuery<T>, updates: Update<T>): number | PromiseLike<number>;
|
|
19
|
+
deleteQuery<T extends Data>(ref: DatabaseQuery<T>): number | PromiseLike<number>;
|
|
20
|
+
}
|
|
21
|
+
/** Thrown if an error occurs while reading a query. */
|
|
22
|
+
export declare class DatabaseReadError<T extends Data> extends Error {
|
|
23
|
+
readonly error: Error;
|
|
24
|
+
readonly ref: DatabaseDocument<T> | DatabaseQuery<T>;
|
|
25
|
+
constructor(error: Error, ref: DatabaseDocument<T> | DatabaseQuery<T>);
|
|
26
|
+
}
|
|
27
|
+
/** Thrown if an error occurs while writing a document. */
|
|
28
|
+
export declare class DatabaseWriteError<T extends Data> extends Error {
|
|
29
|
+
readonly error: Error;
|
|
30
|
+
readonly ref: DatabaseDocument<T> | DatabaseQuery<T>;
|
|
31
|
+
constructor(error: Error, ref: DatabaseDocument<T> | DatabaseQuery<T>);
|
|
32
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { isAsync } from "../index.js";
|
|
2
|
+
import { ThroughObserver } from "../util/observable.js";
|
|
3
|
+
import { ThroughProvider } from "./ThroughProvider.js";
|
|
4
|
+
/** Provider that wraps errors thrown from deeper providers in `DatabaseReadError` and `DatabaseWriteError` etc to make it easier to see what read/write caused the error. */
|
|
5
|
+
export class ErrorProvider extends ThroughProvider {
|
|
6
|
+
get(ref) {
|
|
7
|
+
try {
|
|
8
|
+
const result = super.get(ref);
|
|
9
|
+
return isAsync(result)
|
|
10
|
+
? result.then(undefined, err => {
|
|
11
|
+
throw err instanceof Error ? new DatabaseReadError(err, ref) : err;
|
|
12
|
+
})
|
|
13
|
+
: result;
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
if (err instanceof Error)
|
|
17
|
+
throw new DatabaseReadError(err, ref);
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
subscribe(ref, observer) {
|
|
22
|
+
return super.subscribe(ref, new DatabaseErrorObserver(ref, observer));
|
|
23
|
+
}
|
|
24
|
+
add(ref, data) {
|
|
25
|
+
try {
|
|
26
|
+
const result = super.add(ref, data);
|
|
27
|
+
return isAsync(result)
|
|
28
|
+
? result.then(undefined, err => {
|
|
29
|
+
throw err instanceof Error ? new DatabaseWriteError(err, ref) : err;
|
|
30
|
+
})
|
|
31
|
+
: result;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (err instanceof Error)
|
|
35
|
+
throw new DatabaseWriteError(err, ref);
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
set(ref, data) {
|
|
40
|
+
try {
|
|
41
|
+
const result = super.set(ref, data);
|
|
42
|
+
return isAsync(result)
|
|
43
|
+
? result.then(undefined, err => {
|
|
44
|
+
throw err instanceof Error ? new DatabaseWriteError(err, ref) : err;
|
|
45
|
+
})
|
|
46
|
+
: result;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (err instanceof Error)
|
|
50
|
+
throw new DatabaseWriteError(err, ref);
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
update(ref, updates) {
|
|
55
|
+
try {
|
|
56
|
+
const result = super.update(ref, updates);
|
|
57
|
+
return isAsync(result)
|
|
58
|
+
? result.then(undefined, err => {
|
|
59
|
+
throw err instanceof Error ? new DatabaseWriteError(err, ref) : err;
|
|
60
|
+
})
|
|
61
|
+
: result;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
if (err instanceof Error)
|
|
65
|
+
throw new DatabaseWriteError(err, ref);
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
delete(ref) {
|
|
70
|
+
try {
|
|
71
|
+
const result = super.delete(ref);
|
|
72
|
+
return isAsync(result)
|
|
73
|
+
? result.then(undefined, err => {
|
|
74
|
+
throw err instanceof Error ? new DatabaseWriteError(err, ref) : err;
|
|
75
|
+
})
|
|
76
|
+
: result;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err instanceof Error)
|
|
80
|
+
throw new DatabaseWriteError(err, ref);
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
getQuery(ref) {
|
|
85
|
+
try {
|
|
86
|
+
const results = super.getQuery(ref);
|
|
87
|
+
return isAsync(results)
|
|
88
|
+
? results.then(undefined, err => {
|
|
89
|
+
throw err instanceof Error ? new DatabaseReadError(err, ref) : err;
|
|
90
|
+
})
|
|
91
|
+
: results;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err instanceof Error)
|
|
95
|
+
throw new DatabaseReadError(err, ref);
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
subscribeQuery(ref, observer) {
|
|
100
|
+
return super.subscribeQuery(ref, new DatabaseErrorObserver(ref, observer));
|
|
101
|
+
}
|
|
102
|
+
setQuery(ref, data) {
|
|
103
|
+
try {
|
|
104
|
+
const result = super.setQuery(ref, data);
|
|
105
|
+
return isAsync(result)
|
|
106
|
+
? result.then(undefined, err => {
|
|
107
|
+
throw err instanceof Error ? new DatabaseWriteError(err, ref) : err;
|
|
108
|
+
})
|
|
109
|
+
: result;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (err instanceof Error)
|
|
113
|
+
throw new DatabaseWriteError(err, ref);
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
updateQuery(ref, updates) {
|
|
118
|
+
try {
|
|
119
|
+
const result = super.updateQuery(ref, updates);
|
|
120
|
+
return isAsync(result)
|
|
121
|
+
? result.then(undefined, err => {
|
|
122
|
+
throw err instanceof Error ? new DatabaseWriteError(err, ref) : err;
|
|
123
|
+
})
|
|
124
|
+
: result;
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (err instanceof Error)
|
|
128
|
+
throw new DatabaseWriteError(err, ref);
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
deleteQuery(ref) {
|
|
133
|
+
try {
|
|
134
|
+
const result = super.deleteQuery(ref);
|
|
135
|
+
return isAsync(result)
|
|
136
|
+
? result.then(undefined, err => {
|
|
137
|
+
throw err instanceof Error ? new DatabaseWriteError(err, ref) : err;
|
|
138
|
+
})
|
|
139
|
+
: result;
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (err instanceof Error)
|
|
143
|
+
throw new DatabaseWriteError(err, ref);
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** Thrown if an error occurs while reading a query. */
|
|
149
|
+
export class DatabaseReadError extends Error {
|
|
150
|
+
constructor(error, ref) {
|
|
151
|
+
super(`Error reading ${ref.toString()}:\n${error.message}`);
|
|
152
|
+
this.error = error;
|
|
153
|
+
this.ref = ref;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
DatabaseReadError.prototype.name = "DatabaseReadError";
|
|
157
|
+
/** Thrown if an error occurs while writing a document. */
|
|
158
|
+
export class DatabaseWriteError extends Error {
|
|
159
|
+
constructor(error, ref) {
|
|
160
|
+
super(`Error writing ${ref.toString()}:\n${error.message}`);
|
|
161
|
+
this.error = error;
|
|
162
|
+
this.ref = ref;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
DatabaseWriteError.prototype.name = "DocumentWriteError";
|
|
166
|
+
/** Observer that wraps errors in subscriptions in `DatabaseReadError` */
|
|
167
|
+
class DatabaseErrorObserver extends ThroughObserver {
|
|
168
|
+
constructor(ref, target) {
|
|
169
|
+
super(target);
|
|
170
|
+
this.ref = ref;
|
|
171
|
+
}
|
|
172
|
+
error(error) {
|
|
173
|
+
super.error(error instanceof Error ? new DatabaseReadError(error, this.ref) : error);
|
|
174
|
+
}
|
|
175
|
+
}
|
package/provider/Provider.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export declare abstract class Provider {
|
|
|
40
40
|
*
|
|
41
41
|
* @throws Error If a `Update` was provided but the document does not exist (ideally a `RequiredError` but may be provider-specific).
|
|
42
42
|
*/
|
|
43
|
-
abstract set<T extends Data>(ref: DatabaseDocument<T>,
|
|
43
|
+
abstract set<T extends Data>(ref: DatabaseDocument<T>, data: T): void | PromiseLike<void>;
|
|
44
44
|
/**
|
|
45
45
|
* Update the data an existing document.
|
|
46
46
|
*
|
package/provider/index.d.ts
CHANGED
package/provider/index.js
CHANGED
package/query/Query.js
CHANGED
package/query/Rules.js
CHANGED
|
@@ -19,7 +19,7 @@ export class Rules extends Rule {
|
|
|
19
19
|
return this._rules.length;
|
|
20
20
|
}
|
|
21
21
|
toString() {
|
|
22
|
-
return this._rules.map(toString).join("
|
|
22
|
+
return this._rules.map(toString).join(",");
|
|
23
23
|
}
|
|
24
24
|
/** Clone this set of rules but add additional rules. */
|
|
25
25
|
with(...rules) {
|
package/react/useDocument.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { CacheProvider, throwAsync, NOERROR, findSourceProvider, NOVALUE, callAsync, getDocumentData } from "../index.js";
|
|
2
|
+
import { CacheProvider, throwAsync, NOERROR, findSourceProvider, NOVALUE, callAsync, getDocumentData, isAsync } from "../index.js";
|
|
3
3
|
import { usePureEffect } from "./usePureEffect.js";
|
|
4
4
|
import { usePureMemo } from "./usePureMemo.js";
|
|
5
5
|
import { usePureState } from "./usePureState.js";
|
|
@@ -9,13 +9,14 @@ export function useAsyncDocument(ref, maxAge = 1000) {
|
|
|
9
9
|
// Create two states to hold the value and error.
|
|
10
10
|
const [value, setNext] = usePureState(getCachedResult, memoRef);
|
|
11
11
|
const [error, setError] = useState(NOERROR);
|
|
12
|
-
if (error !== NOERROR)
|
|
13
|
-
throw error; // If there's an error throw it.
|
|
14
12
|
// Register effect.
|
|
15
13
|
usePureEffect(subscribeEffect, memoRef, maxAge, setNext, setError);
|
|
16
14
|
// Always return undefined if there's no ref.
|
|
17
15
|
if (!ref)
|
|
18
16
|
return undefined;
|
|
17
|
+
// If there's an error throw it.
|
|
18
|
+
if (error !== NOERROR)
|
|
19
|
+
throw error;
|
|
19
20
|
// If document is cached return the cached value.
|
|
20
21
|
if (value !== NOVALUE)
|
|
21
22
|
return value;
|
|
@@ -24,7 +25,10 @@ export function useAsyncDocument(ref, maxAge = 1000) {
|
|
|
24
25
|
if (maxAge === true)
|
|
25
26
|
setTimeout(ref.subscribe({ next: setNext, error: setError }), 10000);
|
|
26
27
|
// Return a promise for the result.
|
|
27
|
-
|
|
28
|
+
const result = ref.result;
|
|
29
|
+
if (isAsync(result))
|
|
30
|
+
result.then(setNext, setError);
|
|
31
|
+
return result;
|
|
28
32
|
}
|
|
29
33
|
/** Get the initial result for a reference from the cache. */
|
|
30
34
|
function getCachedResult(ref) {
|
|
@@ -34,22 +38,30 @@ function getCachedResult(ref) {
|
|
|
34
38
|
return provider.isCached(ref) ? provider.cache.get(ref) : NOVALUE;
|
|
35
39
|
}
|
|
36
40
|
/** Effect that subscribes a component to the cache for a reference. */
|
|
37
|
-
function subscribeEffect(ref, maxAge,
|
|
41
|
+
function subscribeEffect(ref, maxAge, setNext, setError) {
|
|
38
42
|
if (ref) {
|
|
39
43
|
const provider = findSourceProvider(ref.db.provider, CacheProvider);
|
|
40
|
-
const stopCache = provider.cache.subscribe(ref, { next, error });
|
|
44
|
+
const stopCache = provider.cache.subscribe(ref, { next: setNext, error: setError });
|
|
41
45
|
if (maxAge === true) {
|
|
42
46
|
// If `maxAge` is true subscribe to the source for as long as this component is attached.
|
|
43
|
-
const stopSource = ref.subscribe({ next, error });
|
|
47
|
+
const stopSource = ref.subscribe({ next: setNext, error: setError });
|
|
44
48
|
return () => {
|
|
45
49
|
stopCache();
|
|
46
50
|
stopSource();
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
|
-
else {
|
|
53
|
+
else if (provider.getCachedAge(ref) > maxAge) {
|
|
50
54
|
// If cache provider's cached document is older than maxAge then force refresh the data.
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
try {
|
|
56
|
+
const result = ref.result;
|
|
57
|
+
if (isAsync(result))
|
|
58
|
+
result.then(setNext, setError);
|
|
59
|
+
else
|
|
60
|
+
setNext(result);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
setError(e);
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
66
|
return stopCache;
|
|
55
67
|
}
|
package/react/useQuery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { CacheProvider, NOERROR, findSourceProvider, NOVALUE, getMap, callAsync, getQueryData, throwAsync, ResultsObserver, getQueryResult, } from "../index.js";
|
|
2
|
+
import { CacheProvider, NOERROR, findSourceProvider, NOVALUE, getMap, callAsync, getQueryData, throwAsync, ResultsObserver, getQueryResult, isAsync, } from "../index.js";
|
|
3
3
|
import { usePureEffect } from "./usePureEffect.js";
|
|
4
4
|
import { usePureMemo } from "./usePureMemo.js";
|
|
5
5
|
import { usePureState } from "./usePureState.js";
|
|
@@ -24,7 +24,10 @@ export function useAsyncQuery(ref, maxAge = 1000) {
|
|
|
24
24
|
if (maxAge === true)
|
|
25
25
|
setTimeout(ref.subscribe({ next: setNext, error: setError }), 10000);
|
|
26
26
|
// Return a promise for the result.
|
|
27
|
-
|
|
27
|
+
const results = ref.results;
|
|
28
|
+
if (isAsync(results))
|
|
29
|
+
results.then(setNext, setError);
|
|
30
|
+
return results;
|
|
28
31
|
}
|
|
29
32
|
/** Get the initial results for a reference from the cache. */
|
|
30
33
|
function getCachedResults(ref) {
|
|
@@ -34,10 +37,10 @@ function getCachedResults(ref) {
|
|
|
34
37
|
return provider.isCached(ref) ? getMap(provider.cache.getQuery(ref)) : NOVALUE;
|
|
35
38
|
}
|
|
36
39
|
/** Effect that subscribes a component to the cache for a reference. */
|
|
37
|
-
function subscribeEffect(ref, maxAge,
|
|
40
|
+
function subscribeEffect(ref, maxAge, setNext, setError) {
|
|
38
41
|
if (ref) {
|
|
39
42
|
const provider = findSourceProvider(ref.db.provider, CacheProvider);
|
|
40
|
-
const observer = new ResultsObserver({ next, error });
|
|
43
|
+
const observer = new ResultsObserver({ next: setNext, error: setError });
|
|
41
44
|
const stopCache = provider.cache.subscribeQuery(ref, observer);
|
|
42
45
|
if (maxAge === true) {
|
|
43
46
|
// If `maxAge` is true subscribe to the source for as long as this component is attached.
|
|
@@ -47,10 +50,19 @@ function subscribeEffect(ref, maxAge, next, error) {
|
|
|
47
50
|
stopSource();
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
|
-
else {
|
|
53
|
+
else if (provider.getCachedAge(ref) > maxAge) {
|
|
51
54
|
// If cache provider's cached document is older than maxAge then force refresh the data.
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
Promise.resolve(ref.results).then(setNext, setError);
|
|
56
|
+
try {
|
|
57
|
+
const results = ref.results;
|
|
58
|
+
if (isAsync(results))
|
|
59
|
+
results.then(setNext, setError);
|
|
60
|
+
else
|
|
61
|
+
setNext(results);
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
setError(e);
|
|
65
|
+
}
|
|
54
66
|
}
|
|
55
67
|
return stopCache;
|
|
56
68
|
}
|
package/util/jsx.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export declare type JSXProps = {
|
|
|
7
7
|
readonly children?: JSXNode;
|
|
8
8
|
};
|
|
9
9
|
/** JSX element (similar to `React.ReactElement`) */
|
|
10
|
-
export declare type JSXElement<P extends JSXProps = JSXProps
|
|
11
|
-
type:
|
|
10
|
+
export declare type JSXElement<P extends JSXProps = JSXProps> = {
|
|
11
|
+
type: string | JSXElementCreator<P>;
|
|
12
12
|
props: P;
|
|
13
13
|
key: string | number | null;
|
|
14
14
|
$$typeof?: symbol;
|