everkit-state 0.1.0 → 0.4.0

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/README.md CHANGED
@@ -1,21 +1,62 @@
1
1
  # everkit-state
2
2
 
3
- Reusable client-state + persistence primitives for React Native / Expo apps. The storage backend is **injected**, so the core stays pure and testable outside a React Native runtime.
3
+ everkit-state provides small, composable Zustand persistence primitives for Expo and React Native apps. It keeps native storage outside the package by accepting a storage adapter from the consumer, so the package remains pure TypeScript and works in plain Node tests
4
4
 
5
- ## Exports
5
+ ## Install
6
6
 
7
- - **`makeSecureStoreAdapter(api)`** → Zustand `StateStorage` — a thin, pass-through bridge from a SecureStore-shaped API (`getItemAsync`/`setItemAsync`/`deleteItemAsync`) to Zustand `persist`. Enforces the load-bearing contract: `getItem` resolves `null` (not `undefined`) on a miss and propagates errors; `removeItem` is idempotent.
8
- - **`makeSecureJsonStorage(api)`** — `createJSONStorage`-wrapped adapter; what `persist({ storage })` consumes. Lazy, so importing it doesn't touch the native store at boot.
9
- - **`createPreferencesSlice`** — a composable Zustand slice for the shared persisted shape: `theme` + `hasCompletedOnboarding` (+ setters). Spread into a bound store alongside app-specific runtime fields.
10
- - **`pickPersistedPreferences(state)`** — `partialize` helper: persists only `theme` + `hasCompletedOnboarding`, dropping actions and foreign fields.
11
- - **`ThemePreference`** — `'light' | 'dark' | 'system'` (owned here; re-exported by the theme package).
7
+ ```sh
8
+ npm install everkit-state zustand
9
+ ```
12
10
 
13
- ## Peer dependencies
11
+ | Peer | Range | Required |
12
+ | --------- | --------- | -------- |
13
+ | `zustand` | `>=5.0.0` | Required |
14
14
 
15
- - `zustand >= 5`
15
+ ## Usage
16
16
 
17
- ## Test
17
+ ```ts
18
+ import * as SecureStore from 'expo-secure-store'
19
+ import { create } from 'zustand'
20
+ import { persist } from 'zustand/middleware'
21
+ import {
22
+ createPreferencesSlice,
23
+ makeSecureJsonStorage,
24
+ pickPersistedPreferences,
25
+ type PreferencesSlice,
26
+ } from 'everkit-state'
18
27
 
19
- ```sh
20
- npm test # node --require sucrase/register --test "src/**/__tests__/**/*.test.ts"
28
+ interface AppState extends PreferencesSlice {
29
+ session: string | null
30
+ setSession: (session: string | null) => void
31
+ }
32
+
33
+ const storage = makeSecureJsonStorage(SecureStore)
34
+
35
+ export const useAppStore = create<AppState>()(
36
+ persist(
37
+ (set, get, store) => ({
38
+ ...createPreferencesSlice(set, get, store),
39
+ session: null,
40
+ setSession: (session) => set({ session }),
41
+ }),
42
+ {
43
+ name: 'app-store',
44
+ storage,
45
+ partialize: pickPersistedPreferences,
46
+ },
47
+ ),
48
+ )
21
49
  ```
50
+
51
+ ## Development
52
+
53
+ - `npm run build`: build CommonJS, ES module, and declaration output
54
+ - `npm run prepare`: build package output after installation
55
+ - `npm run lint`: check source and configuration with ESLint
56
+ - `npm run format:check`: check formatting with Prettier
57
+ - `npm run typecheck`: type-check without emitting files
58
+ - `npm test`: run the Node test suite
59
+ - `npm run test:watch`: run the Node test suite in watch mode
60
+ - `npm run clean`: remove generated package output
61
+
62
+ See [CHANGELOG.md](CHANGELOG.md). All everkit packages share one version line
@@ -6,28 +6,31 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.createPreferencesSlice = void 0;
7
7
  exports.pickPersistedPreferences = pickPersistedPreferences;
8
8
  /**
9
- * User-chosen theme mode. `'system'` defers to the OS appearance. This type
10
- * is owned here (it backs the persisted preference); the theme-rendering
11
- * package re-exports it so callers have a single source of truth.
9
+ * A user's chosen color-scheme preference
10
+ *
11
+ * `'system'` delegates the active color scheme to the consumer's operating-system integration
12
12
  */
13
13
 
14
14
  /**
15
- * The slice of app state that is common to every app and persisted across a
16
- * reload + reboot: the theme preference and the onboarding-completed flag.
17
- * Apps compose this into their bound store alongside app-specific,
18
- * runtime-only fields (auth session, bootstrap flags) which are NOT persisted.
15
+ * The persisted preferences shared by an app's bound Zustand store
16
+ *
17
+ * A slice starts with the `light` theme and incomplete onboarding when created by
18
+ * `createPreferencesSlice`. Consumers may compose additional runtime-only state into the same
19
+ * store and exclude it with `pickPersistedPreferences`
19
20
  */
20
21
 
21
22
  /**
22
- * Zustand slice creator for the shared preferences. Spread it into a bound
23
- * store:
23
+ * Creates a composable Zustand slice for persisted theme and onboarding preferences
24
24
  *
25
- * create<AppState>()(persist((set, get, store) => ({
26
- * ...createPreferencesSlice(set, get, store),
27
- * // app-specific, runtime-only fields:
28
- * session: null,
29
- * setSession: (session) => set({ session }),
30
- * }), { name: 'app-store', storage, partialize: pickPersistedPreferences }))
25
+ * The initial values are `theme: 'light'` and `hasCompletedOnboarding: false`. Each setter updates
26
+ * only its corresponding field and does not persist state by itself
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const store = create<AppState>()(
31
+ * persist((set, get, store) => ({ ...createPreferencesSlice(set, get, store) }), options),
32
+ * )
33
+ * ```
31
34
  */
