shelving 1.68.0 → 1.68.3

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.
Files changed (59) hide show
  1. package/feedback/Feedback.js +2 -2
  2. package/firestore/client/FirestoreClientProvider.d.ts +1 -1
  3. package/firestore/client/FirestoreClientProvider.js +7 -3
  4. package/firestore/lite/FirestoreLiteProvider.js +6 -2
  5. package/firestore/server/FirestoreServerProvider.d.ts +1 -1
  6. package/firestore/server/FirestoreServerProvider.js +7 -3
  7. package/index.js +3 -0
  8. package/package.json +3 -2
  9. package/provider/ThroughProvider.d.ts +4 -2
  10. package/provider/ThroughProvider.js +11 -3
  11. package/provider/ValidationProvider.js +2 -3
  12. package/react/index.d.ts +1 -0
  13. package/react/index.js +1 -0
  14. package/react/useCache.d.ts +23 -3
  15. package/react/useCache.js +23 -3
  16. package/react/useDocument.d.ts +6 -2
  17. package/react/useDocument.js +23 -21
  18. package/react/useQuery.d.ts +8 -3
  19. package/react/useQuery.js +41 -37
  20. package/schema/AllowSchema.js +4 -4
  21. package/schema/ArraySchema.js +3 -3
  22. package/schema/DateSchema.js +4 -4
  23. package/schema/NumberSchema.js +2 -2
  24. package/schema/SlugSchema.js +2 -2
  25. package/schema/ThroughSchema.d.ts +4 -2
  26. package/schema/ThroughSchema.js +10 -4
  27. package/update/ArrayUpdate.d.ts +2 -1
  28. package/update/ArrayUpdate.js +22 -3
  29. package/update/DataUpdate.d.ts +3 -2
  30. package/update/DataUpdate.js +42 -2
  31. package/update/Delete.d.ts +11 -0
  32. package/update/Delete.js +15 -0
  33. package/update/Increment.d.ts +5 -5
  34. package/update/Increment.js +5 -5
  35. package/update/ObjectUpdate.d.ts +8 -8
  36. package/update/ObjectUpdate.js +38 -18
  37. package/update/Update.d.ts +4 -1
  38. package/update/Update.js +6 -0
  39. package/update/hydrations.js +3 -1
  40. package/update/index.d.ts +2 -2
  41. package/update/index.js +2 -2
  42. package/util/color.d.ts +6 -2
  43. package/util/color.js +17 -11
  44. package/util/constants.d.ts +0 -2
  45. package/util/constants.js +0 -2
  46. package/util/data.d.ts +6 -6
  47. package/util/data.js +3 -3
  48. package/util/date.d.ts +5 -5
  49. package/util/date.js +19 -19
  50. package/util/number.d.ts +3 -3
  51. package/util/number.js +5 -5
  52. package/util/search.js +2 -2
  53. package/util/string.d.ts +4 -4
  54. package/util/string.js +7 -7
  55. package/util/transform.js +2 -2
  56. package/util/validate.d.ts +9 -0
  57. package/util/validate.js +13 -2
  58. package/update/util.d.ts +0 -7
  59. package/update/util.js +0 -44
@@ -1,5 +1,5 @@
1
1
  import { debug } from "../util/debug.js";
