Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/expo/app/(app)/(tabs)/profile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
Text,
} from '@packrat/ui/nativewindui';
import { getAppBarOptions } from '@packrat/ui/src/app-bar';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Sentry from '@sentry/react-native';
import { AndroidTabBarInsetFix } from 'expo-app/components/AndroidTabBarInsetFix';
import { Icon } from 'expo-app/components/Icon';
Expand All @@ -29,6 +28,7 @@ import { useImagePicker } from 'expo-app/features/packs/hooks/useImagePicker';
import { uploadImage } from 'expo-app/features/packs/utils/uploadImage';
import { ProfileAuthWall } from 'expo-app/features/profile/components';
import { useUpdateProfile } from 'expo-app/features/profile/hooks/useUpdateProfile';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { cn } from 'expo-app/lib/cn';
import { hasUnsyncedChanges } from 'expo-app/lib/hasUnsyncedChanges';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/app/(app)/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ActivityIndicator, Text } from '@packrat/ui/nativewindui';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Burnt from 'burnt';
import { appAlert } from 'expo-app/app/_layout';
import { Icon, type MaterialIconName } from 'expo-app/components/Icon';
Expand All @@ -18,6 +17,7 @@ import {
import { DeleteAccountButton } from 'expo-app/features/auth/components/DeleteAccountButton';
import { useAuth } from 'expo-app/features/auth/hooks/useAuth';
import { useSeasonSuggestionsPrefs } from 'expo-app/features/packs/atoms/seasonSuggestionsAtoms';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import ImageCacheManager from 'expo-app/lib/utils/ImageCacheManager';
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/app/auth/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { AlertMethods } from '@packrat/ui/nativewindui';
import { ActivityIndicator, AlertAnchor, Button, Text } from '@packrat/ui/nativewindui';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { featureFlags } from 'expo-app/config';
import {
isLoadingAtom,
needsReauthAtom,
redirectToAtom,
} from 'expo-app/features/auth/atoms/authAtoms';
import { useAuth } from 'expo-app/features/auth/hooks/useAuth';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { testIds } from 'expo-app/lib/testIds';
import { Link, router, useLocalSearchParams } from 'expo-router';
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/atoms/atomWithAsyncStorage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isFunction } from '@packrat/guards';
import AsyncStorage from '@react-native-async-storage/async-storage';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { atom } from 'jotai';