32
35
  const createPreferencesSlice = set => ({
33
36
  theme: 'light',
@@ -41,9 +44,10 @@ const createPreferencesSlice = set => ({
41
44
  });
42
45
 
43
46
  /**
44
- * `partialize` helper for `persist`: keep ONLY the persisted preference
45
- * fields, dropping the action functions and any app-specific runtime fields
46
- * (e.g. an auth session) that must never be written to disk.
47
+ * Selects only the serializable preferences that should be passed to Zustand `persist`
48
+ *
49
+ * The returned object always contains exactly `theme` and `hasCompletedOnboarding`. Actions and
50
+ * any additional properties on the consumer's state are excluded
47
51
  */
48
52
  exports.createPreferencesSlice = createPreferencesSlice;
49
53
  function pickPersistedPreferences(state) {
@@ -1 +1 @@
1
- {"version":3,"names":["createPreferencesSlice","set","theme","setTheme","pref","hasCompletedOnboarding","setHasCompletedOnboarding","value","exports","pickPersistedPreferences","state"],"sourceRoot":"../../src","sources":["preferences.ts"],"mappings":";;;;;;;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMA,sBAAgF,GAC3FC,GAAG,KACC;EACJC,KAAK,EAAE,OAAO;EACdC,QAAQ,EAAGC,IAAI,IAAKH,GAAG,CAAC;IAAEC,KAAK,EAAEE;EAAK,CAAC,CAAC;EACxCC,sBAAsB,EAAE,KAAK;EAC7BC,yBAAyB,EAAGC,KAAK,IAAKN,GAAG,CAAC;IAAEI,sBAAsB,EAAEE;EAAM,CAAC;AAC7E,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AAJAC,OAAA,CAAAR,sBAAA,GAAAA,sBAAA;AAKO,SAASS,wBAAwBA,CACtCC,KAAQ,EACoD;EAC5D,OAAO;IAAER,KAAK,EAAEQ,KAAK,CAACR,KAAK;IAAEG,sBAAsB,EAAEK,KAAK,CAACL;EAAuB,CAAC;AACrF","ignoreList":[]}
1
+ {"version":3,"names":["createPreferencesSlice","set","theme","setTheme","pref","hasCompletedOnboarding","setHasCompletedOnboarding","value","exports","pickPersistedPreferences","state"],"sourceRoot":"../../src","sources":["preferences.ts"],"mappings":";;;;;;;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,MAAMA,sBAAgF,GAC3FC,GAAG,KACC;EACJC,KAAK,EAAE,OAAO;EACdC,QAAQ,EAAGC,IAAI,IAAKH,GAAG,CAAC;IAAEC,KAAK,EAAEE;EAAK,CAAC,CAAC;EACxCC,sBAAsB,EAAE,KAAK;EAC7BC,yBAAyB,EAAGC,KAAK,IAAKN,GAAG,CAAC;IAAEI,sBAAsB,EAAEE;EAAM,CAAC;AAC7E,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AALAC,OAAA,CAAAR,sBAAA,GAAAA,sBAAA;AAMO,SAASS,wBAAwBA,CACtCC,KAAQ,EACoD;EAC5D,OAAO;IAAER,KAAK,EAAEQ,KAAK,CAACR,KAAK;IAAEG,sBAAsB,EAAEK,KAAK,CAACL;EAAuB,CAAC;AACrF","ignoreList":[]}
@@ -7,48 +7,18 @@ exports.makeSecureJsonStorage = makeSecureJsonStorage;
7
7
  exports.makeSecureStoreAdapter = makeSecureStoreAdapter;
8
8
  var _middleware = require("zustand/middleware");
9
9
  /**
10
- * Pure SecureStore -> Zustand `StateStorage` bridge.
10
+ * The SecureStore-compatible operations used by this package
11
11
  *
12
- * Apps persist Zustand stores through a secure key-value store (e.g.
13
- * `expo-secure-store`) so preferences (theme, onboarding flag, pinned ids)
14
- * survive a reload AND a device reboot. The wrapping is a one-liner per
15
- * method, but the contract has two load-bearing rules worth pinning with a
16
- * test:
17
- *
18
- * 1. `getItem` MUST resolve to `null` when the key is missing. Zustand
19
- * treats `null` as «no persisted state» and falls back to the store's
20
- * initial state. SecureStore's `getItemAsync` already returns `null`
21
- * for a missing key, so the adapter just passes it through — but we
22
- * want a regression guard against accidentally translating that to
23
- * `undefined`, an empty string, or a thrown error.
24
- *
25
- * 2. `removeItem` MUST be idempotent. Logging out clears the persisted
26
- * slice, and a second logout (or a logout-on-cold-start cleanup pass)
27
- * MUST NOT throw. SecureStore's `deleteItemAsync` is idempotent, so
28
- * again the adapter just passes it through.
29
- *
30
- * The store API is INJECTED rather than imported, so this module never
31
- * touches a native module: the production call site passes the real
32
- * `expo-secure-store` functions in; tests pass an in-memory fake. That keeps
33
- * the adapter loadable (and testable) in plain Node, outside a React Native
34
- * runtime.
12
+ * Implementations must preserve their own read, write, and delete semantics. In particular,
13
+ * `getItemAsync` returns `null` for a missing key only when the supplied backend does so
35
14
  */
36
15
 
37
16
  /**
38
- * The subset of `expo-secure-store` (or any compatible secure key-value
39
- * store) the adapter depends on. Only the three async functions Zustand's
40
- * `StateStorage` needs — `setItemAsync`, `getItemAsync`, `deleteItemAsync`.
41
- * Typed against the platform contract so a future SDK widening of the return
42
- * type fails compilation here, not at runtime.
43
- */
44
-
45
- /**
46
- * Build the `StateStorage` Zustand expects from a SecureStore-shaped API.
17
+ * Adapts a SecureStore-compatible API to Zustand's raw `StateStorage` interface
47
18
  *
48
- * Kept as a thin pass-through on purpose: any logic added here (caching,
49
- * error swallowing, value transforms) would silently change the persisted
50
- * shape across all stores. If you need such behaviour, add a SECOND wrapper
51
- * around this one and document it next to the store that uses it.
19
+ * The adapter passes keys, values, resolved values, and rejected errors through unchanged. It
20
+ * does not import a native module, transform values, or make deletion idempotent when the supplied
21
+ * backend is not idempotent
52
22
  */
53
23
  function makeSecureStoreAdapter(api) {
54
24
  return {
@@ -59,11 +29,11 @@ function makeSecureStoreAdapter(api) {
59
29
  }
60
30
 
61
31
  /**
62
- * `createJSONStorage`-wrapped adapter — what `persist({ storage })` actually
63
- * consumes. The factory function is invoked lazily by Zustand on first
64
- * read/write, so importing this module does NOT touch the secure store at app
65
- * boot. That matters because a native secure store is unavailable during SSR
66
- * and during the test runner's module load.
32
+ * Creates the JSON `PersistStorage` consumed by Zustand's `persist` middleware
33
+ *
34
+ * The supplied API is captured without calling it, so constructing or importing this value does
35
+ * not access native storage. Read and write operations happen only when Zustand uses the storage
36
+ * object
67
37
  */
68
38
  function makeSecureJsonStorage(api) {
69
39
  return (0, _middleware.createJSONStorage)(() => makeSecureStoreAdapter(api));
@@ -1 +1 @@
1
- {"version":3,"names":["_middleware","require","makeSecureStoreAdapter","api","getItem","key","getItemAsync","setItem","value","setItemAsync","removeItem","deleteItemAsync","makeSecureJsonStorage","createJSONStorage"],"sourceRoot":"../../src","sources":["secureStore.ts"],"mappings":";;;;;;;AA4BA,IAAAA,WAAA,GAAAC,OAAA;AA5BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,sBAAsBA,CAACC,GAAmB,EAAgB;EACxE,OAAO;IACLC,OAAO,EAAGC,GAAW,IAAKF,GAAG,CAACG,YAAY,CAACD,GAAG,CAAC;IAC/CE,OAAO,EAAEA,CAACF,GAAW,EAAEG,KAAa,KAAKL,GAAG,CAACM,YAAY,CAACJ,GAAG,EAAEG,KAAK,CAAC;IACrEE,UAAU,EAAGL,GAAW,IAAKF,GAAG,CAACQ,eAAe,CAACN,GAAG;EACtD,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASO,qBAAqBA,CAACT,GAAmB,EAAE;EACzD,OAAO,IAAAU,6BAAiB,EAAC,MAAMX,sBAAsB,CAACC,GAAG,CAAC,CAAC;AAC7D","ignoreList":[]}
1
+ {"version":3,"names":["_middleware","require","makeSecureStoreAdapter","api","getItem","key","getItemAsync","setItem","value","setItemAsync","removeItem","deleteItemAsync","makeSecureJsonStorage","createJSONStorage"],"sourceRoot":"../../src","sources":["secureStore.ts"],"mappings":";;;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,sBAAsBA,CAACC,GAAmB,EAAgB;EACxE,OAAO;IACLC,OAAO,EAAGC,GAAW,IAAKF,GAAG,CAACG,YAAY,CAACD,GAAG,CAAC;IAC/CE,OAAO,EAAEA,CAACF,GAAW,EAAEG,KAAa,KAAKL,GAAG,CAACM,YAAY,CAACJ,GAAG,EAAEG,KAAK,CAAC;IACrEE,UAAU,EAAGL,GAAW,IAAKF,GAAG,CAACQ,eAAe,CAACN,GAAG;EACtD,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASO,qBAAqBA,CAACT,GAAmB,EAAE;EACzD,OAAO,IAAAU,6BAAiB,EAAC,MAAMX,sBAAsB,CAACC,GAAG,CAAC,CAAC;AAC7D","ignoreList":[]}
@@ -1,28 +1,31 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * User-chosen theme mode. `'system'` defers to the OS appearance. This type
5
- * is owned here (it backs the persisted preference); the theme-rendering
6
- * package re-exports it so callers have a single source of truth.
4
+ * A user's chosen color-scheme preference
5
+ *
6
+ * `'system'` delegates the active color scheme to the consumer's operating-system integration
7
7
  */
8
8
 
9
9
  /**
10
- * The slice of app state that is common to every app and persisted across a
11
- * reload + reboot: the theme preference and the onboarding-completed flag.
12
- * Apps compose this into their bound store alongside app-specific,
13
- * runtime-only fields (auth session, bootstrap flags) which are NOT persisted.
10
+ * The persisted preferences shared by an app's bound Zustand store
11
+ *
12
+ * A slice starts with the `light` theme and incomplete onboarding when created by
13
+ * `createPreferencesSlice`. Consumers may compose additional runtime-only state into the same
14
+ * store and exclude it with `pickPersistedPreferences`
14
15
  */
15
16
 
16
17
  /**
17
- * Zustand slice creator for the shared preferences. Spread it into a bound
18
- * store:
18
+ * Creates a composable Zustand slice for persisted theme and onboarding preferences
19
19
  *
20
- * create<AppState>()(persist((set, get, store) => ({
21
- * ...createPreferencesSlice(set, get, store),
22
- * // app-specific, runtime-only fields:
23
- * session: null,
24
- * setSession: (session) => set({ session }),
25
- * }), { name: 'app-store', storage, partialize: pickPersistedPreferences }))
20
+ * The initial values are `theme: 'light'` and `hasCompletedOnboarding: false`. Each setter updates
21
+ * only its corresponding field and does not persist state by itself
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const store = create<AppState>()(
26
+ * persist((set, get, store) => ({ ...createPreferencesSlice(set, get, store) }), options),
27
+ * )
28
+ * ```
26
29
  */
27
30
  export const createPreferencesSlice = set => ({
28
31
  theme: 'light',
@@ -36,9 +39,10 @@ export const createPreferencesSlice = set => ({
36
39
  });
37
40
 
38
41
  /**
39
- * `partialize` helper for `persist`: keep ONLY the persisted preference
40
- * fields, dropping the action functions and any app-specific runtime fields
41
- * (e.g. an auth session) that must never be written to disk.
42
+ * Selects only the serializable preferences that should be passed to Zustand `persist`
43
+ *
44
+ * The returned object always contains exactly `theme` and `hasCompletedOnboarding`. Actions and
45
+ * any additional properties on the consumer's state are excluded
42
46
  */
43
47
  export function pickPersistedPreferences(state) {
44
48
  return {
@@ -1 +1 @@
1
- {"version":3,"names":["createPreferencesSlice","set","theme","setTheme","pref","hasCompletedOnboarding","setHasCompletedOnboarding","value","pickPersistedPreferences","state"],"sourceRoot":"../../src","sources":["preferences.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,sBAAgF,GAC3FC,GAAG,KACC;EACJC,KAAK,EAAE,OAAO;EACdC,QAAQ,EAAGC,IAAI,IAAKH,GAAG,CAAC;IAAEC,KAAK,EAAEE;EAAK,CAAC,CAAC;EACxCC,sBAAsB,EAAE,KAAK;EAC7BC,yBAAyB,EAAGC,KAAK,IAAKN,GAAG,CAAC;IAAEI,sBAAsB,EAAEE;EAAM,CAAC;AAC7E,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CACtCC,KAAQ,EACoD;EAC5D,OAAO;IAAEP,KAAK,EAAEO,KAAK,CAACP,KAAK;IAAEG,sBAAsB,EAAEI,KAAK,CAACJ;EAAuB,CAAC;AACrF","ignoreList":[]}
1
+ {"version":3,"names":["createPreferencesSlice","set","theme","setTheme","pref","hasCompletedOnboarding","setHasCompletedOnboarding","value","pickPersistedPreferences","state"],"sourceRoot":"../../src","sources":["preferences.ts"],"mappings":";;AAEA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,sBAAgF,GAC3FC,GAAG,KACC;EACJC,KAAK,EAAE,OAAO;EACdC,QAAQ,EAAGC,IAAI,IAAKH,GAAG,CAAC;IAAEC,KAAK,EAAEE;EAAK,CAAC,CAAC;EACxCC,sBAAsB,EAAE,KAAK;EAC7BC,yBAAyB,EAAGC,KAAK,IAAKN,GAAG,CAAC;IAAEI,sBAAsB,EAAEE;EAAM,CAAC;AAC7E,CAAC,CAAC;;AAEF;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,wBAAwBA,CACtCC,KAAQ,EACoD;EAC5D,OAAO;IAAEP,KAAK,EAAEO,KAAK,CAACP,KAAK;IAAEG,sBAAsB,EAAEI,KAAK,CAACJ;EAAuB,CAAC;AACrF","ignoreList":[]}
@@ -1,50 +1,20 @@
1
1
  "use strict";
2
2
 
3
- /**
4
- * Pure SecureStore -> Zustand `StateStorage` bridge.
5
- *
6
- * Apps persist Zustand stores through a secure key-value store (e.g.
7
- * `expo-secure-store`) so preferences (theme, onboarding flag, pinned ids)
8
- * survive a reload AND a device reboot. The wrapping is a one-liner per
9
- * method, but the contract has two load-bearing rules worth pinning with a
10
- * test:
11
- *
12
- * 1. `getItem` MUST resolve to `null` when the key is missing. Zustand
13
- * treats `null` as «no persisted state» and falls back to the store's
14
- * initial state. SecureStore's `getItemAsync` already returns `null`
15
- * for a missing key, so the adapter just passes it through — but we
16
- * want a regression guard against accidentally translating that to
17
- * `undefined`, an empty string, or a thrown error.
18
- *
19
- * 2. `removeItem` MUST be idempotent. Logging out clears the persisted
20
- * slice, and a second logout (or a logout-on-cold-start cleanup pass)
21
- * MUST NOT throw. SecureStore's `deleteItemAsync` is idempotent, so
22
- * again the adapter just passes it through.
23
- *
24
- * The store API is INJECTED rather than imported, so this module never
25
- * touches a native module: the production call site passes the real
26
- * `expo-secure-store` functions in; tests pass an in-memory fake. That keeps
27
- * the adapter loadable (and testable) in plain Node, outside a React Native
28
- * runtime.
29
- */
30
-
31
3
  import { createJSONStorage } from 'zustand/middleware';
32
4
 
33
5
  /**
34
- * The subset of `expo-secure-store` (or any compatible secure key-value
35
- * store) the adapter depends on. Only the three async functions Zustand's
36
- * `StateStorage` needs — `setItemAsync`, `getItemAsync`, `deleteItemAsync`.
37
- * Typed against the platform contract so a future SDK widening of the return
38
- * type fails compilation here, not at runtime.
6
+ * The SecureStore-compatible operations used by this package
7
+ *
8
+ * Implementations must preserve their own read, write, and delete semantics. In particular,
9
+ * `getItemAsync` returns `null` for a missing key only when the supplied backend does so
39
10
  */
40
11
 
41
12
  /**
42
- * Build the `StateStorage` Zustand expects from a SecureStore-shaped API.
13
+ * Adapts a SecureStore-compatible API to Zustand's raw `StateStorage` interface
43
14
  *
44
- * Kept as a thin pass-through on purpose: any logic added here (caching,
45
- * error swallowing, value transforms) would silently change the persisted
46
- * shape across all stores. If you need such behaviour, add a SECOND wrapper
47
- * around this one and document it next to the store that uses it.
15
+ * The adapter passes keys, values, resolved values, and rejected errors through unchanged. It
16
+ * does not import a native module, transform values, or make deletion idempotent when the supplied
17
+ * backend is not idempotent
48
18
  */
49
19
  export function makeSecureStoreAdapter(api) {
50
20
  return {
@@ -55,11 +25,11 @@ export function makeSecureStoreAdapter(api) {
55
25
  }
56
26
 
57
27
  /**
58
- * `createJSONStorage`-wrapped adapter — what `persist({ storage })` actually
59
- * consumes. The factory function is invoked lazily by Zustand on first
60
- * read/write, so importing this module does NOT touch the secure store at app
61
- * boot. That matters because a native secure store is unavailable during SSR
62
- * and during the test runner's module load.
28
+ * Creates the JSON `PersistStorage` consumed by Zustand's `persist` middleware
29
+ *
30
+ * The supplied API is captured without calling it, so constructing or importing this value does
31
+ * not access native storage. Read and write operations happen only when Zustand uses the storage
32
+ * object
63
33
  */
64
34
  export function makeSecureJsonStorage(api) {
65
35
  return createJSONStorage(() => makeSecureStoreAdapter(api));
@@ -1 +1 @@
1
- {"version":3,"names":["createJSONStorage","makeSecureStoreAdapter","api","getItem","key","getItemAsync","setItem","value","setItemAsync","removeItem","deleteItemAsync","makeSecureJsonStorage"],"sourceRoot":"../../src","sources":["secureStore.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAASA,iBAAiB,QAA2B,oBAAoB;;AAEzE;AACA;AACA;AACA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,sBAAsBA,CAACC,GAAmB,EAAgB;EACxE,OAAO;IACLC,OAAO,EAAGC,GAAW,IAAKF,GAAG,CAACG,YAAY,CAACD,GAAG,CAAC;IAC/CE,OAAO,EAAEA,CAACF,GAAW,EAAEG,KAAa,KAAKL,GAAG,CAACM,YAAY,CAACJ,GAAG,EAAEG,KAAK,CAAC;IACrEE,UAAU,EAAGL,GAAW,IAAKF,GAAG,CAACQ,eAAe,CAACN,GAAG;EACtD,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASO,qBAAqBA,CAACT,GAAmB,EAAE;EACzD,OAAOF,iBAAiB,CAAC,MAAMC,sBAAsB,CAACC,GAAG,CAAC,CAAC;AAC7D","ignoreList":[]}
1
+ {"version":3,"names":["createJSONStorage","makeSecureStoreAdapter","api","getItem","key","getItemAsync","setItem","value","setItemAsync","removeItem","deleteItemAsync","makeSecureJsonStorage"],"sourceRoot":"../../src","sources":["secureStore.ts"],"mappings":";;AAAA,SAASA,iBAAiB,QAA2B,oBAAoB;;AAEzE;AACA;AACA;AACA;AACA;AACA;;AAOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,sBAAsBA,CAACC,GAAmB,EAAgB;EACxE,OAAO;IACLC,OAAO,EAAGC,GAAW,IAAKF,GAAG,CAACG,YAAY,CAACD,GAAG,CAAC;IAC/CE,OAAO,EAAEA,CAACF,GAAW,EAAEG,KAAa,KAAKL,GAAG,CAACM,YAAY,CAACJ,GAAG,EAAEG,KAAK,CAAC;IACrEE,UAAU,EAAGL,GAAW,IAAKF,GAAG,CAACQ,eAAe,CAACN,GAAG;EACtD,CAAC;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASO,qBAAqBA,CAACT,GAAmB,EAAE;EACzD,OAAOF,iBAAiB,CAAC,MAAMC,sBAAsB,CAACC,GAAG,CAAC,CAAC;AAC7D","ignoreList":[]}
@@ -1,15 +1,16 @@
1
1
  import type { StateCreator } from 'zustand';
2
2
  /**
3
- * User-chosen theme mode. `'system'` defers to the OS appearance. This type
4
- * is owned here (it backs the persisted preference); the theme-rendering
5
- * package re-exports it so callers have a single source of truth.
3
+ * A user's chosen color-scheme preference
4
+ *
5
+ * `'system'` delegates the active color scheme to the consumer's operating-system integration
6
6
  */
7
7
  export type ThemePreference = 'light' | 'dark' | 'system';
8
8
  /**
9
- * The slice of app state that is common to every app and persisted across a
10
- * reload + reboot: the theme preference and the onboarding-completed flag.
11
- * Apps compose this into their bound store alongside app-specific,
12
- * runtime-only fields (auth session, bootstrap flags) which are NOT persisted.
9
+ * The persisted preferences shared by an app's bound Zustand store
10
+ *
11
+ * A slice starts with the `light` theme and incomplete onboarding when created by
12
+ * `createPreferencesSlice`. Consumers may compose additional runtime-only state into the same
13
+ * store and exclude it with `pickPersistedPreferences`
13
14
  */
14
15
  export interface PreferencesSlice {
15
16
  theme: ThemePreference;
@@ -18,21 +19,24 @@ export interface PreferencesSlice {
18
19
  setHasCompletedOnboarding: (value: boolean) => void;
19
20
  }
20
21
  /**
21
- * Zustand slice creator for the shared preferences. Spread it into a bound
22
- * store:
22
+ * Creates a composable Zustand slice for persisted theme and onboarding preferences
23
23
  *
24
- * create<AppState>()(persist((set, get, store) => ({
25
- * ...createPreferencesSlice(set, get, store),
26
- * // app-specific, runtime-only fields:
27
- * session: null,
28
- * setSession: (session) => set({ session }),
29
- * }), { name: 'app-store', storage, partialize: pickPersistedPreferences }))
24
+ * The initial values are `theme: 'light'` and `hasCompletedOnboarding: false`. Each setter updates
25
+ * only its corresponding field and does not persist state by itself
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const store = create<AppState>()(
30
+ * persist((set, get, store) => ({ ...createPreferencesSlice(set, get, store) }), options),
31
+ * )
32
+ * ```
30
33
  */
31
34
  export declare const createPreferencesSlice: StateCreator<PreferencesSlice, [], [], PreferencesSlice>;
32
35
  /**
33
- * `partialize` helper for `persist`: keep ONLY the persisted preference
34
- * fields, dropping the action functions and any app-specific runtime fields
35
- * (e.g. an auth session) that must never be written to disk.
36
+ * Selects only the serializable preferences that should be passed to Zustand `persist`
37
+ *
38
+ * The returned object always contains exactly `theme` and `hasCompletedOnboarding`. Actions and
39
+ * any additional properties on the consumer's state are excluded
36
40
  */
37
41
  export declare function pickPersistedPreferences<S extends PreferencesSlice>(state: S): Pick<PreferencesSlice, 'theme' | 'hasCompletedOnboarding'>;
38
42
  //# sourceMappingURL=preferences.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preferences.d.ts","sourceRoot":"","sources":["../../../src/preferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC,sBAAsB,EAAE,OAAO,CAAA;IAC/B,yBAAyB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACpD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,gBAAgB,EAAE,EAAE,EAAE,EAAE,EAAE,gBAAgB,CAO1F,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,gBAAgB,EACjE,KAAK,EAAE,CAAC,GACP,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,wBAAwB,CAAC,CAE5D"}
1
+ {"version":3,"file":"preferences.d.ts","sourceRoot":"","sources":["../../../src/preferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC,sBAAsB,EAAE,OAAO,CAAA;IAC/B,yBAAyB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACpD;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,gBAAgB,EAAE,EAAE,EAAE,EAAE,EAAE,gBAAgB,CAO1F,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,gBAAgB,EACjE,KAAK,EAAE,CAAC,GACP,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,wBAAwB,CAAC,CAE5D"}
@@ -1,37 +1,9 @@
1
- /**
2
- * Pure SecureStore -> Zustand `StateStorage` bridge.
3
- *
4
- * Apps persist Zustand stores through a secure key-value store (e.g.
5
- * `expo-secure-store`) so preferences (theme, onboarding flag, pinned ids)
6
- * survive a reload AND a device reboot. The wrapping is a one-liner per
7
- * method, but the contract has two load-bearing rules worth pinning with a
8
- * test:
9
- *
10
- * 1. `getItem` MUST resolve to `null` when the key is missing. Zustand
11
- * treats `null` as «no persisted state» and falls back to the store's
12
- * initial state. SecureStore's `getItemAsync` already returns `null`
13
- * for a missing key, so the adapter just passes it through — but we
14
- * want a regression guard against accidentally translating that to
15
- * `undefined`, an empty string, or a thrown error.
16
- *
17
- * 2. `removeItem` MUST be idempotent. Logging out clears the persisted
18
- * slice, and a second logout (or a logout-on-cold-start cleanup pass)
19
- * MUST NOT throw. SecureStore's `deleteItemAsync` is idempotent, so
20
- * again the adapter just passes it through.
21
- *
22
- * The store API is INJECTED rather than imported, so this module never
23
- * touches a native module: the production call site passes the real
24
- * `expo-secure-store` functions in; tests pass an in-memory fake. That keeps
25
- * the adapter loadable (and testable) in plain Node, outside a React Native
26
- * runtime.
27
- */
28
1
  import { type StateStorage } from 'zustand/middleware';
29
2
  /**
30
- * The subset of `expo-secure-store` (or any compatible secure key-value
31
- * store) the adapter depends on. Only the three async functions Zustand's
32
- * `StateStorage` needs — `setItemAsync`, `getItemAsync`, `deleteItemAsync`.
33
- * Typed against the platform contract so a future SDK widening of the return
34
- * type fails compilation here, not at runtime.
3
+ * The SecureStore-compatible operations used by this package
4
+ *
5
+ * Implementations must preserve their own read, write, and delete semantics. In particular,
6
+ * `getItemAsync` returns `null` for a missing key only when the supplied backend does so
35
7
  */
36
8
  export interface SecureStoreApi {
37
9
  getItemAsync: (key: string) => Promise<string | null>;
@@ -39,20 +11,19 @@ export interface SecureStoreApi {
39
11
  deleteItemAsync: (key: string) => Promise<void>;
40
12
  }
41
13
  /**
42
- * Build the `StateStorage` Zustand expects from a SecureStore-shaped API.
14
+ * Adapts a SecureStore-compatible API to Zustand's raw `StateStorage` interface
43
15
  *
44
- * Kept as a thin pass-through on purpose: any logic added here (caching,
45
- * error swallowing, value transforms) would silently change the persisted
46
- * shape across all stores. If you need such behaviour, add a SECOND wrapper
47
- * around this one and document it next to the store that uses it.
16
+ * The adapter passes keys, values, resolved values, and rejected errors through unchanged. It
17
+ * does not import a native module, transform values, or make deletion idempotent when the supplied
18
+ * backend is not idempotent
48
19
  */
49
20
  export declare function makeSecureStoreAdapter(api: SecureStoreApi): StateStorage;
50
21
  /**
51
- * `createJSONStorage`-wrapped adapter — what `persist({ storage })` actually
52
- * consumes. The factory function is invoked lazily by Zustand on first
53
- * read/write, so importing this module does NOT touch the secure store at app
54
- * boot. That matters because a native secure store is unavailable during SSR
55
- * and during the test runner's module load.
22
+ * Creates the JSON `PersistStorage` consumed by Zustand's `persist` middleware
23
+ *
24
+ * The supplied API is captured without calling it, so constructing or importing this value does
25
+ * not access native storage. Read and write operations happen only when Zustand uses the storage
26
+ * object
56
27
  */
57
28
  export declare function makeSecureJsonStorage(api: SecureStoreApi): import("zustand/middleware").PersistStorage<unknown, unknown> | undefined;
58
29
  //# sourceMappingURL=secureStore.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secureStore.d.ts","sourceRoot":"","sources":["../../../src/secureStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEzE;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACrD,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAMxE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,6EAExD"}
1
+ {"version":3,"file":"secureStore.d.ts","sourceRoot":"","sources":["../../../src/secureStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACrD,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAMxE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,6EAExD"}
@@ -1,15 +1,16 @@
1
1
  import type { StateCreator } from 'zustand';
2
2
  /**
3
- * User-chosen theme mode. `'system'` defers to the OS appearance. This type
4
- * is owned here (it backs the persisted preference); the theme-rendering
5
- * package re-exports it so callers have a single source of truth.
3
+ * A user's chosen color-scheme preference
4
+ *
5
+ * `'system'` delegates the active color scheme to the consumer's operating-system integration
6
6
  */
7
7
  export type ThemePreference = 'light' | 'dark' | 'system';
8
8
  /**
9
- * The slice of app state that is common to every app and persisted across a
10
- * reload + reboot: the theme preference and the onboarding-completed flag.
11
- * Apps compose this into their bound store alongside app-specific,
12
- * runtime-only fields (auth session, bootstrap flags) which are NOT persisted.
9
+ * The persisted preferences shared by an app's bound Zustand store
10
+ *
11
+ * A slice starts with the `light` theme and incomplete onboarding when created by
12
+ * `createPreferencesSlice`. Consumers may compose additional runtime-only state into the same
13
+ * store and exclude it with `pickPersistedPreferences`
13
14
  */
14
15
  export interface PreferencesSlice {
15
16
  theme: ThemePreference;
@@ -18,21 +19,24 @@ export interface PreferencesSlice {
18
19
  setHasCompletedOnboarding: (value: boolean) => void;
19
20
  }
20
21
  /**
21
- * Zustand slice creator for the shared preferences. Spread it into a bound
22
- * store:
22
+ * Creates a composable Zustand slice for persisted theme and onboarding preferences
23
23
  *
24
- * create<AppState>()(persist((set, get, store) => ({
25
- * ...createPreferencesSlice(set, get, store),
26
- * // app-specific, runtime-only fields:
27
- * session: null,
28
- * setSession: (session) => set({ session }),
29
- * }), { name: 'app-store', storage, partialize: pickPersistedPreferences }))
24
+ * The initial values are `theme: 'light'` and `hasCompletedOnboarding: false`. Each setter updates
25
+ * only its corresponding field and does not persist state by itself
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const store = create<AppState>()(
30
+ * persist((set, get, store) => ({ ...createPreferencesSlice(set, get, store) }), options),
31
+ * )
32
+ * ```
30
33
  */
31
34
  export declare const createPreferencesSlice: StateCreator<PreferencesSlice, [], [], PreferencesSlice>;
32
35
  /**
33
- * `partialize` helper for `persist`: keep ONLY the persisted preference
34
- * fields, dropping the action functions and any app-specific runtime fields
35
- * (e.g. an auth session) that must never be written to disk.
36
+ * Selects only the serializable preferences that should be passed to Zustand `persist`
37
+ *
38
+ * The returned object always contains exactly `theme` and `hasCompletedOnboarding`. Actions and
39
+ * any additional properties on the consumer's state are excluded
36
40
  */
37
41
  export declare function pickPersistedPreferences<S extends PreferencesSlice>(state: S): Pick<PreferencesSlice, 'theme' | 'hasCompletedOnboarding'>;
38
42
  //# sourceMappingURL=preferences.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preferences.d.ts","sourceRoot":"","sources":["../../../src/preferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC,sBAAsB,EAAE,OAAO,CAAA;IAC/B,yBAAyB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACpD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,gBAAgB,EAAE,EAAE,EAAE,EAAE,EAAE,gBAAgB,CAO1F,CAAA;AAEF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,gBAAgB,EACjE,KAAK,EAAE,CAAC,GACP,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,wBAAwB,CAAC,CAE5D"}
1
+ {"version":3,"file":"preferences.d.ts","sourceRoot":"","sources":["../../../src/preferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,CAAA;IACtB,QAAQ,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;IACzC,sBAAsB,EAAE,OAAO,CAAA;IAC/B,yBAAyB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACpD;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,gBAAgB,EAAE,EAAE,EAAE,EAAE,EAAE,gBAAgB,CAO1F,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,gBAAgB,EACjE,KAAK,EAAE,CAAC,GACP,IAAI,CAAC,gBAAgB,EAAE,OAAO,GAAG,wBAAwB,CAAC,CAE5D"}
@@ -1,37 +1,9 @@
1
- /**
2
- * Pure SecureStore -> Zustand `StateStorage` bridge.
3
- *
4
- * Apps persist Zustand stores through a secure key-value store (e.g.
5
- * `expo-secure-store`) so preferences (theme, onboarding flag, pinned ids)
6
- * survive a reload AND a device reboot. The wrapping is a one-liner per
7
- * method, but the contract has two load-bearing rules worth pinning with a
8
- * test:
9
- *
10
- * 1. `getItem` MUST resolve to `null` when the key is missing. Zustand
11
- * treats `null` as «no persisted state» and falls back to the store's
12
- * initial state. SecureStore's `getItemAsync` already returns `null`
13
- * for a missing key, so the adapter just passes it through — but we
14
- * want a regression guard against accidentally translating that to
15
- * `undefined`, an empty string, or a thrown error.
16
- *
17
- * 2. `removeItem` MUST be idempotent. Logging out clears the persisted
18
- * slice, and a second logout (or a logout-on-cold-start cleanup pass)
19
- * MUST NOT throw. SecureStore's `deleteItemAsync` is idempotent, so
20
- * again the adapter just passes it through.
21
- *
22
- * The store API is INJECTED rather than imported, so this module never
23
- * touches a native module: the production call site passes the real
24
- * `expo-secure-store` functions in; tests pass an in-memory fake. That keeps
25
- * the adapter loadable (and testable) in plain Node, outside a React Native
26
- * runtime.
27
- */
28
1
  import { type StateStorage } from 'zustand/middleware';
29
2
  /**
30
- * The subset of `expo-secure-store` (or any compatible secure key-value
31
- * store) the adapter depends on. Only the three async functions Zustand's
32
- * `StateStorage` needs — `setItemAsync`, `getItemAsync`, `deleteItemAsync`.
33
- * Typed against the platform contract so a future SDK widening of the return
34
- * type fails compilation here, not at runtime.
3
+ * The SecureStore-compatible operations used by this package
4
+ *
5
+ * Implementations must preserve their own read, write, and delete semantics. In particular,
6
+ * `getItemAsync` returns `null` for a missing key only when the supplied backend does so
35
7
  */
36
8
  export interface SecureStoreApi {
37
9
  getItemAsync: (key: string) => Promise<string | null>;
@@ -39,20 +11,19 @@ export interface SecureStoreApi {
39
11
  deleteItemAsync: (key: string) => Promise<void>;
40
12
  }
41
13
  /**
42
- * Build the `StateStorage` Zustand expects from a SecureStore-shaped API.
14
+ * Adapts a SecureStore-compatible API to Zustand's raw `StateStorage` interface
43
15
  *
44
- * Kept as a thin pass-through on purpose: any logic added here (caching,
45
- * error swallowing, value transforms) would silently change the persisted
46
- * shape across all stores. If you need such behaviour, add a SECOND wrapper
47
- * around this one and document it next to the store that uses it.
16
+ * The adapter passes keys, values, resolved values, and rejected errors through unchanged. It
17
+ * does not import a native module, transform values, or make deletion idempotent when the supplied
18
+ * backend is not idempotent
48
19
  */
49
20
  export declare function makeSecureStoreAdapter(api: SecureStoreApi): StateStorage;
50
21
  /**
51
- * `createJSONStorage`-wrapped adapter — what `persist({ storage })` actually
52
- * consumes. The factory function is invoked lazily by Zustand on first
53
- * read/write, so importing this module does NOT touch the secure store at app
54
- * boot. That matters because a native secure store is unavailable during SSR
55
- * and during the test runner's module load.
22
+ * Creates the JSON `PersistStorage` consumed by Zustand's `persist` middleware
23
+ *
24
+ * The supplied API is captured without calling it, so constructing or importing this value does
25
+ * not access native storage. Read and write operations happen only when Zustand uses the storage
26
+ * object
56
27
  */
57
28
  export declare function makeSecureJsonStorage(api: SecureStoreApi): import("zustand/middleware").PersistStorage<unknown, unknown> | undefined;
58
29
  //# sourceMappingURL=secureStore.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secureStore.d.ts","sourceRoot":"","sources":["../../../src/secureStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEzE;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACrD,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAMxE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,6EAExD"}
1
+ {"version":3,"file":"secureStore.d.ts","sourceRoot":"","sources":["../../../src/secureStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACrD,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3D,eAAe,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY,CAMxE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,6EAExD"}
package/package.json CHANGED
@@ -1,10 +1,18 @@
1
1
  {
2
2
  "name": "everkit-state",
3
- "version": "0.1.0",
3
+ "version": "0.4.0",
4
4
  "description": "Reusable client-state + persistence primitives for React Native / Expo apps: a SecureStore-to-Zustand StateStorage adapter and a composable preferences (theme + onboarding) slice. The storage backend is injected by the consumer, so the core stays pure and testable outside a React Native runtime.",
5
5
  "private": false,
6
6
  "license": "MIT",
7
7
  "author": "Vladyslav Petrykov",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://gitlab.com/petrykovvladyslav/everkit-state.git"
11
+ },
12
+ "homepage": "https://gitlab.com/petrykovvladyslav/everkit-state#readme",
13
+ "bugs": {
14
+ "url": "https://gitlab.com/petrykovvladyslav/everkit-state/-/issues"
15
+ },
8
16
  "keywords": [
9
17
  "react-native",
10
18
  "expo",
@@ -43,6 +51,8 @@
43
51
  "scripts": {
44
52
  "build": "bob build",
45
53
  "prepare": "bob build",
54
+ "lint": "eslint .",
55
+ "format:check": "prettier --check .",
46
56
  "typecheck": "tsc --noEmit",
47
57
  "test": "node --require sucrase/register --test \"src/**/__tests__/**/*.test.ts\"",
48
58
  "test:watch": "node --watch --require sucrase/register --test \"src/**/__tests__/**/*.test.ts\"",
@@ -80,9 +90,18 @@
80
90
  "peerDependencies": {
81
91
  "zustand": ">=5.0.0"
82
92
  },
93
+ "engines": {
94
+ "node": ">=22"
95
+ },
83
96
  "devDependencies": {
84
97
  "@babel/core": "^7.29.0",
85
98
  "@types/node": "^22.10.0",
99
+ "eslint": "^9.25.0",
100
+ "eslint-config-expo": "~10.0.0",
101
+ "eslint-config-prettier": "^10.1.8",
102
+ "eslint-plugin-prettier": "^5.5.6",
103
+ "everkit-config": "0.3.1",
104
+ "prettier": "^3.8.4",
86
105
  "react-native-builder-bob": "^0.42.1",
87
106
  "sucrase": "^3.35.1",
88
107
  "typescript": "~5.9.2",