@stork-tools/zod-async-storage
v1.0.1
Published
Zod-validated wrapper around @react-native-async-storage/async-storage with type-safe APIs
Maintainers
Readme
@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.
✨ Features
- 🛡️ 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
📦 Installation
# Using pnpm (recommended)
pnpm add @stork-tools/zod-async-storage zod @react-native-async-storage/async-storage
# Using npm
npm install @stork-tools/zod-async-storage zod @react-native-async-storage/async-storage
# Using yarn
yarn add @stork-tools/zod-async-storage zod @react-native-async-storage/async-storage
# Using bun
bun add @stork-tools/zod-async-storage zod @react-native-async-storage/async-storage🚀 Quick Start
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: "[email protected]",
});
const user = await AsyncStorage.getItem("user"); // Type: User | null📖 API Reference
createAsyncStorage(schemas, options?)
Creates a type-safe AsyncStorage instance with validation.
Parameters
schemas:Record<string, ZodSchema>- Object mapping keys to Zod schemasoptions:GlobalOptions(optional) - Configuration options
Global Options
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
};Instance Methods
All methods maintain the same signature as AsyncStorage but with added type safety:
getItem(key, options?, callback?)
Retrieves and validates an item from storage.
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.
await AsyncStorage.setItem("user", {
id: "123",
name: "John Doe",
email: "[email protected]",
}); // ✅ Type-safe
await AsyncStorage.setItem("user", { invalid: "data" }); // ❌ TypeScript errormultiGet(keys, options?, callback?)
Retrieves multiple items with type safety for each key.
const results = await AsyncStorage.multiGet(["user", "settings"]);
// Type: [["user", User | null], ["settings", Settings | null]]multiSet(keyValuePairs, callback?)
Sets multiple items with type validation.
await AsyncStorage.multiSet([
["user", { id: "123", name: "John", email: "[email protected]" }],
["settings", { theme: "dark", notifications: true }],
]);Other Methods
removeItem(key, callback?)- Remove an itemclear(callback?)- Clear all storagegetAllKeys(callback?)- Get all keysmultiRemove(keys, callback?)- Remove multiple itemsmergeItem(key, value, callback?)- Merge with existing itemmultiMerge(keyValuePairs, callback?)- Merge multiple itemsflushGetRequests()- Flush pending get requests
🎯 Usage Examples
Basic Usage
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
}Strict Mode (Default)
By default, strict mode is enabled to prevent access to undefined keys:
const AsyncStorage = createAsyncStorage(schemas); // strict: true by default
await AsyncStorage.getItem("user"); // ✅ OK
await AsyncStorage.getItem("someUndefinedKey"); // ❌ TypeScript errorLoose Mode
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.
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
Error Handling
Configure how validation failures are handled:
// 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:
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 validationerror: The Zod validation error with detailed issuesvalue: The raw parsed value that failed validation
Note: The callback is only called for Zod schema validation failures, not for JSON parsing errors.
Working with Raw Strings
Keys without schemas work with raw strings:
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
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: "[email protected]" });
};
const updateUser = async () => {
await mergeItem({ name: "Updated Name" });
};
const clearUser = async () => {
await removeItem();
};
}Hook Methods
getItem(options?, callback?)- Retrieve item with type safetysetItem(value, callback?)- Store item with validationmergeItem(value, callback?)- Merge with existing dataremoveItem(callback?)- Remove item from storage
All methods support the same options and callbacks as the storage instance.
🔧 Advanced Configuration
Debug Mode
Enable debug logging to monitor validation failures:
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.
Quick Start for Contributors
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/stork.git cd stork - Install dependencies:
pnpm install - Create a feature branch:
git checkout -b feature/your-feature-name - Make your changes following our coding standards
- Add a changeset (for user-facing changes):
pnpm changeset - Commit and push your changes
- Open a Pull Request - CI will handle testing and validation
Types of Contributions
- 🐛 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
Key Guidelines
- Type Safety: No
anytypes, use type guards over casting - Testing: Include tests for new features and bug fixes
- Changesets: Run
pnpm changesetfor user-facing changes - Code Style: Follow existing patterns, JSDoc for public APIs
For detailed contributing guidelines, see CONTRIBUTING.md.
📝 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Zod for the excellent schema validation library
- React Native AsyncStorage for the underlying storage implementation