export const atomWithAsyncStorage = <T>({
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/atoms/atomWithKvStorage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isFunction } from '@packrat/guards';
import Storage from 'expo-sqlite/kv-store';
import Storage from 'expo-app/lib/expoSqliteKvStore';
import { atom } from 'jotai';

export const atomWithKvStorage = <T>({ key, initialValue }: { key: string; initialValue: T }) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/features/ai/atoms/chatStorageAtoms.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { UIMessage } from '@ai-sdk/react';
import { isObject, isString } from '@packrat/guards';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Sentry from '@sentry/react-native';
import AsyncStorage from 'expo-app/lib/asyncStorage';

export type ChatContext = {
itemId?: string;
Expand Down
10 changes: 3 additions & 7 deletions apps/expo/features/auth/hooks/useAuthActions.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import { asBoolean, asString } from '@packrat/guards';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
GoogleSignin,
isErrorWithCode,
statusCodes,
} from '@react-native-google-signin/google-signin';
import * as Sentry from '@sentry/react-native';
import { AuthClientError, toAuthError } from 'expo-app/features/auth/lib/authErrors';
import { userStore } from 'expo-app/features/auth/store';
import type { User } from 'expo-app/features/profile/types';
import * as AppleAuthentication from 'expo-app/lib/appleAuthentication';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { authClient } from 'expo-app/lib/auth-client';
import Storage from 'expo-app/lib/expoSqliteKvStore';
import { GoogleSignin, isErrorWithCode, statusCodes } from 'expo-app/lib/googleSignin';
import { t } from 'expo-app/lib/i18n';
import * as Updates from 'expo-app/lib/updates';
import ImageCacheManager from 'expo-app/lib/utils/ImageCacheManager';
import { queryClient } from 'expo-app/providers/TanstackProvider';
import { type Href, router } from 'expo-router';
import Storage from 'expo-sqlite/kv-store';
import { useAtomValue, useSetAtom } from 'jotai';
import {
isLoadingAtom,
Expand Down
6 changes: 3 additions & 3 deletions apps/expo/features/auth/hooks/useAuthInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { when } from '@legendapp/state';
import { WEIGHT_UNITS } from '@packrat/constants';
import { clientEnvs } from '@packrat/env/expo-client';
import { asBoolean, asString } from '@packrat/guards';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import * as Sentry from '@sentry/react-native';
import { userStore, userSyncState } from 'expo-app/features/auth/store';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { authClient } from 'expo-app/lib/auth-client';
import Storage from 'expo-app/lib/expoSqliteKvStore';
import { GoogleSignin } from 'expo-app/lib/googleSignin';
import { router } from 'expo-router';
import Storage from 'expo-sqlite/kv-store';
import { useEffect, useState } from 'react';
import { Platform } from 'react-native';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useSelector } from '@legendapp/state/react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Sentry from '@sentry/react-native';
import { useQuery } from '@tanstack/react-query';
import { userStore } from 'expo-app/features/auth/store/user';
import { apiClient } from 'expo-app/lib/api/packrat';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { useAuthenticatedQueryToolkit } from 'expo-app/lib/hooks/useAuthenticatedQueryToolkit';
import { useEffect, useRef, useState } from 'react';
import { trailConditionReportsStore } from '../store/trailConditionReports';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Text } from '@packrat/ui/nativewindui';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Sentry from '@sentry/react-native';
import { Icon } from 'expo-app/components/Icon';
import { SearchInput } from 'expo-app/components/SearchInput';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { cn } from 'expo-app/lib/cn';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/features/wildlife/atoms/wildlifeAtoms.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { atomWithStorage, createJSONStorage, loadable } from 'jotai/utils';
import type { WildlifeIdentification } from '../types';

Expand Down
5 changes: 5 additions & 0 deletions apps/expo/lib/asyncStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @react-native-async-storage/async-storage ships a localStorage-backed web
// implementation out of the box, so a single re-export works on every
// platform. This wrapper exists so call sites never import the native module
// path directly (per the lib/ wrapper convention).
export { default } from '@react-native-async-storage/async-storage';
5 changes: 5 additions & 0 deletions apps/expo/lib/expoSqliteKvStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Default export is the `Storage` singleton from expo-sqlite/kv-store, an
// AsyncStorage-compatible store with extra synchronous methods (getItemSync,
// setItemSync). The native module has no working web build, so .web.ts backs
// the same surface with localStorage.
export { default } from 'expo-sqlite/kv-store';
61 changes: 61 additions & 0 deletions apps/expo/lib/expoSqliteKvStore.web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// expo-sqlite/kv-store relies on the native SQLite module, which has no usable
// web build. Back the same API (async aliases + sync variants + getAllKeys)
// with localStorage so callers need no Platform branches on web.

const getItemSync = (key: string): string | null => {
try {
return localStorage.getItem(key);
} catch {
return null;
}
};

const setItemSync = (key: string, value: string): void => {
localStorage.setItem(key, value);
};
Comment on lines +13 to +15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for setItemSync calls with function arguments.

# Test: Look for setItemSync calls passing callbacks or arrow functions.
rg -nP --type=ts --type=tsx 'setItemSync\s*\(\s*[^,]+,\s*(\(|function)' apps/expo/

Repository: PackRat-AI/PackRat

Length of output: 90


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the target web shim around the reported lines
echo "---- File head (apps/expo/lib/expoSqliteKvStore.web.ts) ----"
nl -ba apps/expo/lib/expoSqliteKvStore.web.ts | sed -n '1,120p'

# 2) Find the declaration(s) of setItemSync
echo
echo "---- setItemSync declarations ----"
rg -n --hidden --no-ignore -S "setItemSync\s*=" apps/expo

# 3) Search for any calls that pass a function as the second argument
echo
echo "---- setItemSync calls passing function/arrow as 2nd arg ----"
rg -n --hidden --no-ignore -S "setItemSync\s*\(\s*[^,]+,\s*(\(|function|async\s*\()" apps/expo -g'*.ts' -g'*.tsx'