2
- import { toTitle } from "../util/string.js";
2
+ import { getTitle } from "../util/string.js";
3
3
  /**
4
4
  * The `Feedback` class represents a feedback message that should be shown to the user.
5
5
  * - Basic `Feedback` is neither good nor bad, `SuccessFeedback` indicates good news, and `ErrorFeedback` indicates bad news.
@@ -27,7 +27,7 @@ export class Feedback {
27
27
  if (v instanceof Feedback)
28
28
  messages[k] = v.feedback;
29
29
  else
30
- messages[k] = toTitle(v);
30
+ messages[k] = getTitle(v);
31
31
  }
32
32
  return messages;
33
33
  }
@@ -2,9 +2,9 @@ import type { Firestore } from "firebase/firestore";
2
2
  import type { DocumentReference, QueryReference } from "../../db/Reference.js";
3
3
  import type { Data, Entities, OptionalEntity } from "../../util/data.js";
4
4
  import type { Unsubscribe } from "../../observe/Observable.js";
5
+ import { Observer } from "../../observe/Observer.js";
5
6
  import { AsynchronousProvider, Provider } from "../../provider/Provider.js";
6
7
  import { DataUpdate } from "../../update/DataUpdate.js";
7
- import { Observer } from "../../observe/Observer.js";
8
8
  /**
9
9
  * Firestore client database provider.
10
10
  * - Works with the Firebase JS SDK.
@@ -1,11 +1,13 @@
1
1
  import { orderBy as firestoreOrderBy, where as firestoreWhere, limit as firestoreLimit, increment as firestoreIncrement, arrayUnion as firestoreArrayUnion, arrayRemove as firestoreArrayRemove, deleteField as firestoreDeleteField, collection as firestoreCollection, doc as firestoreDocument, query as firestoreQuery, onSnapshot, addDoc, setDoc, updateDoc, deleteDoc, getDoc, getDocs, } from "firebase/firestore";
2
+ import { dispatchError, dispatchNext } from "../../observe/Observer.js";
2
3
  import { UnsupportedError } from "../../error/UnsupportedError.js";
3
4
  import { Provider } from "../../provider/Provider.js";
4
5
  import { ArrayUpdate } from "../../update/ArrayUpdate.js";
5
6
  import { DataUpdate } from "../../update/DataUpdate.js";
6
7
  import { Increment } from "../../update/Increment.js";
7
8
  import { ObjectUpdate } from "../../update/ObjectUpdate.js";
8
- import { dispatchError, dispatchNext } from "../../observe/Observer.js";
9
+ import { Delete } from "../../update/Delete.js";
10
+ import { Update } from "../../update/Update.js";
9
11
  // Constants.
10
12
  // const ID = "__name__"; // DH: `__name__` is the entire path of the document. `__id__` is just ID.
11
13
  const ID = "__id__"; // Internal way Firestore Queries can reference the ID of the current document.
@@ -60,7 +62,9 @@ function getOptionalEntity(snapshot) {
60
62
  /** Convert `Update` instances into corresponding Firestore `FieldValue` instances. */
61
63
  function* yieldFieldValues(updates, prefix = "") {
62
64
  for (const [key, update] of updates) {
63
- if (update === undefined)
65
+ if (!(update instanceof Update))
66
+ yield [`${prefix}${key}`, update];
67
+ else if (update instanceof Delete)
64
68
  yield [`${prefix}${key}`, firestoreDeleteField()];
65
69
  else if (update instanceof Increment)
66
70
  yield [`${prefix}${key}`, firestoreIncrement(update.amount)];
@@ -75,7 +79,7 @@ function* yieldFieldValues(updates, prefix = "") {
75
79
  yield [`${prefix}${key}`, firestoreArrayRemove(...update.deletes)];
76
80
  }
77
81
  else
78
- yield [`${prefix}${key}`, update];
82
+ yield [`${prefix}${key}`, update.transform()];
79
83
  }
80
84
  }
81
85
  /**
@@ -5,6 +5,8 @@ import { ArrayUpdate } from "../../update/ArrayUpdate.js";
5
5
  import { DataUpdate } from "../../update/DataUpdate.js";
6
6
  import { Increment } from "../../update/Increment.js";
7
7
  import { ObjectUpdate } from "../../update/ObjectUpdate.js";
8
+ import { Update } from "../../update/Update.js";
9
+ import { Delete } from "../../update/Delete.js";
8
10
  // Constants.
9
11
  // const ID = "__name__"; // DH: `__name__` is the entire path of the document. `__id__` is just ID.
10
12
  const ID = "__id__"; // Internal way Firestore Queries can reference the ID of the current document.
@@ -59,7 +61,9 @@ function getOptionalData(snapshot) {
59
61
  /** Convert `Update` instances into corresponding Firestore `FieldValue` instances. */
60
62
  function* yieldFieldValues(updates, prefix = "") {
61
63
  for (const [key, update] of updates) {
62
- if (update === undefined)
64
+ if (!(update instanceof Update))
65
+ yield [`${prefix}${key}`, update];
66
+ else if (update instanceof Delete)
63
67
  yield [`${prefix}${key}`, firestoreDeleteField()];
64
68
  else if (update instanceof Increment)
65
69
  yield [`${prefix}${key}`, firestoreIncrement(update.amount)];
@@ -74,7 +78,7 @@ function* yieldFieldValues(updates, prefix = "") {
74
78
  yield [`${prefix}${key}`, firestoreArrayRemove(...update.deletes)];
75
79
  }
76
80
  else
77
- yield [`${prefix}${key}`, update];
81
+ yield [`${prefix}${key}`, update.transform()];
78
82
  }
79
83
  }
