Zod-validated wrapper around @react-native-async-storage/async-storage with type-safe APIs
npm install @stork-tools/zod-async-storage


A type-safe and validated wrapper around @react-native-async-storage/async-storage using Zod schemas. Enjoy the benefits of runtime validation, automatic type inference, and better developer experience when working with AsyncStorage in React Native and Expo applications. This library is a drop-in replacement for @react-native-async-storage/async-storage with added type safety.
- ๐ก๏ธ Type Safety: Full TypeScript support with automatic type inference from Zod schemas
- ๐ Drop-in Replacement: Maintains the same API as AsyncStorage with added type safety
- ๐ Incremental adoption: You can start with a single schema and add more later
- โ
Runtime Validation: Automatic validation of stored/retrieved data using Zod schemas
- ๐ Strict Mode: Strict mode enabled by default to prevent access to undefined keys
- ๐งน Error Handling: Configurable behavior for invalid data (clear or throw)
- ๐ Zero Runtime Overhead: Only validates data when schemas are provided
- ๐ฑ React Native & Expo: Compatible with both React Native and Expo projects
``bashUsing pnpm (recommended)
pnpm add @stork-tools/zod-async-storage zod @react-native-async-storage/async-storage
๐ Quick Start
`ts
import { z } from "zod";
import { createAsyncStorage } from "@stork-tools/zod-async-storage";// Define your schemas
const schemas = {
user: z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
}),
settings: z.object({
theme: z.enum(["light", "dark"]),
notifications: z.boolean(),
}),
};
// Create a single instance of type-safe storage
export const AsyncStorage = createAsyncStorage(schemas);
import { AsyncStorage } from "~/async-storage";
// Use with full type safety
await AsyncStorage.setItem("user", {
id: "123",
name: "John Doe",
email: "john@example.com",
});
const user = await AsyncStorage.getItem("user"); // Type: User | null
`๐ API Reference
$3
Creates a type-safe AsyncStorage instance with validation.
#### Parameters
-
schemas: Record - Object mapping keys to Zod schemas
- options: GlobalOptions (optional) - Configuration options#### Global Options
`ts
type GlobalOptions = {
strict?: boolean; // Enforce only defined keys (default: true)
onFailure?: "clear" | "throw"; // Handle zod validation failures (default: "clear")
debug?: boolean; // Enable debug logging (default: false)
onValidationError?: (key: string, error: z.ZodError, value: unknown) => void; // Callback on validation failure
};
`$3
All methods maintain the same signature as AsyncStorage but with added type safety:
####
getItem(key, options?, callback?)Retrieves and validates an item from storage.
`ts
const user = await AsyncStorage.getItem("user");
// Type: { id: string; name: string; email: string } | null// Per-operation options
const user = await AsyncStorage.getItem("user", { onFailure: "throw" });
`####
setItem(key, value, callback?)Stores an item with automatic serialization and type validation.
`ts
await AsyncStorage.setItem("user", {
id: "123",
name: "John Doe",
email: "john@example.com",
}); // โ
Type-safeawait AsyncStorage.setItem("user", { invalid: "data" }); // โ TypeScript error
`####
multiGet(keys, options?, callback?)Retrieves multiple items with type safety for each key.
`ts
const results = await AsyncStorage.multiGet(["user", "settings"]);
// Type: [["user", User | null], ["settings", Settings | null]]
`####
multiSet(keyValuePairs, callback?)Sets multiple items with type validation.
`ts
await AsyncStorage.multiSet([
["user", { id: "123", name: "John", email: "john@example.com" }],
["settings", { theme: "dark", notifications: true }],
]);
`#### Other Methods
-
removeItem(key, callback?) - Remove an item
- clear(callback?) - Clear all storage
- getAllKeys(callback?) - Get all keys
- multiRemove(keys, callback?) - Remove multiple items
- mergeItem(key, value, callback?) - Merge with existing item
- multiMerge(keyValuePairs, callback?) - Merge multiple items
- flushGetRequests() - Flush pending get requests๐ฏ Usage Examples
$3
`ts
import { z } from "zod";
import { createAsyncStorage } from "@stork-tools/zod-async-storage";const schemas = {
user: z.object({
id: z.string(),
name: z.string(),
preferences: z.object({
theme: z.enum(["light", "dark"]),
language: z.string(),
}),
}),
};
// Create a single instance of type-safe storage
export const AsyncStorage = createAsyncStorage(schemas);
import { AsyncStorage } from "~/async-storage";
// Set data
await AsyncStorage.setItem("user", {
id: "u1",
name: "Alice",
preferences: {
theme: "dark",
language: "en",
},
});
// Get data (fully typed)
const user = await AsyncStorage.getItem("user");
if (user) {
console.log(user.preferences.theme); // TypeScript knows this exists
}
`$3
By default, strict mode is enabled to prevent access to undefined keys:
`ts
const AsyncStorage = createAsyncStorage(schemas); // strict: true by defaultawait AsyncStorage.getItem("user"); // โ
OK
await AsyncStorage.getItem("someUndefinedKey"); // โ TypeScript error
`$3
Disable strict mode to allow access to any key while maintaining type safety for schema-defined keys. This is useful if you are migrating to
@stork-tools/zod-async-storage and want to maintain access to keys that are not yet defined in schemas.`ts
const AsyncStorage = createAsyncStorage(schemas, { strict: false });await AsyncStorage.getItem("user"); // Type: User | null (validated)
await AsyncStorage.getItem("any-key"); // Type: string | null (loose autocomplete, no validation)
`With
strict: false, you get:
- Loose autocomplete: Access any string key
- Type-safe returns: Keys matching schemas return validated types
- Raw string fallback: Unknown keys return string | null$3
Configure how validation failures are handled:
`ts
// Clear invalid data (default)
const AsyncStorage = createAsyncStorage(schemas, { onFailure: "clear" });// Throw errors on invalid data
const AsyncStorage = createAsyncStorage(schemas, { onFailure: "throw" });
// Per-operation override
const user = await AsyncStorage.getItem("user", { onFailure: "throw" });
`#### Validation Error Callbacks
Get notified when validation fails using the
onValidationError callback:`ts
const AsyncStorage = createAsyncStorage(schemas, {
onFailure: "clear",
onValidationError: (key, error, value) => {
// Log validation failures for monitoring
console.warn(Validation failed for key "${key}":, error.message);
// Send to analytics
analytics.track('validation_error', {
key,
errors: error.issues,
invalidValue: value
});
}
});// Per-operation callback override
const user = await AsyncStorage.getItem("user", {
onValidationError: (key, error, value) => {
// Handle this specific validation error differently
showUserErrorMessage(
Invalid user data: ${error.message});
}
});
`The callback receives:
-
key: The storage key that failed validation
- error: The Zod validation error with detailed issues
- value: The raw parsed value that failed validationNote: The callback is only called for Zod schema validation failures, not for JSON parsing errors.
$3
Keys without schemas work with raw strings:
`ts
const schemas = {
user: z.object({ name: z.string() }),
// 'token' has no schema
};const AsyncStorage = createAsyncStorage(schemas);
await AsyncStorage.setItem("user", { name: "John" }); // Validated object
await AsyncStorage.setItem("token", "abc123"); // Raw string
`
๐ช React Hooks
`ts
import { createAsyncStorage, createUseAsyncStorage } from "@stork-tools/zod-async-storage";const AsyncStorage = createAsyncStorage(schemas);
const { useAsyncStorage } = createUseAsyncStorage(AsyncStorage);
function UserProfile() {
const { getItem, setItem, mergeItem, removeItem } = useAsyncStorage("user");
const loadUser = async () => {
const user = await getItem(); // Type: User | null
};
const saveUser = async () => {
await setItem({ id: "123", name: "John", email: "john@example.com" });
};
const updateUser = async () => {
await mergeItem({ name: "Updated Name" });
};
const clearUser = async () => {
await removeItem();
};
}
`$3
-
getItem(options?, callback?) - Retrieve item with type safety
- setItem(value, callback?) - Store item with validation
- mergeItem(value, callback?) - Merge with existing data
- removeItem(callback?) - Remove item from storageAll methods support the same options and callbacks as the storage instance.
๐ง Advanced Configuration
$3
Enable debug logging to monitor validation failures:
`ts
export const AsyncStorage = createAsyncStorage(schemas, {
debug: true,
onFailure: "clear",
});// When invalid data is found and cleared, you'll see:
// console.warn("Cleared invalid item", key);
`๐ค Contributing
We welcome contributions from the community! Whether you're fixing bugs, adding features, improving documentation, or sharing feedback, your help makes this project better.
$3
1. Fork the repository on GitHub
2. Clone your fork locally:
`bash
git clone https://github.com/YOUR_USERNAME/stork.git
cd stork
`
3. Install dependencies:
`bash
pnpm install
`
4. Create a feature branch:
`bash
git checkout -b feature/your-feature-name
`
5. Make your changes following our coding standards
6. Add a changeset (for user-facing changes):
`bash
pnpm changeset
`
7. Commit and push your changes
8. Open a Pull Request - CI will handle testing and validation$3
- ๐ Bug Reports: Use our bug report template
- โจ Feature Requests: Use our feature request template
- ๐ป Code Contributions: Follow our coding standards and include tests
- ๐ Documentation: Help improve our docs and examples
- ๐งช Testing: Add or improve test coverage
- ๐ฌ Discussions: Share ideas in GitHub Discussions
$3
- Type Safety: No
any types, use type guards over casting
- Testing: Include tests for new features and bug fixes
- Changesets: Run pnpm changeset` for user-facing changesFor detailed contributing guidelines, see CONTRIBUTING.md.
This project is licensed under the MIT License - see the LICENSE file for details.
- Zod for the excellent schema validation library
- React Native AsyncStorage for the underlying storage implementation