# 4) Also search for setItemSync calls generally (to see how it's used)
echo
echo "---- setItemSync call sites (general) ----"
rg -n --hidden --no-ignore -S "setItemSync\s*\(" apps/expo -g'*.ts' -g'*.tsx' | head -n 200

Repository: PackRat-AI/PackRat

Length of output: 184


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show file with line numbers (without nl)
echo "---- File head (apps/expo/lib/expoSqliteKvStore.web.ts) ----"
awk '{printf "%d\t%s\n", NR, $0}' apps/expo/lib/expoSqliteKvStore.web.ts | sed -n '1,160p'

echo
echo "---- setItemSync declarations ----"
rg -n --hidden --no-ignore -S "setItemSync\s*=" apps/expo -g'*.ts' -g'*.tsx'

echo
echo "---- setItemSync calls passing function/arrow as 2nd arg ----"
rg -n --hidden --no-ignore -S 'setItemSync\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' apps/expo -g'*.ts' -g'*.tsx'

echo
echo "---- setItemSync call sites (general) ----"
rg -n --hidden --no-ignore -S 'setItemSync\s*\(' apps/expo -g'*.ts' -g'*.tsx' | head -n 200

Repository: PackRat-AI/PackRat

Length of output: 2279


🏁 Script executed:

#!/bin/bash
set -u
file="apps/expo/lib/expoSqliteKvStore.web.ts"

echo "---- $file (relevant snippet) ----"
awk 'NR>=1 && NR<=120 {printf "%d\t%s\n", NR, $0}' "$file"

echo
echo "---- setItemSync calls passing function/arrow as 2nd arg (should be empty) ----"
rg -n --hidden --no-ignore -S 'setItemSync\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' apps/expo -g'*.ts' -g'*.tsx' || true

echo
echo "---- setItemSync call sites (2nd arg types) ----"
rg -n --hidden --no-ignore -S 'setItemSync\s*\(' apps/expo -g'*.ts' -g'*.tsx' || true

echo
echo "---- Storage.setItemSync call sites ----"
rg -n --hidden --no-ignore -S 'Storage\.(setItemSync)\s*\(' apps/expo -g'*.ts' -g'*.tsx' || true

echo
echo "---- Any UpdateFn-like usage for setItemSync ----"
rg -n --hidden --no-ignore -S '(UpdateFn|prev\s*=>|prev\s*\))' apps/expo -g'*.ts' -g'*.tsx' || true

Repository: PackRat-AI/PackRat

Length of output: 6353


🏁 Script executed:

#!/bin/bash
set -u

echo "---- apps/expo/lib/kvStorage.ts ----"
awk 'NR>=1 && NR<=200 {printf "%d\t%s\n", NR, $0}' apps/expo/lib/kvStorage.ts

echo
echo "---- apps/expo/mocks/expo-sqlite-kv-store.ts (UpdateFn + setItemSync/setItem patterns) ----"
awk 'NR>=1 && NR<=220 {printf "%d\t%s\n", NR, $0}' apps/expo/mocks/expo-sqlite-kv-store.ts

echo
echo "---- Call sites: setItemSync with function/arrow as 2nd arg (should be empty) ----"
rg -n --hidden --no-ignore -S 'setItemSync\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' apps/expo -g'*.ts' -g'*.tsx' || true

echo
echo "---- Call sites: Storage.setItemSync with function/arrow as 2nd arg ----"
rg -n --hidden --no-ignore -S 'Storage\.setItemSync\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' apps/expo -g'*.ts' -g'*.tsx' || true

echo
echo "---- Call sites: setItem* with function/arrow as 2nd arg (any) ----"
rg -n --hidden --no-ignore -S 'setItem(?:Async|Sync)?\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' apps/expo -g'*.ts' -g'*.tsx' || true

echo
echo "---- Call sites: Storage.setItem* with function/arrow as 2nd arg ----"
rg -n --hidden --no-ignore -S 'Storage\.setItem(?:Async|Sync)?\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' apps/expo -g'*.ts' -g'*.tsx' || true

