shelving 1.88.0 → 1.89.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/db/ItemState.d.ts CHANGED
@@ -14,7 +14,7 @@ export declare class ItemState<T extends Datas, K extends DataKey<T> = DataKey<T
14
14
  constructor(ref: Item<T, K> | AsyncItem<T, K>);
15
15
  /** Refresh this state from the source provider. */
16
16
  readonly refresh: () => void;
17
- _refresh(): Promise<void>;
17
+ private _refresh;
18
18
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
19
19
  refreshStale(maxAge: number): void;
20
20
  /** Subscribe this state to the source provider. */
@@ -27,7 +27,7 @@ export declare class QueryState<T extends Datas, K extends DataKey<T> = DataKey<
27
27
  constructor(ref: Query<T, K> | AsyncQuery<T, K>);
28
28
  /** Refresh this state from the source provider. */
29
29
  readonly refresh: () => void;
30
- _refresh(): Promise<void>;
30
+ private _refresh;
31
31
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
32
32
  refreshStale(maxAge: number): void;
33
33
  /** Subscribe this state to the source provider. */
@@ -37,7 +37,7 @@ export declare class QueryState<T extends Datas, K extends DataKey<T> = DataKey<
37
37
  * - Promise that needs to be handled.
38
38
  */
39
39
  readonly loadMore: () => void;
40
- _loadMore(): Promise<void>;
40
+ private _loadMore;
41
41
  /** Iterate over the items. */
42
42
  [Symbol.iterator](): Iterator<ItemData<T[K]>>;
43
43
  }
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.88.0",
14
+ "version": "1.89.1",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -0,0 +1,15 @@
1
+ /// <reference types="react" />
2
+ /**
3
+ * Interface for a cache controller.
4
+ * - Cache is a `Map` indexed by strings that can be used to store any value.
5
+ */
6
+ export interface CacheController<T> {
7
+ /** Wrap around a set of elements to provide the cache to them. */
8
+ readonly Cache: ({ children }: {
9
+ children: React.ReactNode;
10
+ }) => React.ReactElement;
11
+ /** Use the current cache map in a component. */
12
+ readonly useCache: () => Map<string, T>;
13
+ }
14
+ /** Create a cache. */
15
+ export declare function createCache<T>(): CacheController<T>;
@@ -0,0 +1,21 @@
1
+ import { useContext, createContext, createElement } from "react";
2
+ import { ConditionError } from "../error/ConditionError.js";
3
+ import { useReduce } from "./useReduce.js";
4
+ /** Reducer that gets an existing `Map` instance or creates a new one. */
5
+ const _reduceMap = (previous) => previous || new Map();
6
+ /** Create a cache. */
7
+ export function createCache() {
8
+ const context = createContext(undefined);
9
+ return {
10
+ useCache: () => {
11
+ const cache = useContext(context);
12
+ if (!cache)
13
+ throw new ConditionError("useCache() must be used inside <Cache>");
14
+ return cache;
15
+ },
16
+ Cache: ({ children }) => {
17
+ const cache = useReduce(_reduceMap);
18
+ return createElement(context.Provider, { children, value: cache });
19
+ },
20
+ };
21
+ }
package/react/index.d.ts CHANGED
@@ -1,7 +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
+ export * from "./createCache.js";
5
5
  export * from "./useSequence.js";
6
6
  export * from "./useState.js";
7
7
  export * from "./useData.js";
package/react/index.js CHANGED
@@ -2,7 +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
+ export * from "./createCache.js";
6
6
  // Integration.
7
7
  export * from "./useSequence.js";
8
8
  export * from "./useState.js";
@@ -1,15 +1,18 @@
1
+ /// <reference types="react" />
1
2
  import type { ImmutableArray } from "../util/array.js";
2
- import { DataKey, Datas } from "../util/data.js";
3
3
  import { AsyncItem } from "../db/Item.js";
4
4
  import { ItemState } from "../db/ItemState.js";
5
5
  import { AsyncQuery } from "../db/Query.js";
6
6
  import { QueryState } from "../db/QueryState.js";
