shelving 1.67.0 → 1.68.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/package.json +1 -1
- package/provider/MemoryProvider.d.ts +8 -4
- package/provider/MemoryProvider.js +38 -3
- package/provider/ThroughProvider.d.ts +1 -1
- package/provider/ThroughProvider.js +0 -2
- package/react/useCache.d.ts +26 -0
- package/react/useCache.js +36 -0
- package/react/useDocument.d.ts +4 -14
- package/react/useDocument.js +22 -24
- package/react/useQuery.d.ts +4 -14
- package/react/useQuery.js +23 -26
- package/state/State.d.ts +8 -1
- package/state/State.js +11 -1
- package/util/map.d.ts +3 -0
- package/util/map.js +8 -0
package/package.json
CHANGED
|
@@ -16,14 +16,14 @@ export declare class MemoryProvider extends Provider implements SynchronousProvi
|
|
|
16
16
|
/** List of tables in `{ path: Table }` format. */
|
|
17
17
|
private _tables;
|
|
18
18
|
getTable<T extends Data>({ collection }: DocumentReference<T> | QueryReference<T>): MemoryTable<T>;
|
|
19
|
-
getDocumentTime<T extends Data>(ref: DocumentReference<T>): number |
|
|
19
|
+
getDocumentTime<T extends Data>(ref: DocumentReference<T>): number | null;
|
|
20
20
|
getDocument<T extends Data>(ref: DocumentReference<T>): OptionalEntity<T>;
|
|
21
21
|
subscribeDocument<T extends Data>(ref: DocumentReference<T>, observer: PartialObserver<OptionalEntity<T>>): Unsubscribe;
|
|
22
22
|
addDocument<T extends Data>(ref: QueryReference<T>, data: T): string;
|
|
23
23
|
setDocument<T extends Data>(ref: DocumentReference<T>, data: T): void;
|
|
24
24
|
updateDocument<T extends Data>(ref: DocumentReference<T>, update: DataUpdate<T>): void;
|
|
25
25
|
deleteDocument<T extends Data>(ref: DocumentReference<T>): void;
|
|
26
|
-
getQueryTime<T extends Data>(ref: QueryReference<T>): number |
|
|
26
|
+
getQueryTime<T extends Data>(ref: QueryReference<T>): number | null;
|
|
27
27
|
getQuery<T extends Data>(ref: QueryReference<T>): Entities<T>;
|
|
28
28
|
subscribeQuery<T extends Data>(ref: QueryReference<T>, observer: PartialObserver<Entities<T>>): Unsubscribe;
|
|
29
29
|
setQuery<T extends Data>(ref: QueryReference<T>, data: T): number;
|
|
@@ -39,17 +39,21 @@ export declare class MemoryTable<T extends Data> extends Subject<void> {
|
|
|
39
39
|
protected _times: Map<string, number>;
|
|
40
40
|
protected _listeners: Set<Dispatch<[]>>;
|
|
41
41
|
protected _firing: boolean;
|
|
42
|
-
getDocumentTime(id: string): number |
|
|
42
|
+
getDocumentTime(id: string): number | null;
|
|
43
43
|
getDocument(id: string): OptionalEntity<T>;
|
|
44
44
|
subscribeDocument(id: string, observer: PartialObserver<OptionalEntity<T>>): Unsubscribe;
|
|
45
|
+
/** Subscribe to a query in this table, but only if the query has been explicitly set (and has a time). */
|
|
46
|
+
subscribeCachedDocument(id: string, observer: PartialObserver<OptionalEntity<T>>): Unsubscribe;
|
|
45
47
|
addDocument(data: T): string;
|
|
46
48
|
setEntity(entity: Entity<T>): void;
|
|
47
49
|
setDocument(id: string, data: T): void;
|
|
48
50
|
updateDocument(id: string, update: DataUpdate<T>): void;
|
|
49
51
|
deleteDocument(id: string): void;
|
|
50
|
-
getQueryTime(query: Query<Entity<T>>): number |
|
|
52
|
+
getQueryTime(query: Query<Entity<T>>): number | null;
|
|
51
53
|
getQuery(query: Query<Entity<T>>): Entities<T>;
|
|
52
54
|
subscribeQuery(query: Query<Entity<T>>, observer: PartialObserver<Entities<T>>): Unsubscribe;
|
|
55
|
+
/** Subscribe to a query in this table, but only if the query has been explicitly set (and has a time). */
|
|
56
|
+
subscribeCachedQuery(query: Query<Entity<T>>, observer: PartialObserver<Entities<T>>): Unsubscribe;
|
|
53
57
|
protected _getWrites(query: Query<Entity<T>>): Iterable<Entity<T>>;
|
|
54
58
|
setEntities(query: Query<Entity<T>>, entities: Entities<T>): number;
|
|
55
59
|
setQuery(query: Query<Entity<T>>, data: T): number;
|
|
@@ -76,7 +76,7 @@ export class MemoryTable extends Subject {
|
|
|
76
76
|
this._firing = false;
|
|
77
77
|
}
|
|
78
78
|
getDocumentTime(id) {
|
|
79
|
-
return this._times.get(id);
|
|
79
|
+
return this._times.get(id) || null;
|
|
80
80
|
}
|
|
81
81
|
getDocument(id) {
|
|
82
82
|
return this._data.get(id) || null;
|
|
@@ -94,6 +94,23 @@ export class MemoryTable extends Subject {
|
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
+
/** Subscribe to a query in this table, but only if the query has been explicitly set (and has a time). */
|
|
98
|
+
subscribeCachedDocument(id, observer) {
|
|
99
|
+
// Call next() immediately with initial results.
|
|
100
|
+
let last = this.getDocument(id);
|
|
101
|
+
if (this._times.has(id))
|
|
102
|
+
dispatchNext(observer, last);
|
|
103
|
+
// Call next() every time the collection changes.
|
|
104
|
+
return this.subscribe(() => {
|
|
105
|
+
if (this._times.has(id)) {
|
|
106
|
+
const next = this.getDocument(id);
|
|
107
|
+
if (next !== last) {
|
|
108
|
+
last = next;
|
|
109
|
+
dispatchNext(observer, last);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
97
114
|
addDocument(data) {
|
|
98
115
|
let id = getRandomKey();
|
|
99
116
|
while (this._data.has(id))
|
|
@@ -122,7 +139,7 @@ export class MemoryTable extends Subject {
|
|
|
122
139
|
this.next();
|
|
123
140
|
}
|
|
124
141
|
getQueryTime(query) {
|
|
125
|
-
return this._times.get(_getQueryReference(query));
|
|
142
|
+
return this._times.get(_getQueryReference(query)) || null;
|
|
126
143
|
}
|
|
127
144
|
getQuery(query) {
|
|
128
145
|
return getArray(query.transform(this._data.values()));
|
|
@@ -140,6 +157,24 @@ export class MemoryTable extends Subject {
|
|
|
140
157
|
}
|
|
141
158
|
});
|
|
142
159
|
}
|
|
160
|
+
/** Subscribe to a query in this table, but only if the query has been explicitly set (and has a time). */
|
|
161
|
+
subscribeCachedQuery(query, observer) {
|
|
162
|
+
// Call next() immediately with initial results.
|
|
163
|
+
const ref = _getQueryReference(query);
|
|
164
|
+
let last = this.getQuery(query);
|
|
165
|
+
if (this._times.has(ref))
|
|
166
|
+
dispatchNext(observer, last);
|
|
167
|
+
// Call next() every time the collection changes.
|
|
168
|
+
return this.subscribe(() => {
|
|
169
|
+
if (this._times.has(ref)) {
|
|
170
|
+
const next = this.getQuery(query);
|
|
171
|
+
if (next !== last) {
|
|
172
|
+
last = next;
|
|
173
|
+
dispatchNext(observer, last);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
143
178
|
_getWrites(query) {
|
|
144
179
|
// Queries that have no limit don't care about sorting either.
|
|
145
180
|
// So sorting can be skipped for performance.
|
|
@@ -196,5 +231,5 @@ export class MemoryTable extends Subject {
|
|
|
196
231
|
}
|
|
197
232
|
function _getQueryReference(query) {
|
|
198
233
|
// Queries that have no limit don't care about sorting either.
|
|
199
|
-
return query.limit ? `
|
|
234
|
+
return query.limit ? `{${query.filters.toString()}}` : Query.prototype.toString.call(query);
|
|
200
235
|
}
|
|
@@ -32,4 +32,4 @@ export interface AsynchronousThroughProvider extends AsynchronousProvider {
|
|
|
32
32
|
new (source: AsynchronousProvider): AsynchronousProvider;
|
|
33
33
|
}
|
|
34
34
|
/** Find a specific source provider in a database's provider stack. */
|
|
35
|
-
export declare function findSourceProvider<P extends Provider>(provider: Provider, type: Class<P>): P;
|
|
35
|
+
export declare function findSourceProvider<P extends Provider>(provider: Provider, type: Class<P>): P | undefined;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AssertionError } from "../error/AssertionError.js";
|
|
2
1
|
import { Provider } from "./Provider.js";
|
|
3
2
|
/**
|
|
4
3
|
* Pass all reads and writes through to a source provider.
|
|
@@ -48,5 +47,4 @@ export function findSourceProvider(provider, type) {
|
|
|
48
47
|
return provider;
|
|
49
48
|
if (provider instanceof ThroughProvider)
|
|
50
49
|
return findSourceProvider(provider.source, type);
|
|
51
|
-
throw new AssertionError(`Source provider ${type.name} not found`, provider);
|
|
52
50
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
/**
|
|
3
|
+
* Controller that creates an independent cache.
|
|
4
|
+
* - The cache is a `Map` instance that stores indexed items.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CacheController<T> {
|
|
7
|
+
protected _context: import("react").Context<Map<string, T> | undefined>;
|
|
8
|
+
/** Use this cache in a component. */
|
|
9
|
+
readonly useCache: () => Map<string, T>;
|
|
10
|
+
/** Component that provides a cache of this type to its children. */
|
|
11
|
+
readonly Cache: ({ children }: {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}) => React.ReactElement | null;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Default cache
|
|
17
|
+
* - This is a flexible generic cache intended to be the default.
|
|
18
|
+
* - Use this cache unless
|
|
19
|
+
*/
|
|
20
|
+
export declare const CACHE: CacheController<any>;
|
|
21
|
+
/** Use a global cache in a component. */
|
|
22
|
+
export declare const useCache: () => Map<string, any>;
|
|
23
|
+
/** Component that provides a global cache to its children. */
|
|
24
|
+
export declare const Cache: ({ children }: {
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
}) => React.ReactElement | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useContext, createContext, createElement } from "react";
|
|
2
|
+
import { ConditionError } from "../error/ConditionError.js";
|
|
3
|
+
import { useReduce } from "./useReduce.js";
|
|
4
|
+
/**
|
|
5
|
+
* Controller that creates an independent cache.
|
|
6
|
+
* - The cache is a `Map` instance that stores indexed items.
|
|
7
|
+
*/
|
|
8
|
+
export class CacheController {
|
|
9
|
+
constructor() {
|
|
10
|
+
this._context = createContext(undefined);
|
|
11
|
+
/** Use this cache in a component. */
|
|
12
|
+
this.useCache = () => {
|
|
13
|
+
const cache = useContext(this._context);
|
|
14
|
+
if (!cache)
|
|
15
|
+
throw new ConditionError("useCache() must be used inside <Cache>");
|
|
16
|
+
return cache;
|
|
17
|
+
};
|
|
18
|
+
/** Component that provides a cache of this type to its children. */
|
|
19
|
+
this.Cache = ({ children }) => {
|
|
20
|
+
const cache = useReduce(_reduceMap);
|
|
21
|
+
return createElement(this._context.Provider, { children, value: cache });
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Reducer that gets an existing `Map` instance or creates a new one. */
|
|
26
|
+
const _reduceMap = (previous) => previous || new Map();
|
|
27
|
+
/**
|
|
28
|
+
* Default cache
|
|
29
|
+
* - This is a flexible generic cache intended to be the default.
|
|
30
|
+
* - Use this cache unless
|
|
31
|
+
*/
|
|
32
|
+
export const CACHE = new CacheController(); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
33
|
+
/** Use a global cache in a component. */
|
|
34
|
+
export const useCache = CACHE.useCache;
|
|
35
|
+
/** Component that provides a global cache to its children. */
|
|
36
|
+
export const Cache = CACHE.Cache;
|
package/react/useDocument.d.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../observe/Observable.js";
|
|
2
2
|
import type { DocumentReference } from "../db/Reference.js";
|
|
3
|
-
import type { Data, OptionalEntity } from "../util/data.js";
|
|
4
|
-
import { Entity } from "../util/data.js";
|
|
3
|
+
import type { Data, OptionalEntity, Entity } from "../util/data.js";
|
|
5
4
|
import { State } from "../state/State.js";
|
|
6
|
-
import { MemoryTable } from "../provider/MemoryProvider.js";
|
|
7
5
|
import { BooleanState } from "../state/BooleanState.js";
|
|
8
6
|
/** Hold the current state of a document. */
|
|
9
7
|
export declare class DocumentState<T extends Data> extends State<OptionalEntity<T>> {
|
|
10
8
|
readonly ref: DocumentReference<T>;
|
|
11
9
|
readonly busy: BooleanState;
|
|
12
|
-
protected readonly _table: MemoryTable<T>;
|
|
13
|
-
/** Time this state was last updated with a new value. */
|
|
14
|
-
get time(): number | undefined;
|
|
15
|
-
/** How old this state's value is (in milliseconds). */
|
|
16
|
-
get age(): number;
|
|
17
10
|
/** Get the data of the document (throws `RequiredError` if document doesn't exist). */
|
|
18
11
|
get data(): Entity<T>;
|
|
19
12
|
/** Does the document exist (i.e. its value isn't `null`)? */
|
|
@@ -25,14 +18,11 @@ export declare class DocumentState<T extends Data> extends State<OptionalEntity<
|
|
|
25
18
|
refreshStale(maxAge: number): void;
|
|
26
19
|
/** Subscribe this state to the source provider. */
|
|
27
20
|
connectSource(): Unsubscribe;
|
|
21
|
+
/** Subscribe this state to any `CacheProvider` that exists in the provider chain. */
|
|
22
|
+
connectCache(): Unsubscribe | void;
|
|
28
23
|
protected _addFirstObserver(): void;
|
|
29
24
|
protected _removeLastObserver(): void;
|
|
30
25
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Use a document in a React component.
|
|
33
|
-
* - Use `useDocument(ref).data` to get the data of the document.
|
|
34
|
-
* - Use `useDocument(ref).value` to get the data of the document or `null` if it doesn't exist.
|
|
35
|
-
* - Use `useDocument(ref).exists` to check if the document is loaded before accessing `.data` or `.value`
|
|
36
|
-
*/
|
|
26
|
+
/** Use a document in a React component. */
|
|
37
27
|
export declare function useDocument<T extends Data>(ref: DocumentReference<T>): DocumentState<T>;
|
|
38
28
|
export declare function useDocument<T extends Data>(ref?: DocumentReference<T>): DocumentState<T> | undefined;
|
package/react/useDocument.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
+
import { reduceMapItem } from "../util/map.js";
|
|
1
2
|
import { getDocumentData, isSameReference } from "../db/Reference.js";
|
|
2
3
|
import { CacheProvider } from "../provider/CacheProvider.js";
|
|
3
4
|
import { findSourceProvider } from "../provider/ThroughProvider.js";
|
|
4
5
|
import { State } from "../state/State.js";
|
|
5
|
-
import { MatchObserver } from "../observe/MatchObserver.js";
|
|
6
6
|
import { BooleanState } from "../state/BooleanState.js";
|
|
7
7
|
import { ConditionError } from "../error/ConditionError.js";
|
|
8
|
-
import {
|
|
8
|
+
import { NOVALUE } from "../util/constants.js";
|
|
9
9
|
import { useSubscribe } from "./useSubscribe.js";
|
|
10
|
+
import { useCache } from "./useCache.js";
|
|
10
11
|
/** Hold the current state of a document. */
|
|
11
12
|
export class DocumentState extends State {
|
|
12
13
|
constructor(ref) {
|
|
13
|
-
|
|
14
|
+
var _a;
|
|
15
|
+
const table = (_a = findSourceProvider(ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(ref);
|
|
16
|
+
const time = table ? table.getDocumentTime(ref.id) : null;
|
|
17
|
+
const isCached = typeof time === "number";
|
|
18
|
+
super(table && isCached ? table.getDocument(ref.id) : NOVALUE);
|
|
14
19
|
this.busy = new BooleanState();
|
|
15
20
|
/** Refresh this state from the source provider. */
|
|
16
21
|
this.refresh = async () => {
|
|
@@ -30,23 +35,11 @@ export class DocumentState extends State {
|
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
};
|
|
33
|
-
this.
|
|
38
|
+
this._time = time;
|
|
34
39
|
this.ref = ref;
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.next(this._table.getDocument(ref.id)); // Use the existing cached value.
|
|
39
|
-
else
|
|
40
|
-
void this.refresh(); // Queue a request to refresh the value.
|
|
41
|
-
}
|
|
42
|
-
/** Time this state was last updated with a new value. */
|
|
43
|
-
get time() {
|
|
44
|
-
return this._table.getDocumentTime(this.ref.id);
|
|
45
|
-
}
|
|
46
|
-
/** How old this state's value is (in milliseconds). */
|
|
47
|
-
get age() {
|
|
48
|
-
const time = this.time;
|
|
49
|
-
return typeof time === "number" ? Date.now() - time : Infinity;
|
|
40
|
+
// Queue a request to refresh the value if it doesn't exist.
|
|
41
|
+
if (this.loading)
|
|
42
|
+
void this.refresh();
|
|
50
43
|
}
|
|
51
44
|
/** Get the data of the document (throws `RequiredError` if document doesn't exist). */
|
|
52
45
|
get data() {
|
|
@@ -65,11 +58,15 @@ export class DocumentState extends State {
|
|
|
65
58
|
connectSource() {
|
|
66
59
|
return this.connect(() => this.ref.subscribe({}));
|
|
67
60
|
}
|
|
61
|
+
/** Subscribe this state to any `CacheProvider` that exists in the provider chain. */
|
|
62
|
+
connectCache() {
|
|
63
|
+
var _a;
|
|
64
|
+
const table = (_a = findSourceProvider(this.ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(this.ref);
|
|
65
|
+
table && this.connect(() => table.subscribeCachedDocument(this.ref.id, this));
|
|
66
|
+
}
|
|
68
67
|
// Override to subscribe to the cache when an observer is added.
|
|
69
68
|
_addFirstObserver() {
|
|
70
|
-
|
|
71
|
-
// Connect through a `MatchObserver` that only dispatches `next()` if the document is actually cached (it might just be `null` because no document has been cached yet).
|
|
72
|
-
this.connect(() => this._table.subscribeDocument(this.ref.id, new MatchObserver(() => this._table.getDocumentTime(this.ref.id) !== undefined, this)));
|
|
69
|
+
this.connectCache();
|
|
73
70
|
}
|
|
74
71
|
// Override to unsubscribe from the cache when an observer is removed.
|
|
75
72
|
_removeLastObserver() {
|
|
@@ -78,9 +75,10 @@ export class DocumentState extends State {
|
|
|
78
75
|
}
|
|
79
76
|
}
|
|
80
77
|
/** Reuse the previous `DocumentState` or create a new one. */
|
|
81
|
-
const
|
|
78
|
+
const _reduceDocumentState = (existing, ref) => (existing && isSameReference(existing.ref, ref) ? existing : new DocumentState(ref));
|
|
82
79
|
export function useDocument(ref) {
|
|
83
|
-
const
|
|
80
|
+
const cache = useCache();
|
|
81
|
+
const state = ref ? reduceMapItem(cache, ref.toString(), _reduceDocumentState, ref) : undefined;
|
|
84
82
|
useSubscribe(state);
|
|
85
83
|
return state;
|
|
86
84
|
}
|
package/react/useQuery.d.ts
CHANGED
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import type { Unsubscribe } from "../observe/Observable.js";
|
|
2
2
|
import type { QueryReference } from "../db/Reference.js";
|
|
3
|
-
import type { Data, Entities, OptionalEntity } from "../util/data.js";
|
|
4
|
-
import { Entity } from "../util/data.js";
|
|
3
|
+
import type { Data, Entities, OptionalEntity, Entity } from "../util/data.js";
|
|
5
4
|
import { State } from "../state/State.js";
|
|
6
|
-
import { MemoryTable } from "../provider/MemoryProvider.js";
|
|
7
5
|
import { BooleanState } from "../state/BooleanState.js";
|
|
8
6
|
/** Hold the current state of a query. */
|
|
9
7
|
export declare class QueryState<T extends Data> extends State<Entities<T>> {
|
|
10
8
|
readonly ref: QueryReference<T>;
|
|
11
9
|
readonly busy: BooleanState;
|
|
12
10
|
readonly limit: number;
|
|
13
|
-
protected readonly _table: MemoryTable<T>;
|
|
14
|
-
/** Time this state was last updated with a new value. */
|
|
15
|
-
get time(): number | undefined;
|
|
16
|
-
/** How old this state's value is (in milliseconds). */
|
|
17
|
-
get age(): number;
|
|
18
11
|
/** Can more items be loaded after the current result. */
|
|
19
12
|
get hasMore(): boolean;
|
|
20
13
|
protected _hasMore: boolean;
|
|
@@ -37,6 +30,8 @@ export declare class QueryState<T extends Data> extends State<Entities<T>> {
|
|
|
37
30
|
refreshStale(maxAge: number): void;
|
|
38
31
|
/** Subscribe this state to the source provider. */
|
|
39
32
|
connectSource(): Unsubscribe;
|
|
33
|
+
/** Subscribe this state to any `CacheProvider` that exists in the provider chain. */
|
|
34
|
+
connectCache(): Unsubscribe | void;
|
|
40
35
|
protected _addFirstObserver(): void;
|
|
41
36
|
protected _removeLastObserver(): void;
|
|
42
37
|
/**
|
|
@@ -45,11 +40,6 @@ export declare class QueryState<T extends Data> extends State<Entities<T>> {
|
|
|
45
40
|
*/
|
|
46
41
|
readonly loadMore: () => Promise<void>;
|
|
47
42
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Use a query in a React component.
|
|
50
|
-
* - Use `useQuery(ref).data` to get the data of the query.
|
|
51
|
-
* - Use `useQuery(ref).value` to get the data of the query or `null` if it doesn't exist.
|
|
52
|
-
* - Use `useQuery(ref).exists` to check if the query is loaded before accessing `.data` or `.value`
|
|
53
|
-
*/
|
|
43
|
+
/** Use a query in a React component. */
|
|
54
44
|
export declare function useQuery<T extends Data>(ref: QueryReference<T>): QueryState<T>;
|
|
55
45
|
export declare function useQuery<T extends Data>(ref?: QueryReference<T>): QueryState<T> | undefined;
|
package/react/useQuery.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import { reduceMapItem } from "../util/map.js";
|
|
1
2
|
import { getQueryFirstData, getQueryFirstValue, isSameReference } from "../db/Reference.js";
|
|
2
3
|
import { CacheProvider } from "../provider/CacheProvider.js";
|
|
3
4
|
import { findSourceProvider } from "../provider/ThroughProvider.js";
|
|
4
5
|
import { State } from "../state/State.js";
|
|
5
|
-
import { MatchObserver } from "../observe/MatchObserver.js";
|
|
6
6
|
import { ConditionError } from "../error/ConditionError.js";
|
|
7
7
|
import { BooleanState } from "../state/BooleanState.js";
|
|
8
|
-
import {
|
|
8
|
+
import { NOVALUE } from "../util/constants.js";
|
|
9
9
|
import { useSubscribe } from "./useSubscribe.js";
|
|
10
|
+
import { useCache } from "./useCache.js";
|
|
10
11
|
/** Hold the current state of a query. */
|
|
11
12
|
export class QueryState extends State {
|
|
12
13
|
constructor(ref) {
|
|
13
|
-
var _a;
|
|
14
|
-
|
|
14
|
+
var _a, _b;
|
|
15
|
+
const table = (_a = findSourceProvider(ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(ref);
|
|
16
|
+
const time = table ? table.getQueryTime(ref) : null;
|
|
17
|
+
const isCached = typeof time === "number";
|
|
18
|
+
super(table && isCached ? table.getQuery(ref) : NOVALUE);
|
|
15
19
|
this.busy = new BooleanState();
|
|
16
20
|
this._hasMore = false;
|
|
17
21
|
/** Refresh this state from the source provider. */
|
|
@@ -55,24 +59,12 @@ export class QueryState extends State {
|
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
61
|
};
|
|
58
|
-
this.
|
|
62
|
+
this._time = time;
|
|
59
63
|
this.ref = ref;
|
|
60
|
-
this.limit = (
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.next(this._table.getQuery(ref)); // Use the existing cached value.
|
|
65
|
-
else
|
|
66
|
-
void this.refresh(); // Queue a request to refresh the value.
|
|
67
|
-
}
|
|
68
|
-
/** Time this state was last updated with a new value. */
|
|
69
|
-
get time() {
|
|
70
|
-
return this._table.getQueryTime(this.ref);
|
|
71
|
-
}
|
|
72
|
-
/** How old this state's value is (in milliseconds). */
|
|
73
|
-
get age() {
|
|
74
|
-
const time = this.time;
|
|
75
|
-
return typeof time === "number" ? Date.now() - time : Infinity;
|
|
64
|
+
this.limit = (_b = ref.limit) !== null && _b !== void 0 ? _b : Infinity;
|
|
65
|
+
// Queue a request to refresh the value if it doesn't exist.
|
|
66
|
+
if (this.loading)
|
|
67
|
+
void this.refresh();
|
|
76
68
|
}
|
|
77
69
|
/** Can more items be loaded after the current result. */
|
|
78
70
|
get hasMore() {
|
|
@@ -111,11 +103,15 @@ export class QueryState extends State {
|
|
|
111
103
|
connectSource() {
|
|
112
104
|
return this.connect(() => this.ref.subscribe({}));
|
|
113
105
|
}
|
|
106
|
+
/** Subscribe this state to any `CacheProvider` that exists in the provider chain. */
|
|
107
|
+
connectCache() {
|
|
108
|
+
var _a;
|
|
109
|
+
const table = (_a = findSourceProvider(this.ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(this.ref);
|
|
110
|
+
return table && this.connect(() => table.subscribeCachedQuery(this.ref, this));
|
|
111
|
+
}
|
|
114
112
|
// Override to subscribe to the cache when an observer is added.
|
|
115
113
|
_addFirstObserver() {
|
|
116
|
-
|
|
117
|
-
// Connect through a `MatchObserver` that only dispatches `next()` if the query is actually cached (it might just be `[]` because no query has been cached yet).
|
|
118
|
-
this.connect(() => this._table.subscribeQuery(this.ref, new MatchObserver(() => this._table.getQueryTime(this.ref) !== undefined, this)));
|
|
114
|
+
this.connectCache();
|
|
119
115
|
}
|
|
120
116
|
// Override to unsubscribe from the cache when an observer is removed.
|
|
121
117
|
_removeLastObserver() {
|
|
@@ -124,9 +120,10 @@ export class QueryState extends State {
|
|
|
124
120
|
}
|
|
125
121
|
}
|
|
126
122
|
/** Reuse the previous `QueryState` or create a new one. */
|
|
127
|
-
const
|
|
123
|
+
const _reduceQueryState = (existing, ref) => (existing && isSameReference(existing.ref, ref) ? existing : new QueryState(ref));
|
|
128
124
|
export function useQuery(ref) {
|
|
129
|
-
const
|
|
125
|
+
const cache = useCache();
|
|
126
|
+
const state = ref ? reduceMapItem(cache, ref.toString(), _reduceQueryState, ref) : undefined;
|
|
130
127
|
useSubscribe(state);
|
|
131
128
|
return state;
|
|
132
129
|
}
|
package/state/State.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NOERROR } from "../util/constants.js";
|
|
1
|
+
import { NOVALUE, NOERROR } from "../util/constants.js";
|
|
2
2
|
import { Matchable } from "../util/match.js";
|
|
3
3
|
import { Subject } from "../observe/Subject.js";
|
|
4
4
|
import { Observer } from "../observe/Observer.js";
|
|
@@ -20,7 +20,14 @@ export declare class State<T> extends Subject<T> implements Matchable<T, void> {
|
|
|
20
20
|
/** Most recently dispatched value (or throw `Promise` that resolves to the next value). */
|
|
21
21
|
get value(): T;
|
|
22
22
|
private _value;
|
|
23
|
+
/** Time this state was last updated with a new value. */
|
|
24
|
+
get time(): number | null;
|
|
25
|
+
protected _time: number | null;
|
|
26
|
+
/** How old this state's value is (in milliseconds). */
|
|
27
|
+
get age(): number;
|
|
23
28
|
/** State is initiated with an initial state. */
|
|
29
|
+
constructor(initial: T | typeof NOVALUE);
|
|
30
|
+
constructor();
|
|
24
31
|
constructor(...args: [] | [T]);
|
|
25
32
|
/** Is there a current value, or is it still loading. */
|
|
26
33
|
get loading(): boolean;
|
package/state/State.js
CHANGED
|
@@ -14,12 +14,12 @@ import { dispatchComplete, dispatchError, dispatchNext } from "../observe/Observ
|
|
|
14
14
|
* - To set the state to an explicit value, use that value or another `State` instance with a value.
|
|
15
15
|
* */
|
|
16
16
|
export class State extends Subject {
|
|
17
|
-
/** State is initiated with an initial state. */
|
|
18
17
|
constructor(...args) {
|
|
19
18
|
super();
|
|
20
19
|
/** Cached reason this state errored. */
|
|
21
20
|
this.reason = NOERROR;
|
|
22
21
|
this._value = args.length ? args[0] : NOVALUE;
|
|
22
|
+
this._time = args.length ? Date.now() : null;
|
|
23
23
|
}
|
|
24
24
|
/** Most recently dispatched value (or throw `Promise` that resolves to the next value). */
|
|
25
25
|
get value() {
|
|
@@ -29,6 +29,15 @@ export class State extends Subject {
|
|
|
29
29
|
throw awaitNext(this);
|
|
30
30
|
return this._value;
|
|
31
31
|
}
|
|
32
|
+
/** Time this state was last updated with a new value. */
|
|
33
|
+
get time() {
|
|
34
|
+
return this._time;
|
|
35
|
+
}
|
|
36
|
+
/** How old this state's value is (in milliseconds). */
|
|
37
|
+
get age() {
|
|
38
|
+
const time = this.time;
|
|
39
|
+
return time !== null ? Date.now() - time : Infinity;
|
|
40
|
+
}
|
|
32
41
|
/** Is there a current value, or is it still loading. */
|
|
33
42
|
get loading() {
|
|
34
43
|
return this._value === NOVALUE;
|
|
@@ -59,6 +68,7 @@ export class State extends Subject {
|
|
|
59
68
|
// Override to save value that is dispatched.
|
|
60
69
|
_dispatch(value) {
|
|
61
70
|
this._value = value;
|
|
71
|
+
this._time = Date.now();
|
|
62
72
|
super._dispatch(value);
|
|
63
73
|
}
|
|
64
74
|
// Implement Matchable to see if a value matches the current value of this state.
|
package/util/map.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Entry } from "./entry.js";
|
|
2
|
+
import type { Arguments } from "./function.js";
|
|
2
3
|
/**
|
|
3
4
|
* `Map` with string keys that cannot be changed.
|
|
4
5
|
* - Only allows keys to be `string`
|
|
@@ -20,3 +21,5 @@ export declare type PossibleMap<T> = ImmutableMap<T> | Iterable<Entry<T>>;
|
|
|
20
21
|
/** Convert an iterable to a `Map` (if it's already a `Map` it passes through unchanged). */
|
|
21
22
|
export declare function getMap<T>(iterable: ImmutableMap<T> | Iterable<Entry<T>>): ImmutableMap<T>;
|
|
22
23
|
export declare function getMap<T>(iterable: PossibleMap<T>): ImmutableMap<T>;
|
|
24
|
+
/** Function that lets new items in a map be created and updated by calling a `reduce()` callback that receives the existing value. */
|
|
25
|
+
export declare function reduceMapItem<K, T, A extends Arguments = []>(map: Map<K, T>, key: K, reduce: (existing: T | undefined, ...a: A) => T, ...args: A): T;
|
package/util/map.js
CHANGED
|
@@ -8,3 +8,11 @@ export function limitMap(map, limit) {
|
|
|
8
8
|
export function getMap(iterable) {
|
|
9
9
|
return iterable instanceof Map ? iterable : new Map(iterable);
|
|
10
10
|
}
|
|
11
|
+
/** Function that lets new items in a map be created and updated by calling a `reduce()` callback that receives the existing value. */
|
|
12
|
+
export function reduceMapItem(map, key, reduce, ...args) {
|
|
13
|
+
const existing = map.get(key);
|
|
14
|
+
const next = reduce(existing, ...args);
|
|
15
|
+
if (existing !== next)
|
|
16
|
+
map.set(key, next);
|
|
17
|
+
return next;
|
|
18
|
+
}
|