echo
echo "---- Call sites: setItem* where 2nd arg is an arrow function specifically (prev => ...) ----"
rg -n --hidden --no-ignore -S 'setItem(?:Async|Sync)?\s*\(\s*[^,]+,\s*[^,=]*=>\s*' apps/expo -g'*.ts' -g'*.tsx' || true

Repository: PackRat-AI/PackRat

Length of output: 5905


🏁 Script executed:

#!/bin/bash
set -u

echo "---- Find UpdateFn definitions/usages in apps/expo/lib ----"
rg -n --hidden --no-ignore -S "UpdateFn" apps/expo/lib

echo
echo "---- Inspect expoSqliteKvStore type surface (files named expoSqliteKvStore.*) ----"
fd -a -t f "expoSqliteKvStore" apps/expo/lib apps/expo -E node_modules

echo
echo "---- Print expoSqliteKvStore files (small regions around setItem signatures) ----"
for f in $(fd -a -t f "expoSqliteKvStore" apps/expo/lib apps/expo -E node_modules); do
  echo "===== $f (setItem signatures) ====="
  rg -n --hidden --no-ignore -S "setItem(Async|Sync)?\s*\(|setItem\s*:\s*|UpdateFn|prevValue" "$f" || true
  echo "===== $f (around setItemSync) ====="
  rg -n --hidden --no-ignore -S "setItemSync" "$f" | head -n 20 || true
done

Repository: PackRat-AI/PackRat

Length of output: 2007


🏁 Script executed:

#!/bin/bash
set -u

file="apps/expo/lib/expoSqliteKvStore.ts"

echo "---- $file (first 220 lines) ----"
awk 'NR<=220 {printf "%d\t%s\n", NR, $0}' "$file" | head -n 220

echo
echo "---- $file: lines containing setItem/setItemSync/UpdateFn ----"
rg -n --hidden --no-ignore -S "setItemSync|setItemAsync|setItem\s*\(|UpdateFn|prevValue|UpdateFn|value:\s*string" "$file"

Repository: PackRat-AI/PackRat

Length of output: 626


🏁 Script executed:

#!/bin/bash
set -u
echo "---- Locate expo-sqlite/kv-store type files ----"
rg -n --hidden --no-ignore -S "expo-sqlite/kv-store" package.json apps/expo -g'package.json' -g'*.ts' -g'*.tsx' || true

echo
echo "---- Search node_modules for UpdateFn and setItem signatures ----"
rg -n --hidden --no-ignore -S "type UpdateFn|interface.*UpdateFn|UpdateFn\s*=\s*\(|UpdateFn" node_modules -g'*.ts' -g'*.d.ts' -g'*.tsx' 2>/dev/null | head -n 50 || true

echo
rg -n --hidden --no-ignore -S "setItemSync\s*\(" node_modules -g'*.d.ts' -g'*.ts' 2>/dev/null | head -n 50 || true

echo
rg -n --hidden --no-ignore -S "setItem\s*\(" node_modules -g'*.d.ts' -g'*.ts' 2>/dev/null | head -n 50 || true

echo
echo "---- If present, print the kv-store .d.ts around setItem/setItemSync ----"
kvpath=$(rg -l --hidden --no-ignore -S "setItemSync\s*\(" node_modules -g'*.d.ts' 2>/dev/null | rg -m 1 "kv-store" || true)
echo "kvpath=$kvpath"
if [ -n "${kvpath:-}" ]; then
  sed -n '1,200p' "$kvpath" | cat
fi

Repository: PackRat-AI/PackRat

Length of output: 581


🏁 Script executed:

#!/bin/bash
set -u

echo "---- setItemSync calls passing function/arrow as 2nd arg (repo-wide) ----"
rg -n --hidden --no-ignore -S 'setItemSync\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' . -g'*.ts' -g'*.tsx' || true

echo
echo "---- setItemAsync calls passing function/arrow as 2nd arg (repo-wide) ----"
rg -n --hidden --no-ignore -S 'setItemAsync\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' . -g'*.ts' -g'*.tsx' || true

