From c5e2342ca8da4ded0ddb042777ebb95cd4d9d5c0 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 23 Feb 2023 23:30:41 -0500 Subject: [PATCH] Reversed getCacheKey and loadData params --- .../src/examples/createCache/async.ts | 5 +-- .../src/examples/createCache/cache.ts | 9 ++---- .../src/examples/createCache/cacheWithKey.ts | 16 ++++++++++ .../src/examples/createCache/evict.ts | 4 +-- .../src/examples/createCache/hook.ts | 4 +-- .../src/examples/createCache/prefetch.ts | 4 +-- .../src/examples/createCache/suspense.ts | 5 +-- .../src/examples/createCache/sync.ts | 6 ++-- .../examples/createStreamingCache/cache.ts | 3 -- .../createStreamingCache/cacheWithKey.ts | 32 +++++++++++++++++++ .../suspense-website/src/examples/index.ts | 9 ++++++ .../src/routes/createCache.tsx | 17 +++++----- .../src/routes/createStreamingCache.tsx | 19 +++++------ .../src/suspense/ImportCache.ts | 29 ++++++++--------- .../src/suspense/SyntaxParsingCache.ts | 2 +- packages/suspense/CHANGELOG.md | 3 ++ .../suspense/src/cache/createCache.test.ts | 2 +- packages/suspense/src/cache/createCache.ts | 6 +++- .../src/cache/createStreamingCache.test.ts | 2 +- .../src/cache/createStreamingCache.ts | 6 +++- .../src/hooks/useCacheStatus.test.tsx | 2 +- .../src/hooks/useStreamingValues.test.tsx | 9 ++---- 22 files changed, 126 insertions(+), 68 deletions(-) create mode 100644 packages/suspense-website/src/examples/createCache/cacheWithKey.ts create mode 100644 packages/suspense-website/src/examples/createStreamingCache/cacheWithKey.ts diff --git a/packages/suspense-website/src/examples/createCache/async.ts b/packages/suspense-website/src/examples/createCache/async.ts index 54854ac..f59c4de 100644 --- a/packages/suspense-website/src/examples/createCache/async.ts +++ b/packages/suspense-website/src/examples/createCache/async.ts @@ -1,7 +1,8 @@ -import { exampleCache } from "./cache"; +import { userProfileCache } from "./cache"; // REMOVE_BEFORE async function load(userId: string) { - const userData = await exampleCache.fetchAsync(userId); + const userProfile = await userProfileCache.fetchAsync(userId); + // ... } diff --git a/packages/suspense-website/src/examples/createCache/cache.ts b/packages/suspense-website/src/examples/createCache/cache.ts index 9719e4d..03efc41 100644 --- a/packages/suspense-website/src/examples/createCache/cache.ts +++ b/packages/suspense-website/src/examples/createCache/cache.ts @@ -1,12 +1,9 @@ import { createCache } from "suspense"; -export const exampleCache = createCache<[userId: string], JSON>( - // Create unique key for params - (userId: string) => userId, - - // Load data for params +export const userProfileCache = createCache<[userId: string], JSON>( async (userId: string) => { const response = await fetch(`https://example.com/user?id=${userId}`); - return await response.json(); + const json = await response.json(); + return json; } ); diff --git a/packages/suspense-website/src/examples/createCache/cacheWithKey.ts b/packages/suspense-website/src/examples/createCache/cacheWithKey.ts new file mode 100644 index 0000000..7a26911 --- /dev/null +++ b/packages/suspense-website/src/examples/createCache/cacheWithKey.ts @@ -0,0 +1,16 @@ +import { createCache } from "suspense"; + +class ApiClient { + async loadData(id: string) { + return JSON.parse(""); + } +} + +// REMOVE_BEFORE +createCache<[client: ApiClient, id: string], JSON>( + // In this example, data is loaded by a "client" object + async (client: ApiClient, id: string) => client.loadData(id), + + // The id parameter is sufficiently unique to be the key + (client: ApiClient, id: string) => id +); diff --git a/packages/suspense-website/src/examples/createCache/evict.ts b/packages/suspense-website/src/examples/createCache/evict.ts index 484b892..6ea921b 100644 --- a/packages/suspense-website/src/examples/createCache/evict.ts +++ b/packages/suspense-website/src/examples/createCache/evict.ts @@ -1,6 +1,6 @@ -import { exampleCache } from "./cache"; +import { userProfileCache } from "./cache"; // REMOVE_BEFORE const userId = "123"; -exampleCache.evict(userId); +userProfileCache.evict(userId); diff --git a/packages/suspense-website/src/examples/createCache/hook.ts b/packages/suspense-website/src/examples/createCache/hook.ts index 4a1594a..a390d27 100644 --- a/packages/suspense-website/src/examples/createCache/hook.ts +++ b/packages/suspense-website/src/examples/createCache/hook.ts @@ -1,10 +1,10 @@ -import { exampleCache } from "./cache"; +import { userProfileCache } from "./cache"; // REMOVE_BEFORE import { useCacheStatus } from "suspense"; function StatusBadge({ userId }: { userId: string }) { - const status = useCacheStatus(exampleCache, userId); + const status = useCacheStatus(userProfileCache, userId); switch (status) { case "pending": diff --git a/packages/suspense-website/src/examples/createCache/prefetch.ts b/packages/suspense-website/src/examples/createCache/prefetch.ts index f2426f4..4dfbd64 100644 --- a/packages/suspense-website/src/examples/createCache/prefetch.ts +++ b/packages/suspense-website/src/examples/createCache/prefetch.ts @@ -1,4 +1,4 @@ -import { exampleCache } from "./cache"; +import { userProfileCache } from "./cache"; function getQueryParam(key: string): string { return "dummy"; @@ -8,7 +8,7 @@ function getQueryParam(key: string): string { const userId = getQueryParam("userId"); // Start loading user data eagerly, while route renders. -exampleCache.prefetch(userId); +userProfileCache.prefetch(userId); function UserProfileRoute() { // ... diff --git a/packages/suspense-website/src/examples/createCache/suspense.ts b/packages/suspense-website/src/examples/createCache/suspense.ts index 63de26e..8ddec03 100644 --- a/packages/suspense-website/src/examples/createCache/suspense.ts +++ b/packages/suspense-website/src/examples/createCache/suspense.ts @@ -1,7 +1,8 @@ -import { exampleCache } from "./cache"; +import { userProfileCache } from "./cache"; // REMOVE_BEFORE function UserProfile({ userId }: { userId: string }) { - const userData = exampleCache.fetchSuspense(userId); + const userProfile = userProfileCache.fetchSuspense(userId); + // ... } diff --git a/packages/suspense-website/src/examples/createCache/sync.ts b/packages/suspense-website/src/examples/createCache/sync.ts index 9e40da7..1c3c5b4 100644 --- a/packages/suspense-website/src/examples/createCache/sync.ts +++ b/packages/suspense-website/src/examples/createCache/sync.ts @@ -1,9 +1,9 @@ -import { exampleCache } from "./cache"; +import { userProfileCache } from "./cache"; const userId = "fake"; // REMOVE_BEFORE // Returns a cached value if one has already been saved in the cache -const userDataOrUndefined = exampleCache.getValueIfCached(userId); +const userProfileOrUndefined = userProfileCache.getValueIfCached(userId); // Returns a cached value or throws if none has been loaded -const userData = exampleCache.getValue(userId); +const userProfile = userProfileCache.getValue(userId); diff --git a/packages/suspense-website/src/examples/createStreamingCache/cache.ts b/packages/suspense-website/src/examples/createStreamingCache/cache.ts index a746c43..d793380 100644 --- a/packages/suspense-website/src/examples/createStreamingCache/cache.ts +++ b/packages/suspense-website/src/examples/createStreamingCache/cache.ts @@ -6,9 +6,6 @@ export const exampleStreamingCache = createStreamingCache< [path: string], string >( - // Create unique key for params - (path: string) => path, - // Stream data for params async (notifier: StreamingProgressNotifier, path: string) => { let loadedLines = 0; diff --git a/packages/suspense-website/src/examples/createStreamingCache/cacheWithKey.ts b/packages/suspense-website/src/examples/createStreamingCache/cacheWithKey.ts new file mode 100644 index 0000000..b8a8968 --- /dev/null +++ b/packages/suspense-website/src/examples/createStreamingCache/cacheWithKey.ts @@ -0,0 +1,32 @@ +import { createStreamingCache, StreamingProgressNotifier } from "suspense"; + +class ApiClient { + async streamData( + id: string, + onData: (data: string[]) => void, + onComplete: () => void + ) { + return JSON.parse(""); + } +} + +// REMOVE_BEFORE +export const exampleStreamingCache = createStreamingCache< + [client: ApiClient, id: string], + string +>( + // In this example, data is streamed by a "client" object + async ( + notifier: StreamingProgressNotifier, + client: ApiClient, + id: string + ) => + client.streamData( + id, + (values: string[]) => notifier.update(values), + () => notifier.resolve() + ), + + // The id parameter is sufficiently unique to be the key + (client: ApiClient, id: string) => id +); diff --git a/packages/suspense-website/src/examples/index.ts b/packages/suspense-website/src/examples/index.ts index aec9b36..318b533 100644 --- a/packages/suspense-website/src/examples/index.ts +++ b/packages/suspense-website/src/examples/index.ts @@ -17,6 +17,9 @@ const createCache = { cache: processExample( readFileSync(join(__dirname, "createCache", "cache.ts"), "utf8") ), + cacheWithKey: processExample( + readFileSync(join(__dirname, "createCache", "cacheWithKey.ts"), "utf8") + ), evict: processExample( readFileSync(join(__dirname, "createCache", "evict.ts"), "utf8") ), @@ -50,6 +53,12 @@ const createStreamingCache = { cache: processExample( readFileSync(join(__dirname, "createStreamingCache", "cache.ts"), "utf8") ), + cacheWithKey: processExample( + readFileSync( + join(__dirname, "createStreamingCache", "cacheWithKey.ts"), + "utf8" + ) + ), hook: processExample( readFileSync(join(__dirname, "createStreamingCache", "hook.ts"), "utf8") ), diff --git a/packages/suspense-website/src/routes/createCache.tsx b/packages/suspense-website/src/routes/createCache.tsx index ec20b3f..41ea7c8 100644 --- a/packages/suspense-website/src/routes/createCache.tsx +++ b/packages/suspense-website/src/routes/createCache.tsx @@ -26,18 +26,17 @@ export default function CreateCacheRoute() { .)