80
84
  /**
@@ -2,9 +2,9 @@ import { Firestore } from "@google-cloud/firestore";
2
2
  import type { DocumentReference, QueryReference } from "../../db/Reference.js";
3
3
  import type { Data, Entities, OptionalEntity } from "../../util/data.js";
4
4
  import type { Unsubscribe } from "../../observe/Observable.js";
5
+ import { Observer } from "../../observe/Observer.js";
5
6
  import { AsynchronousProvider, Provider } from "../../provider/Provider.js";
6
7
  import { DataUpdate } from "../../update/DataUpdate.js";
7
- import { Observer } from "../../observe/Observer.js";
8
8
  /**
9
9
  * Firestore server database provider.
10
10
  * - Works with the Firebase Admin SDK for Node.JS
@@ -1,11 +1,13 @@
1
1
  import { Firestore, FieldValue } from "@google-cloud/firestore";
2
+ import { dispatchError, dispatchNext } from "../../observe/Observer.js";
2
3
  import { UnsupportedError } from "../../error/UnsupportedError.js";
3
4
  import { Provider } from "../../provider/Provider.js";
4
5
  import { ArrayUpdate } from "../../update/ArrayUpdate.js";
5
6
  import { DataUpdate } from "../../update/DataUpdate.js";
6
7
  import { Increment } from "../../update/Increment.js";
7
8
  import { ObjectUpdate } from "../../update/ObjectUpdate.js";
8
- import { dispatchError, dispatchNext } from "../../observe/Observer.js";
9
+ import { Delete } from "../../update/Delete.js";
10
+ import { Update } from "../../update/Update.js";
9
11
  // Constants.
10
12
  // const ID = "__name__"; // DH: `__name__` is the entire path of the document. `__id__` is just ID.
11
13
  const ID = "__id__"; // Internal way Firestore Queries can reference the ID of the current document.
@@ -60,7 +62,9 @@ function getOptionalEntity(snapshot) {
60
62
  /** Convert `Update` instances into corresponding Firestore `FieldValue` instances. */
61
63
  function* yieldFieldValues(updates, prefix = "") {
62
64
  for (const [key, update] of updates) {
63
- if (update === undefined)
65
+ if (!(update instanceof Update))
66
+ yield [`${prefix}${key}`, update];
67
+ else if (update instanceof Delete)
64
68
  yield [`${prefix}${key}`, FieldValue.delete()];
65
69
  else if (update instanceof Increment)
66
70
  yield [`${prefix}${key}`, FieldValue.increment(update.amount)];
@@ -75,7 +79,7 @@ function* yieldFieldValues(updates, prefix = "") {
75
79
  yield [`${prefix}${key}`, FieldValue.arrayRemove(...update.deletes)];
76
80
  }
77
81
  else
78
- yield [`${prefix}${key}`, update];
82
+ yield [`${prefix}${key}`, update.transform()];
79
83
  }
80
84
  }
81
85
  /**
package/index.js CHANGED
@@ -19,5 +19,8 @@ export * from "./update/index.js";
19
19
  export * from "./util/index.js";
20
20
  // Integrations.
21
21
  // export * from "./react/index.js"; // Not exported.
22
+ // export * from "./firestore/client/index.js"; // Not exported.
23
+ // export * from "./firestore/lite/index.js"; // Not exported.
24
+ // export * from "./firestore/server/index.js"; // Not exported.
22
25
  // Testing.
23
26
  // export * from "./test/index.js"; // Not exported.
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.68.0",
14
+ "version": "1.68.3",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -29,11 +29,12 @@
29
29
  "./firestore/lite": "./firestore/lite/index.js",
30
30
  "./firestore/server": "./firestore/server/index.js",
31
31
  "./markup": "./markup/index.js",
32
+ "./observe": "./observe/index.js",
32
33
  "./provider": "./provider/index.js",
33
34
  "./query": "./query/index.js",
34
35
  "./react": "./react/index.js",
35
36
  "./schema": "./schema/index.js",
36
- "./stream": "./stream/index.js",
37
+ "./state": "./state/index.js",
37
38
  "./test": "./test/index.js",
38
39
  "./update": "./update/index.js",
39
40
  "./util": "./util/index.js"
@@ -31,5 +31,7 @@ export interface SynchronousThroughProvider extends SynchronousProvider {
31
31
  export interface AsynchronousThroughProvider extends AsynchronousProvider {
32
32
  new (source: AsynchronousProvider): AsynchronousProvider;
33
33
  }
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 | undefined;
34
+ /** Find a possible source provider in a database's provider stack (if it exists). */
35
+ export declare function getOptionalSourceProvider<P extends Provider>(provider: Provider, type: Class<P>): P | undefined;
36
+ /** Find a source provider in a database's provider stack. */
37
+ export declare function getSourceProvider<P extends Provider>(provider: Provider, type: Class<P>): P;
@@ -1,3 +1,4 @@
1
+ import { AssertionError } from "../error/AssertionError.js";
1
2
  import { Provider } from "./Provider.js";