7
- type Reference<T extends Datas, K extends DataKey<T>> = AsyncItem<T, K> | AsyncQuery<T, K>;
8
- type ReferenceReferenceState<T extends Reference<Datas, string>> = T extends AsyncItem<infer D, infer K> ? ItemState<D, K> : T extends AsyncQuery<infer D, infer K> ? QueryState<D, K> : never;
7
+ type AnyRef = AsyncItem<any, any> | AsyncQuery<any, any>;
8
+ type RefToState<T> = T extends undefined ? undefined : T extends AsyncItem<infer D, infer K> ? ItemState<D, K> : T extends AsyncQuery<infer D, infer K> ? QueryState<D, K> : never;
9
9
  /** Use one or more data items or queries. */
10
- export declare function useData<T extends Reference<Datas, string>>(ref: T): ReferenceReferenceState<T>;
11
- export declare function useData<T extends Reference<Datas, string>>(ref: T | undefined): ReferenceReferenceState<T> | undefined;
12
- export declare function useData<T extends ImmutableArray<Reference<Datas, string>>>(...refs: T): {
13
- [K in keyof T]: ReferenceReferenceState<T[K]>;
10
+ export declare function useData<T extends AnyRef | undefined>(ref: T): RefToState<T>;
11
+ export declare function useData<T extends ImmutableArray<AnyRef | undefined>>(...refs: T): {
12
+ [K in keyof T]: RefToState<T[K]>;
14
13
  };
14
+ /** Wrap components with `<DataCache>` to allow the use of `useData()`. */
15
+ export declare const DataCache: ({ children }: {
16
+ children: import("react").ReactNode;
17
+ }) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
15
18
  export {};
package/react/useData.js CHANGED
@@ -4,14 +4,18 @@ import { AsyncItem } from "../db/Item.js";
4
4
  import { ItemState } from "../db/ItemState.js";
5
5
  import { QueryState } from "../db/QueryState.js";
6
6
  import { useState } from "./useState.js";
7
- import { useCache } from "./useCache.js";
7
+ import { createCache } from "./createCache.js";
8
+ /** Create a cache. */
9
+ const { Cache, useCache } = createCache();
8
10
  export function useData(...refs) {
9
11
  const cache = useCache();
10
- const states = mapArray(refs, _getReferenceState, cache);
12
+ const states = mapArray(refs, _getRefState, cache);
11
13
  return useState(...states);
12
14
  }
13
15
  /** Get the corresponding `ItemState` or `QueryState` instance from the cache for a given `Item` or `Query`. */
14
- function _getReferenceState(ref, cache) {
16
+ function _getRefState(ref, cache) {
15
17
  const key = ref === null || ref === void 0 ? void 0 : ref.toString();
16
18
  return ref && key ? cache.get(key) || setMapItem(cache, key, ref instanceof AsyncItem ? new ItemState(ref) : new QueryState(ref)) : undefined;
17
19
  }
20
+ /** Wrap components with `<DataCache>` to allow the use of `useData()`. */
21
+ export const DataCache = Cache;
package/util/data.d.ts CHANGED
@@ -36,9 +36,12 @@ export declare function assertData<T extends Data>(value: T | unknown): asserts
36
36
  export declare const isDataProp: <T extends Data>(obj: T, key: unknown) => key is DataKey<T>;
37
37
  /** Get the data of a result (returns data or throws `RequiredError` if value is `null` or `undefined`). */
38
38
  export declare function getData<T extends Data>(result: OptionalData<T>): T;
39
- /** Get the props of a data object. */
39
+ /** Get the props of a data object as a set of entries. */
40
40
  export declare function getDataProps<T extends Data>(data: T): ImmutableArray<DataProp<T>>;
41
41
  export declare function getDataProps<T extends Data>(data: T | Partial<T>): ImmutableArray<DataProp<T>>;
42
+ /** Get the props of a data object as a set of entries. */
43
+ export declare function getDataKeys<T extends Data>(data: T): ImmutableArray<DataKey<T>>;
44
+ export declare function getDataKeys<T extends Data>(data: T | Partial<T>): ImmutableArray<DataKey<T>>;
42
45
  /** Type that represents an empty data object. */
43
46
  export type EmptyData = {
44
47
  readonly [K in never]: never;
package/util/data.js CHANGED
@@ -19,6 +19,9 @@ export function getData(result) {
19
19
  export function getDataProps(data) {
20
20
  return Object.entries(data);
21
21
  }
22
+ export function getDataKeys(data) {
23
+ return Object.keys(data);
24
+ }
22
25
  /** An empty object. */
23
26
  export const EMPTY_DATA = { __proto__: null };
24
27
  /** Function that returns an an empty object. */
package/util/object.d.ts CHANGED
@@ -67,6 +67,10 @@ export type OmitProps<T, TT> = Omit<T, {
67
67
  export declare function getProps<T extends ImmutableObject>(obj: T): ImmutableArray<ObjectProp<T>>;
68
68
  export declare function getProps<T extends ImmutableObject>(obj: T | Partial<T>): ImmutableArray<ObjectProp<T>>;
69
69
  export declare function getProps<T extends ImmutableObject>(obj: T | Partial<T> | Iterable<ObjectProp<T>>): Iterable<ObjectProp<T>>;
70
+ /** Get the keys of an object as an array. */
71
+ export declare function getKeys<T extends ImmutableObject>(obj: T): ImmutableArray<ObjectKey<T>>;
72
+ export declare function getKeys<T extends ImmutableObject>(obj: T | Partial<T>): ImmutableArray<ObjectKey<T>>;
73
+ export declare function getKeys<T extends ImmutableObject>(obj: T | Partial<T> | Iterable<ObjectKey<T>>): Iterable<ObjectKey<T>>;
70
74
  /**
71
75
  * Extract the value of a named prop from an object.
72
76
  * - Extraction is possibly deep if deeper keys are specified.
package/util/object.js CHANGED
@@ -26,6 +26,9 @@ export const isProp = (obj, key) => Object.prototype.hasOwnProperty.call(obj, ke
26
26
  export function getProps(obj) {
27
27
  return isIterable(obj) ? obj : Object.entries(obj);
28
28
  }
29
+ export function getKeys(obj) {
30
+ return isIterable(obj) ? obj : Object.keys(obj);
31
+ }
29
32
  export function getProp(obj, key, ...keys) {
30
33
  const value = obj[key];
31
34
  return !isArrayLength(keys) ? value : isObject(value) ? getProp(value, ...keys) : undefined;
package/util/string.d.ts CHANGED
@@ -32,7 +32,7 @@ export declare const isStringLength: (str: string, min?: number, max?: number) =
32
32
  /** Assert that a value has a specific length (or length is in a specific range). */
33
33
  export declare function assertStringLength(str: string | unknown, min?: number, max?: number): asserts str is string;
34
34
  /** Get a string if it has the specified minimum length. */
35
- export declare function getStringLength(arr: string, min?: number, max?: number): string;
35
+ export declare function getStringLength(str: string, min?: number, max?: number): string;
36
36
  /** Concatenate an iterable set of strings together. */
37
37
  export declare const joinStrings: (strs: Iterable<string> & NotString, joiner?: string) => string;
38
38
  /**
@@ -63,16 +63,17 @@ export declare const sanitizeLines: (str: string) => string;
63
63
  * - Used when you're running a query against a string entered by a user.
64
64
  *
65
65
  * @example normalizeString("Däve-is\nREALLY éxcitable—apparęntly!!! 😂"); // Returns "dave is really excitable apparently"
66
- */
67
- export declare const simplifyString: (str: string) => string;
68
- /**
69
- * Convert a string to a `kebab-case` URL slug.
70
- * - Remove any characters not in the range `[a-z0-9-]`
71
- * - Change all spaces/separators/hyphens/dashes/underscores to `-` single hyphen.
72
66
  *
73
- * Note: this splits words based on spaces, so won't work well with logographic writing systems e.g. kanji.
67
+ * @todo Convert letter-like characters (e.g. `ℝ`) to their ASCII equivalent (e.g. `R`).
74
68
  */
75
- export declare const getSlug: (str: string) => string;
69
+ export declare const simplifyString: (str: string) => string;
70
+ /** Convert a string to a `kebab-case` URL slug, or throw `AssertionError` */
71
+ export declare const getOptionalSlug: (str: string) => string | null;
72
+ export declare function getSlug(str: string): string;
73
+ /** Convert a string to a unique ref e.g. `abc123`, or `null` */
74
+ export declare const getOptionalRef: (str: string) => string | null;
75
+ /** Convert a string to a unique ref e.g. `abc123`, or throw `AssertionError` */
76
+ export declare function getRef(str: string): string;
76
77
  /**
77
78
  * Return an array of the separate words and "quoted phrases" found in a string.
78
79
  * - Phrases enclosed "in quotes" are a single word.
package/util/string.js CHANGED
@@ -53,9 +53,9 @@ export function assertStringLength(str, min = 1, max = Infinity) {
53
53
  throw new AssertionError(`Must be string with length ${formatRange(min, max)}`, str);
54
54
  }
55
55
  /** Get a string if it has the specified minimum length. */
56
- export function getStringLength(arr, min = 1, max = Infinity) {
57
- assertStringLength(arr, min, max);
58
- return arr;
56
+ export function getStringLength(str, min = 1, max = Infinity) {
57
+ assertStringLength(str, min, max);
58
+ return str;
59
59
  }
60
60
  /** Concatenate an iterable set of strings together. */
61
61
  export const joinStrings = (strs, joiner = "") => getArray(strs).join(joiner);
@@ -99,6 +99,8 @@ export const sanitizeLines = (str) => str
99
99
  * - Used when you're running a query against a string entered by a user.
100
100
  *
101
101
  * @example normalizeString("Däve-is\nREALLY éxcitable—apparęntly!!! 😂"); // Returns "dave is really excitable apparently"
102
+ *
103
+ * @todo Convert letter-like characters (e.g. `ℝ`) to their ASCII equivalent (e.g. `R`).
102
104
  */
103
105
  export const simplifyString = (str) => str
104
106
  .normalize("NFD") // Convert ligatures (e.g. `ff`) and letters with marks (e.g. `ü`) to separate characters (e.g. `ff` and `u◌̈`)`.
@@ -106,14 +108,24 @@ export const simplifyString = (str) => str
106
108
  .replace(/[^\p{L}\p{N} ]+/gu, "") // Strip characters that aren't letters, numbers, spaces.
107
109
  .trim()
108
110
  .toLowerCase();
109
- /**
110
- * Convert a string to a `kebab-case` URL slug.
111
- * - Remove any characters not in the range `[a-z0-9-]`
112
- * - Change all spaces/separators/hyphens/dashes/underscores to `-` single hyphen.
113
- *
114
- * Note: this splits words based on spaces, so won't work well with logographic writing systems e.g. kanji.
115
- */
116
- export const getSlug = (str) => simplifyString(str).replace(/ /g, "-");
111
+ /** Convert a string to a `kebab-case` URL slug, or throw `AssertionError` */
112
+ export const getOptionalSlug = (str) => simplifyString(str).replace(/ /g, "-") || null;
113
+ /* Convert a string to a `kebab-case` URL slug, or throw `AssertionError` */
114
+ export function getSlug(str) {
115
+ const slug = getOptionalSlug(str);
116
+ if (slug)
117
+ return slug;
118
+ throw new AssertionError(`String slug cannot be empty (received "${str}")`);
119
+ }
120
+ /** Convert a string to a unique ref e.g. `abc123`, or `null` */
121
+ export const getOptionalRef = (str) => simplifyString(str).replace(/ /g, "") || null;
122
+ /** Convert a string to a unique ref e.g. `abc123`, or throw `AssertionError` */
123
+ export function getRef(str) {
124
+ const ref = getOptionalRef(str);
125
+ if (ref)
126
+ return ref;
127
+ throw new AssertionError(`String ref cannot be empty (received "${str}")`);
128
+ }
117
129
  /**
118
130
  * Return an array of the separate words and "quoted phrases" found in a string.
119
131
  * - Phrases enclosed "in quotes" are a single word.
@@ -1,46 +0,0 @@
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 you want to cache a completely independent set of items without interference.
19
- */
20
- export declare const CACHE: CacheController<any>;
21
- /**
22
- * Use a global cache in a component.
23
- * - Throws an error if used outside of `<Cache>`
24
- */
25
- export declare const useCache: <T>() => Map<string, T>;
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
- */
44
- export declare const Cache: ({ children }: {
45
- children: React.ReactNode;
46
- }) => React.ReactElement | null;
package/react/useCache.js DELETED
@@ -1,56 +0,0 @@
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 you want to cache a completely independent set of items without interference.
31
- */
32
- export const CACHE = new CacheController(); // eslint-disable-line @typescript-eslint/no-explicit-any
33
- /**
34
- * Use a global cache in a component.
35
- * - Throws an error if used outside of `<Cache>`
36
- */
37
- export const useCache = CACHE.useCache;
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
- */
56
- export const Cache = CACHE.Cache;