-

Implementing one of these caches requires two methods:

-
    -
  • - One to compute a unique key from cache parameters, and -
  • -
  • One to load the data
  • -

- For example, a cache that loads user data from a (JSON) API might look - like this: + Implementing one of these caches typically only requires a single + method (to load the data). For example, a cache that loads user data + from a (JSON) API might look like this:

+

+ If one of the cache key parameters can't be stringified, a second + method should also be provided to compute a unique key. +

+ diff --git a/packages/suspense-website/src/routes/createStreamingCache.tsx b/packages/suspense-website/src/routes/createStreamingCache.tsx index f099af3..ac909a9 100644 --- a/packages/suspense-website/src/routes/createStreamingCache.tsx +++ b/packages/suspense-website/src/routes/createStreamingCache.tsx @@ -21,16 +21,17 @@ export default function CreateStreamingCacheRoute() { it provides a subscription interface that can be used to re-render as data incrementally loads.

-

Implementing a streaming cache requires two methods:

-
    -
  • - One to compute a unique key from cache parameters, and -
  • -
  • - One to stream the data using an API like a WebSocket -
  • -
+

+ Like a regular cache, implementing a streaming cache typically only + requires a single method– to stream the data using an API like a{" "} + WebSocket. +

+

+ A second method can be provided to compute the cache key if one of the + parameters can't be stringified. +

+
diff --git a/packages/suspense-website/src/suspense/ImportCache.ts b/packages/suspense-website/src/suspense/ImportCache.ts index b04be92..db2dbfa 100644 --- a/packages/suspense-website/src/suspense/ImportCache.ts +++ b/packages/suspense-website/src/suspense/ImportCache.ts @@ -5,20 +5,17 @@ type Module = any; export const { fetchAsync: fetchModuleAsync, fetchSuspense: fetchModuleSuspense, -} = createCache<[string], Module>( - (path: string) => path, - async (path: string) => { - switch (path) { - case "@codemirror/lang-css": - return await import("@codemirror/lang-css"); - case "@codemirror/lang-html": - return await import("@codemirror/lang-html"); - case "@codemirror/lang-javascript": - return await import("@codemirror/lang-javascript"); - case "@codemirror/lang-markdown": - return await import("@codemirror/lang-markdown"); - default: - throw Error(`Unknown path: ${path}`); - } +} = createCache<[string], Module>(async (path: string) => { + switch (path) { + case "@codemirror/lang-css": + return await import("@codemirror/lang-css"); + case "@codemirror/lang-html": + return await import("@codemirror/lang-html"); + case "@codemirror/lang-javascript": + return await import("@codemirror/lang-javascript"); + case "@codemirror/lang-markdown": + return await import("@codemirror/lang-markdown"); + default: + throw Error(`Unknown path: ${path}`); } -); +}); diff --git a/packages/suspense-website/src/suspense/SyntaxParsingCache.ts b/packages/suspense-website/src/suspense/SyntaxParsingCache.ts index 796cf49..6d1af62 100644 --- a/packages/suspense-website/src/suspense/SyntaxParsingCache.ts +++ b/packages/suspense-website/src/suspense/SyntaxParsingCache.ts @@ -35,7 +35,6 @@ export const { fetchAsync: highlightSyntaxAsync, fetchSuspense: highlightSyntaxSuspense, } = createCache<[code: string, language: Language], ParsedTokens[]>( - (code: string, language: Language) => `${code.length}-${language}`, async (code: string, language: Language) => { const languageExtension = await getLanguageExtension(language); const parsedTokens: ParsedTokens[] = []; @@ -159,6 +158,7 @@ export const { return parsedTokens; }, + (code: string, language: Language) => `${code.length}-${language}`, "SyntaxParsingCache" ); diff --git a/packages/suspense/CHANGELOG.md b/packages/suspense/CHANGELOG.md index b40334a..ac78222 100644 --- a/packages/suspense/CHANGELOG.md +++ b/packages/suspense/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.0.3 +* Swapped order of `getCacheKey` and `loadData` functions for both `createCache` and `createStreamingCache` so that `getCacheKey` could be optional. + ## 0.0.2 The initial release includes the following APIs: #### Cache types diff --git a/packages/suspense/src/cache/createCache.test.ts b/packages/suspense/src/cache/createCache.test.ts index d787b43..62fcf97 100644 --- a/packages/suspense/src/cache/createCache.test.ts +++ b/packages/suspense/src/cache/createCache.test.ts @@ -23,7 +23,7 @@ describe("createCache", () => { getCacheKey = jest.fn(); getCacheKey.mockImplementation((key) => key.toString()); - cache = createCache<[string], string>(getCacheKey, fetch, "cache"); + cache = createCache<[string], string>(fetch, getCacheKey, "cache"); }); describe("cache", () => { diff --git a/packages/suspense/src/cache/createCache.ts b/packages/suspense/src/cache/createCache.ts index b16037a..4a302cd 100644 --- a/packages/suspense/src/cache/createCache.ts +++ b/packages/suspense/src/cache/createCache.ts @@ -12,8 +12,8 @@ import { assertPendingRecord } from "../utils/assertPendingRecord"; import { isThennable } from "../utils/isThennable"; export function createCache, Value>( - getKey: (...params: Params) => string, load: (...params: Params) => Thennable | Value, + getKey: (...params: Params) => string = defaultGetKey, debugLabel?: string ): Cache { const recordMap = new Map>(); @@ -192,3 +192,7 @@ export function createCache, Value>( subscribeToStatus, }; } + +function defaultGetKey(...params: any[]): string { + return params.join(","); +} diff --git a/packages/suspense/src/cache/createStreamingCache.test.ts b/packages/suspense/src/cache/createStreamingCache.test.ts index b851734..4963770 100644 --- a/packages/suspense/src/cache/createStreamingCache.test.ts +++ b/packages/suspense/src/cache/createStreamingCache.test.ts @@ -25,8 +25,8 @@ describe("createStreamingCache", () => { ); cache = createStreamingCache<[string], string, any>( - getCacheKey, fetch, + getCacheKey, "cache" ); }); diff --git a/packages/suspense/src/cache/createStreamingCache.ts b/packages/suspense/src/cache/createStreamingCache.ts index dc827ed..038149f 100644 --- a/packages/suspense/src/cache/createStreamingCache.ts +++ b/packages/suspense/src/cache/createStreamingCache.ts @@ -13,8 +13,8 @@ export function createStreamingCache< Value, AdditionalData = undefined >( - getKey: (...params: Params) => string, load: (notifier: StreamingProgressNotifier, ...params: Params) => void, + getKey: (...params: Params) => string = defaultGetKey, debugLabel?: string ): StreamingCache { const streamingValuesMap = new Map< @@ -140,3 +140,7 @@ export function createStreamingCache< stream, }; } + +function defaultGetKey(...params: any[]): string { + return params.join(","); +} diff --git a/packages/suspense/src/hooks/useCacheStatus.test.tsx b/packages/suspense/src/hooks/useCacheStatus.test.tsx index e557051..f416d13 100644 --- a/packages/suspense/src/hooks/useCacheStatus.test.tsx +++ b/packages/suspense/src/hooks/useCacheStatus.test.tsx @@ -39,7 +39,7 @@ describe("useCacheStatus", () => { getCacheKey = jest.fn(); getCacheKey.mockImplementation((key) => key.toString()); - cache = createCache<[string], string>(getCacheKey, fetch, "cache"); + cache = createCache<[string], string>(fetch, getCacheKey, "cache"); lastRenderedStatus = undefined; }); diff --git a/packages/suspense/src/hooks/useStreamingValues.test.tsx b/packages/suspense/src/hooks/useStreamingValues.test.tsx index 3fa9c32..6043839 100644 --- a/packages/suspense/src/hooks/useStreamingValues.test.tsx +++ b/packages/suspense/src/hooks/useStreamingValues.test.tsx @@ -38,12 +38,9 @@ describe("useStreamingValue", () => { notifiers = new Map(); - cache = createStreamingCache( - (key) => key, - (notifier, key) => { - notifiers.set(key, notifier); - } - ); + cache = createStreamingCache((notifier, key) => { + notifiers.set(key, notifier); + }); }); it("should re-render as values stream in", () => {