Skip to content

Commit 89117a6

Browse files
committed
storage: expose init
1 parent 6f8c642 commit 89117a6

File tree

6 files changed

+40
-20
lines changed

6 files changed

+40
-20
lines changed

.changeset/modern-windows-rhyme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@solid-primitives/storage": minor
3+
"@solid-primitives/resource": patch
4+
---
5+
6+
storage: expose init promise/value, resource: docs clarification

packages/resource/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const [signal, abort] = makeAbortable({ timeout: 10000 });
3838

3939
const fetcher = (url: string) => fetch(url, { signal: signal() }).then(r => r.json());
4040

41-
// cached fetcher will only be called if `url` source changes, or gets invalidated
41+
// cached fetcher will not be called if something for the same URL is still in cache
4242
const [cachedFetcher, invalidate] = makeCache(fetcher, { storage: localStorage });
4343

4444
// works with createResource, or any wrapping API with the same interface

packages/storage/src/persisted.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Setter, Signal } from "solid-js";
1+
import type { Accessor, Setter, Signal } from "solid-js";
22
import { createUniqueId, untrack } from "solid-js";
33
import { isServer, isDev } from "solid-js/web";
44
import type { SetStoreFunction, Store } from "solid-js/store";
@@ -73,6 +73,10 @@ export type PersistenceOptions<T, O extends Record<string, any> | undefined> = {
7373
export type SignalType<S extends Signal<any> | [Store<any>, SetStoreFunction<any>]> =
7474
S extends Signal<infer T> ? T : S extends [Store<infer T>, SetStoreFunction<infer T>] ? T : never;
7575

76+
export type EnhancedSignalOrStore<T> =
77+
| [get: Accessor<T>, set: Setter<T>, init: Promise<string> | string | null]
78+
| [get: Store<T>, set: SetStoreFunction<T>, init: Promise<string> | string | null];
79+
7680
/**
7781
* Persists a signal, store or similar API
7882
* ```ts
@@ -91,31 +95,35 @@ export type SignalType<S extends Signal<any> | [Store<any>, SetStoreFunction<any
9195
*
9296
* @param {Signal<T> | [get: Store<T>, set: SetStoreFunction<T>]} signal - The signal or store to be persisted.
9397
* @param {PersistenceOptions<T, O>} options - The options for persistence.
94-
* @returns {Signal<T> | [get: Store<T>, set: SetStoreFunction<T>]} - The persisted signal or store.
98+
* @returns {EnhancedSignalOrStore<T>} - The persisted signal or store.
9599
*/
96100
export function makePersisted<S extends Signal<any> | [Store<any>, SetStoreFunction<any>]>(
97101
signal: S,
98102
options?: PersistenceOptions<SignalType<S>, undefined>,
99-
): S;
103+
): [S[0], S[1], init: Promise<string> | string | null];
100104
export function makePersisted<
101105
S extends Signal<any> | [Store<any>, SetStoreFunction<any>],
102106
O extends Record<string, any>,
103-
>(signal: S, options: PersistenceOptions<SignalType<S>, O>): S;
107+
>(
108+
signal: S,
109+
options: PersistenceOptions<SignalType<S>, O>,
110+
): [S[0], S[1], init: Promise<string> | string | null];
104111
export function makePersisted<
105112
S extends Signal<any> | [Store<any>, SetStoreFunction<any>],
106113
O extends Record<string, any> | undefined,
107114
T = SignalType<S>,
108-
>(signal: S, options: PersistenceOptions<T, O> = {} as PersistenceOptions<T, O>): S {
109-
const storage = options.storage || globalThis.localStorage;
115+
>(
116+
signal: S,
117+
options: PersistenceOptions<T, O> = {} as PersistenceOptions<T, O>,
118+
): [S[0], S[1], init: Promise<string> | string | null] {
119+
const storage = options.storage || (globalThis.localStorage as Storage | undefined);
110120
const name = options.name || `storage-${createUniqueId()}`;
111-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
112121
if (!storage) {
113-
return signal;
122+
return [signal[0], signal[1], null];
114123
}
115124
const storageOptions = (options as unknown as { storageOptions: O }).storageOptions;
116125
const serialize: (data: T) => string = options.serialize || JSON.stringify.bind(JSON);
117126
const deserialize: (data: string) => T = options.deserialize || JSON.parse.bind(JSON);
118-
// @ts-ignore
119127
const init = storage.getItem(name, storageOptions);
120128
const set =
121129
typeof signal[0] === "function"
@@ -163,9 +171,7 @@ export function makePersisted<
163171
const serialized: string | null | undefined =
164172
value != null ? (serialize(output) as string) : (value as null | undefined);
165173
options.sync?.[1](name, serialized);
166-
// @ts-ignore
167174
if (value != null) storage.setItem(name, serialized as string, storageOptions);
168-
// @ts-ignore
169175
else storage.removeItem(name, storageOptions);
170176
unchanged = false;
171177
return output;
@@ -174,11 +180,11 @@ export function makePersisted<
174180
(signal[1] as any)(...args);
175181
const value = serialize(untrack(() => signal[0] as any));
176182
options.sync?.[1](name, value);
177-
// @ts-ignore
178183
storage.setItem(name, value, storageOptions);
179184
unchanged = false;
180185
},
181-
] as S;
186+
init,
187+
] as [S[0], S[1], init: Promise<string> | string | null];
182188
}
183189

184190
/**

packages/storage/tauri-storage/src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { createSignal, For, untrack } from "solid-js";
22
import { createStore } from "solid-js/store";
33
// this would usually be imported from "@solid-primitives/storage":
4-
import { makePersisted, tauriStorage } from "../../src/index.ts";
4+
import { makePersisted } from "../../src/index.js";
5+
import { tauriStorage } from "../../src/tauri.js";
56

67
declare global {
78
interface Window {

packages/storage/tauri-storage/src/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
import { render } from "solid-js/web";
33

44
import "./styles.css";
5-
import App from "./App";
5+
import App from "./App.jsx";
66

77
render(() => <App />, document.getElementById("root") as HTMLElement);

packages/storage/test/persisted.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
22
import { createSignal } from "solid-js";
33
import { createStore } from "solid-js/store";
44
import { makePersisted } from "../src/persisted.js";
5-
import { AsyncStorage } from "../src/types.js";
5+
import { AsyncStorage } from "../src/index.js";
66

77
describe("makePersisted", () => {
88
let data: Record<string, string> = {};
@@ -127,9 +127,16 @@ describe("makePersisted", () => {
127127
});
128128
expect(signal()).toBe("init");
129129
setSignal("overwritten");
130-
if (resolve) {
131-
resolve("persisted");
132-
}
130+
resolve("persisted");
133131
expect(signal()).toBe("overwritten");
134132
});
133+
134+
it("exposes the initial value as third part of the return tuple", () => {
135+
const anotherMockAsyncStorage = { ...mockAsyncStorage };
136+
const promise = Promise.resolve("init");
137+
anotherMockAsyncStorage.getItem = () => promise;
138+
const [_signal, _setSignal, init] = makePersisted(createSignal("default"),
139+
{ storage: anotherMockAsyncStorage, name: "test8" });
140+
expect(init).toBe(promise);
141+
});
135142
});

0 commit comments

Comments
 (0)