2
3
  /**
3
4
  * Pass all reads and writes through to a source provider.
@@ -41,10 +42,17 @@ export class ThroughProvider extends Provider {
41
42
  return this.source.deleteQuery(ref);
42
43
  }
43
44
  }
44
- /** Find a specific source provider in a database's provider stack. */
45
- export function findSourceProvider(provider, type) {
45
+ /** Find a possible source provider in a database's provider stack (if it exists). */
46
+ export function getOptionalSourceProvider(provider, type) {
46
47
  if (provider instanceof type)
47
48
  return provider;
48
49
  if (provider instanceof ThroughProvider)
49
- return findSourceProvider(provider.source, type);
50
+ return getSourceProvider(provider.source, type);
51
+ }
52
+ /** Find a source provider in a database's provider stack. */
53
+ export function getSourceProvider(provider, type) {
54
+ const source = getOptionalSourceProvider(provider, type);
55
+ if (!source)
56
+ throw new AssertionError(`Source provider ${type.name} not found`, provider);
57
+ return source;
50
58
  }
@@ -1,6 +1,5 @@
1
1
  import { callAsync } from "../util/async.js";
2
2
  import { validate } from "../util/validate.js";
3
- import { validateUpdate } from "../update/util.js";
4
3
  import { Feedback } from "../feedback/Feedback.js";
5
4
  import { ValidationError } from "../error/ValidationError.js";
6
5
  import { InvalidFeedback } from "../feedback/InvalidFeedback.js";
@@ -21,7 +20,7 @@ export class ValidationProvider extends ThroughProvider {
21
20
  return super.setDocument(ref, validate(value, ref.validator));
22
21
  }
23
22
  updateDocument(ref, update) {
24
- return super.updateDocument(ref, validateUpdate(update, ref.validator));
23
+ return super.updateDocument(ref, update.validate(ref.validator));
25
24
  }
26
25
  getQuery(ref) {
27
26
  return callAsync(_validateEntities, super.getQuery(ref), ref);
@@ -33,7 +32,7 @@ export class ValidationProvider extends ThroughProvider {
33
32
  return super.setQuery(ref, validate(value, ref.validator));
34
33
  }
35
34
  updateQuery(ref, update) {
36
- return super.updateQuery(ref, validateUpdate(update, ref.validator));
35
+ return super.updateQuery(ref, update.validate(ref.validator));
37
36
  }
38
37
  }
39
38
  /** Validate an entity for a document reference. */
package/react/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./useLazy.js";
2
2
  export * from "./useReduce.js";
3
3
  export * from "./useInstance.js";
4
+ export * from "./useCache.js";
4
5
  export * from "./useSubscribe.js";
5
6
  export * from "./useDocument.js";
6
7
  export * from "./useQuery.js";
package/react/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  export * from "./useLazy.js";
3
3
  export * from "./useReduce.js";
4
4
  export * from "./useInstance.js";
5
+ export * from "./useCache.js";
5
6
  // Observables.
6
7
  export * from "./useSubscribe.js";
7
8
  // DB.
@@ -15,12 +15,32 @@ export declare class CacheController<T> {
15
15
  /**
16
16
  * Default cache
17
17
  * - This is a flexible generic cache intended to be the default.
18
- * - Use this cache unless
18
+ * - Use this cache unless you want to cache a completely independent set of items without interference.
19
19
  */
20
20
  export declare const CACHE: CacheController<any>;
21
- /** Use a global cache in a component. */
21
+ /**
22
+ * Use a global cache in a component.
23
+ * - Throws an error if used outside of `<Cache>`
24
+ */
22
25
  export declare const useCache: () => Map<string, any>;
23
- /** Component that provides a global cache to its children. */
26
+ /**
27
+ * Component that provides a global cache to its children.
28
+ *
29
+ * Note: If mounted globally this cache will bloat over time, so you need a strategy to clear or reset the cache occasionally.
30
+ *
31
+ * A good strategy is to wrap a separate `<Cache>` around each page of your app.
32
+ * This means the cache can only grow to the size of each page and the memory is released when the user navigates to a new page.
33
+ * You might need to use `<Cache key="something unique to the page">` to ensure the cache component is destroyed and remounted for each page.
34
+ *
35
+ * Put a `<Suspense>` boundary _inside_ `<Cache>`
36
+ * - This prevents promises being thrown up through the cache causing it to be destroyed.
37
+ * - When the promise resolves and the render is tried again the data would not exist (because the cache was destroyed).
38
+ * - This will cause an infinite loading loop.
39
+ *
40
+ * Put your error boundary _outside_ your `<Cache>`
41
+ * - The error being thrown up through the cache causes it to be destroyed.
42
+ * - This means when the uses tells the error boundary to try again (if supported) all data on the page will be retried.
43
+ */
24
44
  export declare const Cache: ({ children }: {
25
45
  children: React.ReactNode;
26
46
  }) => React.ReactElement | null;
package/react/useCache.js CHANGED
@@ -27,10 +27,30 @@ const _reduceMap = (previous) => previous || new Map();
27
27
  /**
28
28
  * Default cache
29
29
  * - This is a flexible generic cache intended to be the default.
30
- * - Use this cache unless
30
+ * - Use this cache unless you want to cache a completely independent set of items without interference.
31
31
  */
32
32
  export const CACHE = new CacheController(); // eslint-disable-line @typescript-eslint/no-explicit-any
33
- /** Use a global cache in a component. */
33
+ /**
34
+ * Use a global cache in a component.
35
+ * - Throws an error if used outside of `<Cache>`
36
+ */
34
37
  export const useCache = CACHE.useCache;
35
- /** Component that provides a global cache to its children. */
38
+ /**
39
+ * Component that provides a global cache to its children.
40
+ *
41
+ * Note: If mounted globally this cache will bloat over time, so you need a strategy to clear or reset the cache occasionally.
42
+ *
43
+ * A good strategy is to wrap a separate `<Cache>` around each page of your app.
44
+ * This means the cache can only grow to the size of each page and the memory is released when the user navigates to a new page.
45
+ * You might need to use `<Cache key="something unique to the page">` to ensure the cache component is destroyed and remounted for each page.
46
+ *
47
+ * Put a `<Suspense>` boundary _inside_ `<Cache>`
48
+ * - This prevents promises being thrown up through the cache causing it to be destroyed.
49
+ * - When the promise resolves and the render is tried again the data would not exist (because the cache was destroyed).
50
+ * - This will cause an infinite loading loop.
51
+ *
52
+ * Put your error boundary _outside_ your `<Cache>`
53
+ * - The error being thrown up through the cache causes it to be destroyed.
54
+ * - This means when the uses tells the error boundary to try again (if supported) all data on the page will be retried.
55
+ */
36
56
  export const Cache = CACHE.Cache;
@@ -13,7 +13,8 @@ export declare class DocumentState<T extends Data> extends State<OptionalEntity<
13
13
  get exists(): boolean;
14
14
  constructor(ref: DocumentReference<T>);
15
15
  /** Refresh this state from the source provider. */
16
- readonly refresh: () => Promise<void>;
16
+ readonly refresh: () => void;
17
+ _refresh(): Promise<void>;
17
18
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
18
19
  refreshStale(maxAge: number): void;
19
20
  /** Subscribe this state to the source provider. */
@@ -23,6 +24,9 @@ export declare class DocumentState<T extends Data> extends State<OptionalEntity<
23
24
  protected _addFirstObserver(): void;
24
25
  protected _removeLastObserver(): void;
25
26
  }
26
- /** Use a document in a React component. */
27
+ /**
28
+ * Use a document in a React component.
29
+ * - Uses the default cache, so will error if not used inside `<Cache>`
30
+ */
27
31
  export declare function useDocument<T extends Data>(ref: DocumentReference<T>): DocumentState<T>;
28
32
  export declare function useDocument<T extends Data>(ref?: DocumentReference<T>): DocumentState<T> | undefined;
@@ -1,7 +1,7 @@
1
1
  import { reduceMapItem } from "../util/map.js";
2
- import { getDocumentData, isSameReference } from "../db/Reference.js";
2
+ import { getDocumentData } from "../db/Reference.js";
3
3
  import { CacheProvider } from "../provider/CacheProvider.js";
4
- import { findSourceProvider } from "../provider/ThroughProvider.js";
4
+ import { getOptionalSourceProvider } from "../provider/ThroughProvider.js";
5
5
  import { State } from "../state/State.js";
6
6
  import { BooleanState } from "../state/BooleanState.js";
7
7
  import { ConditionError } from "../error/ConditionError.js";
@@ -12,34 +12,23 @@ import { useCache } from "./useCache.js";
12
12
  export class DocumentState extends State {
13
13
  constructor(ref) {
14
14
  var _a;
15
- const table = (_a = findSourceProvider(ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(ref);
15
+ const table = (_a = getOptionalSourceProvider(ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(ref);
16
16
  const time = table ? table.getDocumentTime(ref.id) : null;
17
17
  const isCached = typeof time === "number";
18
18
  super(table && isCached ? table.getDocument(ref.id) : NOVALUE);
19
19
  this.busy = new BooleanState();
20
20
  /** Refresh this state from the source provider. */
21
- this.refresh = async () => {
21
+ this.refresh = () => {
22
22
  if (this.closed)
23
23
  throw new ConditionError("State is closed");
24
- if (!this.busy.value) {
25
- this.busy.next(true);
26
- try {
27
- const result = await this.ref.value;
28
- this.next(result);
29
- }
30
- catch (thrown) {
31
- this.error(thrown);
32
- }
33
- finally {
34
- this.busy.next(false);
35
- }
36
- }
24
+ if (!this.busy.value)
25
+ void this._refresh();
37
26
  };
38
27
  this._time = time;
39
28
  this.ref = ref;
40
29
  // Queue a request to refresh the value if it doesn't exist.
41
30
  if (this.loading)
42
- void this.refresh();
31
+ this.refresh();
43
32
  }
44
33
  /** Get the data of the document (throws `RequiredError` if document doesn't exist). */
45
34
  get data() {
@@ -49,10 +38,23 @@ export class DocumentState extends State {
49
38
  get exists() {
50
39
  return !!this.value;
51
40
  }
41
+ async _refresh() {
42
+ this.busy.next(true);
43
+ try {
44
+ const result = await this.ref.value;
45
+ this.next(result);
46
+ }
47
+ catch (thrown) {
48
+ this.error(thrown);
49
+ }
50
+ finally {
51
+ this.busy.next(false);
52
+ }
53
+ }
52
54
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
53
55
  refreshStale(maxAge) {
54
56
  if (this.age > maxAge)
55
- void this.refresh();
57
+ this.refresh();
56
58
  }
57
59
  /** Subscribe this state to the source provider. */
58
60
  connectSource() {
@@ -61,7 +63,7 @@ export class DocumentState extends State {
61
63
  /** Subscribe this state to any `CacheProvider` that exists in the provider chain. */
62
64
  connectCache() {
63
65
  var _a;
64
- const table = (_a = findSourceProvider(this.ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(this.ref);
66
+ const table = (_a = getOptionalSourceProvider(this.ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(this.ref);
65
67
  table && this.connect(() => table.subscribeCachedDocument(this.ref.id, this));
66
68
  }
67
69
  // Override to subscribe to the cache when an observer is added.
@@ -75,7 +77,7 @@ export class DocumentState extends State {
75
77
  }
76
78
  }
77
79
  /** Reuse the previous `DocumentState` or create a new one. */
78
- const _reduceDocumentState = (existing, ref) => (existing && isSameReference(existing.ref, ref) ? existing : new DocumentState(ref));
80
+ const _reduceDocumentState = (existing, ref) => existing || new DocumentState(ref);
79
81
  export function useDocument(ref) {
80
82
  const cache = useCache();
81
83
  const state = ref ? reduceMapItem(cache, ref.toString(), _reduceDocumentState, ref) : undefined;
@@ -25,7 +25,8 @@ export declare class QueryState<T extends Data> extends State<Entities<T>> {
25
25
  get count(): number;
26
26
  constructor(ref: QueryReference<T>);
27
27
  /** Refresh this state from the source provider. */
28
- readonly refresh: () => Promise<void>;
28
+ readonly refresh: () => void;
29
+ _refresh(): Promise<void>;
29
30
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
30
31
  refreshStale(maxAge: number): void;
31
32
  /** Subscribe this state to the source provider. */
@@ -38,8 +39,12 @@ export declare class QueryState<T extends Data> extends State<Entities<T>> {
38
39
  * Load more items after the last once.
39
40
  * - Promise that needs to be handled.
40
41
  */
41
- readonly loadMore: () => Promise<void>;
42
+ readonly loadMore: () => void;
43
+ _loadMore(): Promise<void>;
42
44
  }
43
- /** Use a query in a React component. */
45
+ /**
46
+ * Use a query in a React component.
47
+ * - Uses the default cache, so will error if not used inside `<Cache>`
48
+ */
44
49
  export declare function useQuery<T extends Data>(ref: QueryReference<T>): QueryState<T>;
45
50
  export declare function useQuery<T extends Data>(ref?: QueryReference<T>): QueryState<T> | undefined;
package/react/useQuery.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { reduceMapItem } from "../util/map.js";
2
- import { getQueryFirstData, getQueryFirstValue, isSameReference } from "../db/Reference.js";
2
+ import { getQueryFirstData, getQueryFirstValue } from "../db/Reference.js";
3
3
  import { CacheProvider } from "../provider/CacheProvider.js";
4
- import { findSourceProvider } from "../provider/ThroughProvider.js";
4
+ import { getOptionalSourceProvider } from "../provider/ThroughProvider.js";
5
5
  import { State } from "../state/State.js";
6
6
  import { ConditionError } from "../error/ConditionError.js";
7
7
  import { BooleanState } from "../state/BooleanState.js";
@@ -12,59 +12,35 @@ import { useCache } from "./useCache.js";
12
12
  export class QueryState extends State {
13
13
  constructor(ref) {
14
14
  var _a, _b;
15
- const table = (_a = findSourceProvider(ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(ref);
15
+ const table = (_a = getOptionalSourceProvider(ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(ref);
16
16
  const time = table ? table.getQueryTime(ref) : null;
17
17
  const isCached = typeof time === "number";
18
18
  super(table && isCached ? table.getQuery(ref) : NOVALUE);
19
19
  this.busy = new BooleanState();
20
20
  this._hasMore = false;
21
21
  /** Refresh this state from the source provider. */
22
- this.refresh = async () => {
22
+ this.refresh = () => {
23
23
  if (this.closed)
24
24
  throw new ConditionError("State is closed");
25
- if (!this.busy.value) {
26
- this.busy.next(true);
27
- try {
28
- const result = await this.ref.value;
29
- this._hasMore = result.length < this.limit;
30
- this.next(result);
31
- }
32
- catch (thrown) {
33
- this.error(thrown);
34
- }
35
- finally {
36
- this.busy.next(false);
37
- }
38
- }
25
+ if (!this.busy.value)
26
+ void this._refresh();
39
27
  };
40
28
  /**
41
29
  * Load more items after the last once.
42
30
  * - Promise that needs to be handled.
43
31
  */
44
- this.loadMore = async () => {
32
+ this.loadMore = () => {
45
33
  if (this.closed)
46
34
  throw new ConditionError("State is closed");
47
- if (!this.busy.value) {
48
- this.busy.next(true);
49
- try {
50
- const items = await this.ref.after(this.lastData).value;
51
- this.next([...this.value, ...items]);
52
- this._hasMore = items.length < this.limit;
53
- }
54
- catch (thrown) {
55
- this.error(thrown);
56
- }
57
- finally {
58
- this.busy.next(false);
59
- }
60
- }
35
+ if (!this.busy.value)
36
+ void this._loadMore();
61
37
  };
62
38
  this._time = time;
63
39
  this.ref = ref;
64
40
  this.limit = (_b = ref.limit) !== null && _b !== void 0 ? _b : Infinity;
65
41
  // Queue a request to refresh the value if it doesn't exist.
66
42
  if (this.loading)
67
- void this.refresh();
43
+ this.refresh();
68
44
  }
69
45
  /** Can more items be loaded after the current result. */
70
46
  get hasMore() {
@@ -94,10 +70,24 @@ export class QueryState extends State {
94
70
  get count() {
95
71
  return this.value.length;
96
72
  }
73
+ async _refresh() {
74
+ this.busy.next(true);
75
+ try {
76
+ const result = await this.ref.value;
77
+ this._hasMore = result.length < this.limit;
78
+ this.next(result);
79
+ }
80
+ catch (thrown) {
81
+ this.error(thrown);
82
+ }
83
+ finally {
84
+ this.busy.next(false);
85
+ }
86
+ }
97
87
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
98
88
  refreshStale(maxAge) {
99
89
  if (this.age > maxAge)
100
- void this.refresh();
90
+ this.refresh();
101
91
  }
102
92
  /** Subscribe this state to the source provider. */
103
93
  connectSource() {
@@ -106,7 +96,7 @@ export class QueryState extends State {
106
96
  /** Subscribe this state to any `CacheProvider` that exists in the provider chain. */
107
97
  connectCache() {
108
98
  var _a;
109
- const table = (_a = findSourceProvider(this.ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(this.ref);
99
+ const table = (_a = getOptionalSourceProvider(this.ref.db.provider, CacheProvider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(this.ref);
110
100
  return table && this.connect(() => table.subscribeCachedQuery(this.ref, this));
111
101
  }
112
102
  // Override to subscribe to the cache when an observer is added.
@@ -118,9 +108,23 @@ export class QueryState extends State {
118
108
  // Disconnect all sources.
119
109
  this.disconnect();
120
110
  }
111
+ async _loadMore() {
112
+ this.busy.next(true);
113
+ try {
114
+ const items = await this.ref.after(this.lastData).value;
115
+ this.next([...this.value, ...items]);
116
+ this._hasMore = items.length < this.limit;
117
+ }
118
+ catch (thrown) {
119
+ this.error(thrown);
120
+ }
121
+ finally {
122
+ this.busy.next(false);
123
+ }
124
+ }
121
125
  }
122
126
  /** Reuse the previous `QueryState` or create a new one. */
123
- const _reduceQueryState = (existing, ref) => (existing && isSameReference(existing.ref, ref) ? existing : new QueryState(ref));
127
+ const _reduceQueryState = (existing, ref) => existing || new QueryState(ref);
124
128
  export function useQuery(ref) {
125
129
  const cache = useCache();
126
130
  const state = ref ? reduceMapItem(cache, ref.toString(), _reduceQueryState, ref) : undefined;
@@ -1,7 +1,7 @@
1
1
  import { InvalidFeedback } from "../feedback/InvalidFeedback.js";
2
- import { toString } from "../util/string.js";
2
+ import { getString } from "../util/string.js";
3
3
  import { isItem } from "../util/array.js";
4
- import { toNumber } from "../util/number.js";
4
+ import { getOptionalNumber } from "../util/number.js";
5
5
  import { isKey } from "../util/object.js";
6
6
  import { Schema } from "./Schema.js";
7
7
  /** Validate a value against a specific set of allowed values. */
@@ -27,13 +27,13 @@ export class AllowSchema extends Schema {
27
27
  /** Define a valid string from an allowed set of strings. */
28
28
  export class AllowStringSchema extends AllowSchema {
29
29
  validate(unsafeValue = this.value) {
30
- return validateAllowed(toString(unsafeValue), this.allow);
30
+ return validateAllowed(getString(unsafeValue), this.allow);
31
31
  }
32
32
  }
33
33
  /** Define a valid number from an allowed set of numbers. */
34
34
  export class AllowNumberSchema extends AllowSchema {
35
35
  validate(unsafeValue = this.value) {
36
- return validateAllowed(toNumber(unsafeValue), this.allow);
36
+ return validateAllowed(getOptionalNumber(unsafeValue), this.allow);
37
37
  }
38
38
  }
39
39
  /** Valid string from an allowed set of strings. */
@@ -1,6 +1,6 @@
1
1
  import { InvalidFeedback } from "../feedback/InvalidFeedback.js";
2
- import { getArray, uniqueArray } from "../util/array.js";
3
- import { validateItems } from "../util/validate.js";
2
+ import { uniqueArray } from "../util/array.js";
3
+ import { validateArray } from "../util/validate.js";
4
4
  import { Schema } from "./Schema.js";
5
5
  /**
6
6
  * Define a valid array.
@@ -41,7 +41,7 @@ export class ArraySchema extends Schema {
41
41
  validate(unsafeValue = this.value) {
42
42
  if (!(unsafeValue instanceof Array))
43
43
  throw new InvalidFeedback("Must be array", { value: unsafeValue });
44
- const safeArray = getArray(validateItems(unsafeValue, this.items));
44
+ const safeArray = validateArray(unsafeValue, this.items);
45
45
  const dedupedArray = this.unique ? uniqueArray(safeArray) : safeArray;
46
46
  if (typeof this.min === "number" && dedupedArray.length < this.min)
47
47
  throw new InvalidFeedback(dedupedArray.length ? `Minimum ${this.min} items` : "Required", { value: dedupedArray });