echo
echo "---- Storage.setItemSync / Storage.setItemAsync / Storage.setItem calls passing arrow/function (repo-wide) ----"
rg -n --hidden --no-ignore -S '(Storage|AsyncStorage)\.setItem(?:Sync|Async)?\s*\(\s*[^,]+,\s*(\(|function|async\s*\()' . -g'*.ts' -g'*.tsx' || true

echo
echo "---- setItem* where 2nd arg contains an arrow '=>' (repo-wide; may include other setState calls) ----"
rg -n --hidden --no-ignore -S 'setItem(?:Sync|Async)?\s*\(\s*[^,]+,\s*\(?\s*[^)]*=>\s*' . -g'*.ts' -g'*.tsx' || true

Repository: PackRat-AI/PackRat

Length of output: 428


Align web setItemSync with UpdateFn-capable kv-store contract

No call sites pass a functional updater to setItemSync/setItemAsync/setItem in this repo, so nothing breaks today. However, apps/expo/mocks/expo-sqlite-kv-store.ts supports setItemSync(key, value: string | UpdateFn) while apps/expo/lib/expoSqliteKvStore.web.ts only accepts value: string, creating a contract mismatch—add the UpdateFn overload to the web shim and derive the final value from getItemSync(key) when value is a function.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/lib/expoSqliteKvStore.web.ts` around lines 13 - 15, The web shim
setItemSync currently only accepts a string but must mirror the kv-store
contract that allows an UpdateFn; update setItemSync (and similarly
setItemAsync/setItem shims if present) to accept value: string | UpdateFn,
detect when value is a function, call getItemSync(key) to obtain the current
string (treat null/undefined as null), invoke the UpdateFn with that current
value to compute the new string, and then call localStorage.setItem(key,
finalValue); ensure the function signature/overload reflects UpdateFn and that
non-function values are stored unchanged.


const removeItemSync = (key: string): void => {
localStorage.removeItem(key);
};

const getAllKeysSync = (): string[] => {
const keys: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key !== null) keys.push(key);
}
return keys;
};

const Storage = {
getItem: (key: string): Promise<string | null> => Promise.resolve(getItemSync(key)),
setItem: (key: string, value: string): Promise<void> => {
setItemSync(key, value);
return Promise.resolve();
},
removeItem: (key: string): Promise<void> => {
removeItemSync(key);
return Promise.resolve();
},
getAllKeys: (): Promise<string[]> => Promise.resolve(getAllKeysSync()),
clear: (): Promise<void> => {
localStorage.clear();
return Promise.resolve();
Comment on lines +41 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: clear() destroys all localStorage data, not just KV store scope.

Native clearSync() only removes keys in the isolated SQLite table. This web shim calls localStorage.clear(), which wipes everything in localStorage—breaking unrelated features that persist data there.

🛡️ Proposed fix to scope clear() to managed keys

Track a prefix or registry of managed keys. For now, iterate all keys and remove only those set via this Storage instance:

+const STORAGE_PREFIX = '__expo_kv_';
+
 const setItemSync = (key: string, value: string): void => {
-  localStorage.setItem(key, value);
+  localStorage.setItem(STORAGE_PREFIX + key, value);
 };

 const removeItemSync = (key: string): void => {
-  localStorage.removeItem(key);
+  localStorage.removeItem(STORAGE_PREFIX + key);
 };

 const getAllKeysSync = (): string[] => {
   const keys: string[] = [];
   for (let i = 0; i < localStorage.length; i++) {
     const key = localStorage.key(i);
-    if (key !== null) keys.push(key);
+    if (key !== null && key.startsWith(STORAGE_PREFIX)) {
+      keys.push(key.slice(STORAGE_PREFIX.length));
+    }
   }
   return keys;
 };

 // ... in Storage object:
   clear: (): Promise<void> => {
-    localStorage.clear();
+    const keys = getAllKeysSync();
+    keys.forEach(k => removeItemSync(k));
     return Promise.resolve();
   },

Adjust getItemSync similarly to prepend prefix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/lib/expoSqliteKvStore.web.ts` around lines 41 - 43, The clear()
implementation currently calls localStorage.clear(), wiping all keys; change it
to only remove keys managed by this KV store by using a unique prefix (or a
stored registry) for this Storage instance and iterating through localStorage
keys to remove only those that start with that prefix; update clear (and any
removeItem/getItem/getItemSync wrappers like clear:(), getItemSync,
setItem/removeItem) to add/remove/lookup keys with that prefix so no unrelated
localStorage entries are deleted, and ensure any existing getItemSync calls
prepend the same prefix when reading.

},
getItemAsync: (key: string): Promise<string | null> => Promise.resolve(getItemSync(key)),
setItemAsync: (key: string, value: string): Promise<void> => {
setItemSync(key, value);
return Promise.resolve();
},
removeItemAsync: (key: string): Promise<boolean> => {
removeItemSync(key);
return Promise.resolve(true);
},
getAllKeysAsync: (): Promise<string[]> => Promise.resolve(getAllKeysSync()),
getItemSync,
setItemSync,
removeItemSync,
getAllKeysSync,
};

