diff --git a/.changeset/short-memes-count.md b/.changeset/short-memes-count.md new file mode 100644 index 000000000..6c55740f9 --- /dev/null +++ b/.changeset/short-memes-count.md @@ -0,0 +1,5 @@ +--- +"@solid-primitives/storage": minor +--- + +types and resource usage diff --git a/packages/storage/README.md b/packages/storage/README.md index b912f9c67..803b3b852 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -56,13 +56,17 @@ Instead of wrapping the resource itself, it is far simpler to use the `storage` persisted signal or [deep signal](../resource/#createdeepsignal): ```ts -const [resource] = createResource(fetcher, { storage: makePersisted(createSignal()) }); +const [resource] = createResource(fetcher, { + storage: value => makePersisted(createSignal(value)), +}); ``` If you are using an asynchronous storage to persist the state of a resource, it might receive an update due to being initialized from the storage before or after the fetcher resolved. If the initialization resolves after the fetcher, its result is discarded not to overwrite more current data. +If instead of a signal you want to use a store, consider using the `makeDeepSignal` primitive from the `resource` package. + ### Using `makePersisted` with Suspense In case you are using an asynchronous storage and want the initialisation mesh into Suspense instead of mixing it with Show, we provide the output of the initialisation as third part of the returned tuple: diff --git a/packages/storage/src/persisted.ts b/packages/storage/src/persisted.ts index a8f26a1ba..ad814bd2c 100644 --- a/packages/storage/src/persisted.ts +++ b/packages/storage/src/persisted.ts @@ -1,4 +1,4 @@ -import type { Accessor, Setter, Signal } from "solid-js"; +import type { Setter, Signal } from "solid-js"; import { createUniqueId, untrack } from "solid-js"; import { isServer, isDev } from "solid-js/web"; import type { SetStoreFunction, Store } from "solid-js/store"; @@ -70,17 +70,7 @@ export type PersistenceOptions | undefined> = { storageOptions?: O; }); -export type SignalInput = Signal | [Store, SetStoreFunction]; - -export type SignalType = - S extends Signal ? T : S extends [Store, SetStoreFunction] ? T : never; - -export type PersistedState = - S extends Signal - ? [get: Accessor, set: Setter, init: Promise | string | null] - : S extends [Store, SetStoreFunction] - ? [get: Store, set: SetStoreFunction, init: Promise | string | null] - : never; +export type PersistedState = S & { 2: Promise | string | null }; /** * Persists a signal, store or similar API @@ -102,18 +92,19 @@ export type PersistedState = * @param {PersistenceOptions} options - The options for persistence. * @returns {PersistedState} - The persisted signal or store. */ -export function makePersisted( - signal: S, - options?: PersistenceOptions, undefined>, -): PersistedState; -export function makePersisted>( +export function makePersisted | [Store, SetStoreFunction]>( signal: S, - options: PersistenceOptions, O>, + options?: PersistenceOptions, ): PersistedState; export function makePersisted< - S extends SignalInput, + T, + S extends Signal | [Store, SetStoreFunction], + O extends Record, +>(signal: S, options: PersistenceOptions): PersistedState; +export function makePersisted< + T, + S extends Signal | [Store, SetStoreFunction], O extends Record | undefined, - T = SignalType, >( signal: S, options: PersistenceOptions = {} as PersistenceOptions, @@ -121,7 +112,7 @@ export function makePersisted< const storage = options.storage || (globalThis.localStorage as Storage | undefined); const name = options.name || `storage-${createUniqueId()}`; if (!storage) { - return [signal[0], signal[1], null] as PersistedState; + return Object.assign(signal, { 2: null }); } const storageOptions = (options as unknown as { storageOptions: O }).storageOptions; const serialize: (data: T) => string = options.serialize || JSON.stringify.bind(JSON); @@ -167,28 +158,28 @@ export function makePersisted< }); } - return [ - signal[0], - typeof signal[0] === "function" - ? (value?: T | ((prev: T) => T)) => { - const output = (signal[1] as Setter)(value as any); - const serialized: string | null | undefined = - value != null ? serialize(output) : (value as null | undefined); - options.sync?.[1](name, serialized); - if (serialized != null) storage.setItem(name, serialized, storageOptions); - else storage.removeItem(name, storageOptions); - unchanged = false; - return output; - } - : (...args: any[]) => { - (signal[1] as any)(...args); - const value = serialize(untrack(() => signal[0])); - options.sync?.[1](name, value); - storage.setItem(name, value, storageOptions); - unchanged = false; - }, - init, - ] as PersistedState; + return Object.assign([], signal, { + 1: + typeof signal[0] === "function" + ? (value?: T | ((prev: T) => T)) => { + const output = (signal[1] as Setter)(value as any); + const serialized: string | null | undefined = + value != null ? serialize(output) : (value as null | undefined); + options.sync?.[1](name, serialized); + if (serialized != null) storage.setItem(name, serialized, storageOptions); + else storage.removeItem(name, storageOptions); + unchanged = false; + return output; + } + : (...args: any[]) => { + (signal[1] as any)(...args); + const value = untrack(() => serialize(signal[0] as T)); + options.sync?.[1](name, value); + storage.setItem(name, value, storageOptions); + unchanged = false; + }, + 2: init, + }) as PersistedState; } /** diff --git a/packages/storage/test/persisted.test.ts b/packages/storage/test/persisted.test.ts index 9cbab1eb0..06ea9137a 100644 --- a/packages/storage/test/persisted.test.ts +++ b/packages/storage/test/persisted.test.ts @@ -133,7 +133,7 @@ describe("makePersisted", () => { it("exposes the initial value as third part of the return tuple", () => { const anotherMockAsyncStorage = { ...mockAsyncStorage }; - const promise = Promise.resolve("init"); + const promise = Promise.resolve('"init"'); anotherMockAsyncStorage.getItem = () => promise; const [_signal, _setSignal, init] = makePersisted(createSignal("default"), { storage: anotherMockAsyncStorage,