export default Storage;
9 changes: 9 additions & 0 deletions apps/expo/lib/googleSignin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export {
GoogleSignin,
GoogleSigninButton,
isCancelledResponse,
isErrorWithCode,
isNoSavedCredentialFoundResponse,
isSuccessResponse,
statusCodes,
} from '@react-native-google-signin/google-signin';
34 changes: 34 additions & 0 deletions apps/expo/lib/googleSignin.web.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @react-native-google-signin/google-signin is a native module with no usable
// web implementation. Native Google Sign-In is not wired up for web (web auth
// uses a different OAuth flow), so this stub keeps the bundle building and
// fails loudly only if a web caller actually tries to trigger native sign-in.

const notAvailable = (): never => {
throw new Error('Native Google Sign-In is not available on web.');
};

export const GoogleSignin = {
configure: () => {},
hasPlayServices: () => Promise.resolve(false),
signIn: notAvailable,
signInSilently: notAvailable,
getTokens: notAvailable,
hasPreviousSignIn: () => false,
signOut: () => Promise.resolve(null),
revokeAccess: () => Promise.resolve(null),
getCurrentUser: () => null,
};

export const statusCodes = {
SIGN_IN_CANCELLED: 'SIGN_IN_CANCELLED',
IN_PROGRESS: 'IN_PROGRESS',
PLAY_SERVICES_NOT_AVAILABLE: 'PLAY_SERVICES_NOT_AVAILABLE',
SIGN_IN_REQUIRED: 'SIGN_IN_REQUIRED',
} as const;

export const isErrorWithCode = (_error: unknown): boolean => false;
export const isSuccessResponse = (_response: unknown): boolean => false;
export const isCancelledResponse = (_response: unknown): boolean => false;
export const isNoSavedCredentialFoundResponse = (_response: unknown): boolean => false;

export const GoogleSigninButton = (): null => null;
2 changes: 1 addition & 1 deletion apps/expo/lib/kvStorage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Storage from 'expo-sqlite/kv-store';
import Storage from 'expo-app/lib/expoSqliteKvStore';

export default {
getItem: (key: string): string | null => Storage.getItemSync(key),
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/lib/kvStorage.web.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import AsyncStorage from 'expo-app/lib/asyncStorage';

export default {
getItem: (key: string) => AsyncStorage.getItem(key),
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/lib/persist-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { observablePersistSqlite } from '@legendapp/state/persist-plugins/expo-sqlite';
import Storage from 'expo-sqlite/kv-store';
import Storage from 'expo-app/lib/expoSqliteKvStore';

export const persistPlugin = observablePersistSqlite(Storage);
2 changes: 1 addition & 1 deletion apps/expo/lib/persist-plugin.web.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { observablePersistAsyncStorage } from '@legendapp/state/persist-plugins/async-storage';
import AsyncStorage from '@react-native-async-storage/async-storage';
import AsyncStorage from 'expo-app/lib/asyncStorage';

// On web, the expo-sqlite persist plugin requires SharedArrayBuffer (COEP/COOP
// headers). Use the AsyncStorage plugin instead, which falls through to our
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/utils/storage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import type { WeatherLocation } from 'expo-app/features/weather/types';
import AsyncStorage from 'expo-app/lib/asyncStorage';
import { createJSONStorage } from 'jotai/utils';

// Create a storage adapter for Jotai that uses AsyncStorage
Expand Down
Loading
Loading