
Security News
AI Agent Lands PRs in Major OSS Projects, Targets Maintainers via Cold Outreach
An AI agent is merging PRs into major OSS projects and cold-emailing maintainers to drum up more work.
Simple, minimal library for encapsulating localStorage logic.
Kvozy separates storage logic from React integration:
This architecture makes it easy to add connectors for other frameworks (Vue, Svelte, Angular, etc.) in the future.
npm install kvozy
⚠️ Note:
bindValueis an internal API. UseuseStoragefor React components. Direct usage ofbindValueis not recommended.
import { bindValue, useStorage } from 'kvozy';
// Define the binding with type, serialize, deserialize, and defaultValue
const myValue = bindValue<string>({
key: 'my-key',
defaultValue: '',
serialize: (v) => v,
deserialize: (s) => s,
});
// Use in component
const Component = () => {
const { value, setValue } = useStorage(myValue);
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
};
In-memory storage provides a graceful fallback when persistent storage is unavailable, such as:
// This code works even if localStorage is unavailable (e.g., incognito mode)
const bindedValue = bindValue({
key: "some-key",
storage: localStorage ?? undefined,
});
// Falls back to in-memory storage, so your code continues to work
The in-memory storage ensures your application remains functional, maintaining session state without throwing errors or breaking your user experience.
⚠️ Note:
bindValueis an internal API. UseuseStoragefor React components. Direct usage ofbindValueis not recommended.
Options for creating a BindValue instance.
Parameters:
key (string, required) - Storage keydefaultValue (T, required) - Default value when key doesn't exist or deserialize failsserialize (function, required) - Convert value to string: (value: T) => stringdeserialize (function, required) - Convert string to value: (serialized: string) => Tstorage (Storage, optional) - localStorage, sessionStorage, or undefined for in-memoryversion (string, optional) - Schema version for migration supportmigrate (function, optional) - Migration function: (oldSerialized: string, oldVersion: string | undefined) => TReact hook that connects a BindValue instance to React state.
Parameters:
binding (BindValue, required) - binding instance from bindValueReturns: { value, setValue }
value - T - current value from storagesetValue - (value: T) => void - function to update valueBehavior:
subscribe() does NOT call callback immediately when subscribingset()serialize() fails, in-memory value is kept but storage is NOT updateddeserialize() fails, defaultValue is returnedExample:
const Component = () => {
const { value, setValue } = useStorage(myBinding);
return <div>
<p>Current value: {value}</p>
<button onClick={() => setValue('new value')}>
Update Value
</button>
</div>;
};
⚠️ Note:
bindValueNSis an internal API. UseuseStorageNSfor React components. Direct usage ofbindValueNSis not recommended.
Options for creating a namespace binder. A namespace allows grouping related keys with a shared prefix and configuration.
Parameters:
prefix (string, required) - Prefix for all keys in this namespace (cannot be empty or whitespace)defaultValue (T, required) - Default value shared across all keys in namespaceserialize (function, required) - Convert value to string: (value: T) => stringdeserialize (function, required) - Convert string to value: (serialized: string) => Tstorage (Storage, optional) - localStorage, sessionStorage, or undefined for in-memoryversion (string, optional) - Schema version for migration supportmigrate (function, optional) - Migration function: (oldSerialized: string, oldVersion: string | undefined) => TFactory function for creating a namespace binder.
Returns: BindValueNS<T> instance
Behavior:
${prefix}\x1F${key} using Unit SeparatorCreates a individual binding for a specific key within the namespace.
Parameters:
key (string, required) - Key for this specific bindingReturns: BindValue<T> instance
Behavior:
${prefix}\x1F${key}React hook that connects a namespace to React state with a specific key.
Parameters:
namespace (BindValueNS, required) - Namespace instanceoptions: { key: string } - Key for this specific bindingReturns: { value, setValue }
value - T - current value from storagesetValue - (value: T) => void - function to update valueBehavior:
namespace.bind(options.key)useStorage hookExample:
const Component = () => {
const { value, setValue } = useStorage(myBinding);
return <div>
<p>Current value: {value}</p>
<button onClick={() => setValue('new value')}>
Update Value
</button>
</div>;
};
Kvozy provides type-specific shortcuts for common types, eliminating boilerplate and providing sensible defaults. Each shortcut pre-defines serialization/deserialization logic while allowing custom defaults and full feature support.
| Shortcut | Type | Default Value | Storage Format |
|---|---|---|---|
bindStringValue | string | "" | String as-is |
bindNumberValue | number | 0 | Decimal string |
bindBooleanValue | boolean | false | "true" or "false" |
bindJSONValue<T> | T | User must provide | JSON string |
bindEnumValue<E> | E (enum) | User must provide | String/number as-is |
For string values with identity serialization.
import { bindStringValue, useStorage } from "kvozy";
// Default empty string
const nameBinding = bindStringValue({
key: "name",
});
// Custom default value
const greetingBinding = bindStringValue({
key: "greeting",
defaultValue: "Hello",
});
// With versioning
const themeBinding = bindStringValue({
key: "theme",
defaultValue: "light",
version: "2.0.0",
migrate: (old, oldVersion) => {
return "dark";
},
});
For numeric values.
import { bindNumberValue, useStorage } from "kvozy";
// Default zero
const counterBinding = bindNumberValue({
key: "counter",
});
// Custom default value
const priceBinding = bindNumberValue({
key: "price",
defaultValue: 9.99,
});
// With storage
const scoreBinding = bindNumberValue({
key: "score",
defaultValue: 100,
storage: sessionStorage,
});
For boolean values. Stores as "true" or "false" for readability in devtools.
import { bindBooleanValue, useStorage } from "kvozy";
// Default false
const enabledBinding = bindBooleanValue({
key: "enabled",
});
// Custom default value
const notificationsBinding = bindBooleanValue({
key: "notifications",
defaultValue: true,
});
For complex objects and arrays. Requires a default value.
import { bindJSONValue, useStorage } from "kvozy";
interface User {
name: string;
age: number;
}
// Objects
const userBinding = bindJSONValue<User>({
key: "user",
defaultValue: { name: "", age: 0 },
});
// Arrays
const tagsBinding = bindJSONValue<string[]>({
key: "tags",
defaultValue: [],
});
// Nested structures
const configBinding = bindJSONValue<{
theme: { primary: string; secondary: string };
features: string[];
}>({
key: "config",
defaultValue: {
theme: { primary: "#000", secondary: "#fff" },
features: [],
},
});
For TypeScript enums. Works with both string and number enums.
import { bindEnumValue, useStorage } from "kvozy";
// String enum
enum Color {
Red = "red",
Green = "green",
Blue = "blue",
}
const colorBinding = bindEnumValue<Color>({
key: "color",
defaultValue: Color.Blue,
});
// Number enum
enum Priority {
Low = 1,
Medium = 2,
High = 3,
}
const priorityBinding = bindEnumValue<Priority>({
key: "priority",
defaultValue: Priority.Low,
});
// With migration
const statusBinding = bindEnumValue<Color>({
key: "status",
defaultValue: Color.Red,
version: "2.0.0",
migrate: (old, oldVersion) => {
return Color.Blue;
},
});
Kvozy provides namespace shortcuts for common types, eliminating boilerplate while maintaining the benefits of namespace-based key organization.
| Shortcut | Type | Default Value | Storage Format |
|---|---|---|---|
bindStringValueNS | string | "" | String as-is |
bindNumberValueNS | number | 0 | Decimal string |
bindBooleanValueNS | boolean | false | "true" or "false" |
bindJSONValueNS<T> | T | User must provide | JSON string |
bindEnumValueNS<E> | E (enum) | User must provide | String/number as-is |
For string values with identity serialization.
import { bindStringValueNS, useStorageNS } from "kvozy";
// Default empty string
const appNS = bindStringValueNS({
prefix: "app",
storage: localStorage,
});
// With custom default value
const userNS = bindStringValueNS({
prefix: "user",
defaultValue: "guest",
storage: localStorage,
});
// Use in components
const Component = () => {
const { value: name, setValue: setName } = useStorageNS(userNS, { key: "name" });
return <input value={name} onChange={(e) => setName(e.target.value)} />;
};
For numeric values.
import { bindNumberValueNS, useStorageNS } from "kvozy";
const counterNS = bindNumberValueNS({
prefix: "counters",
storage: localStorage,
});
const { value: count, setValue: setCount } = useStorageNS(counterNS, {
key: "views",
});
For boolean values. Stores as "true" or "false" for readability in devtools.
import { bindBooleanValueNS, useStorageNS } from "kvozy";
const settingsNS = bindBooleanValueNS({
prefix: "settings",
storage: localStorage,
});
const { value: enabled, setValue: setEnabled } = useStorageNS(settingsNS, {
key: "notifications",
});
For complex objects and arrays. Requires a default value.
import { bindJSONValueNS, useStorageNS } from "kvozy";
interface User {
name: string;
email: string;
}
const userNS = bindJSONValueNS<User>({
prefix: "users",
defaultValue: { name: "", email: "" },
storage: localStorage,
});
const { value: user, setValue: setUser } = useStorageNS(userNS, {
key: "current",
});
For TypeScript enums. Works with both string and number enums.
import { bindEnumValueNS, useStorageNS } from "kvozy";
enum Theme {
Light = "light",
Dark = "dark",
}
const settingsNS = bindEnumValueNS<Theme>({
prefix: "settings",
defaultValue: Theme.Light,
storage: localStorage,
});
const { value: theme, setValue: setTheme } = useStorageNS(settingsNS, {
key: "theme",
});
Use namespaces when:
Use individual bindings when:
import { bindValueNS, useStorageNS } from 'kvozy';
// Create namespace with shared configuration
const appNS = bindValueNS<string>({
prefix: 'app',
defaultValue: '',
serialize: (v) => v,
deserialize: (s) => s,
storage: localStorage,
});
// Use in component with specific key
const Component = () => {
const { value, setValue } = useStorageNS(appNS, { key: 'user' });
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
};
// Storage key will be: 'app\x1Fuser'
import { bindValueNS, useStorageNS } from 'kvozy';
const appNS = bindValueNS<string>({
prefix: 'app',
defaultValue: '',
serialize: (v) => v,
deserialize: (s) => s,
storage: localStorage,
});
const UserSettings = () => {
const { value: language, setValue: setLanguage } = useStorageNS(appNS, { key: 'language' });
const { value: theme, setValue: setTheme } = useStorageNS(appNS, { key: 'theme' });
return (
<div>
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="en">English</option>
<option value="es">Spanish</option>
</select>
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle Theme
</button>
</div>
);
};
import { bindValueNS, useStorageNS } from "kvozy";
const appNS = bindValueNS<UserData>({
prefix: "app",
defaultValue: { name: "", email: "" },
serialize: (v) => JSON.stringify(v),
deserialize: (s) => JSON.parse(s),
storage: localStorage,
version: "2.0.0",
migrate: (old, oldVersion) => {
const oldData = JSON.parse(old);
return { name: oldData.name, email: "" };
},
});
const { value: user, setValue: setUser } = useStorageNS(appNS, { key: "user" });
Organize your app by creating separate namespaces for different domains:
// User-related keys
const userNS = bindValueNS<string>({
prefix: "user",
defaultValue: "",
storage: localStorage,
});
// Application settings keys
const settingsNS = bindValueNS<string>({
prefix: "settings",
defaultValue: "",
storage: localStorage,
});
// Temporary state keys
const tempNS = bindValueNS<string>({
prefix: "temp",
defaultValue: "",
storage: undefined, // in-memory only
});
// Keys will be stored as:
// user\x1Fname', 'user\x1Femail', 'user\x1Ftheme'
// Settings keys: 'settings\x1Fnotifications', 'settings\x1Flanguage', 'settings\x1Ftheme'
// Temp keys: 'temp\x1Fdraft', 'temp\x1Funsaved'
Each namespace maintains complete isolation from other namespaces:
const appNS = bindValueNS<string>({
prefix: "app",
defaultValue: "",
storage: localStorage,
});
const userNS = bindValueNS<string>({
prefix: "user",
defaultValue: "",
storage: localStorage,
});
// These are completely isolated:
// appNS.bind('name') stores to 'app\x1Fname'
// userNS.bind('name') stores to 'user\x1Fname'
// No risk of key collisions between namespaces
Benefits:
Use shortcuts when:
Use full bindValue when:
Before (with full API):
const counter = bindValue<number>({
key: "counter",
defaultValue: 0,
serialize: (v) => String(v),
deserialize: (s) => Number(s),
});
After (with shortcut):
const counter = bindNumberValue({
key: "counter",
});
Each shortcut supports versioning and migration just like bindValue.
// Migrating object structure
const userBinding = bindJSONValue<User>({
key: "user",
defaultValue: { name: "", age: 0, email: "" },
version: "2.0.0",
migrate: (old, oldVersion) => {
const oldData = JSON.parse(old);
return {
name: oldData.name,
age: oldData.age ?? 0,
email: oldData.email ?? "",
};
},
});
// Migrating enum values
enum Theme {
Light = "light",
Dark = "dark",
Auto = "auto",
}
const themeBinding = bindEnumValue<Theme>({
key: "theme",
defaultValue: Theme.Light,
version: "2.0.0",
migrate: (old, oldVersion) => {
if (oldVersion === "1.0.0" && old === "auto-dark") {
return Theme.Dark;
}
return Theme.Light;
},
});
Each namespace maintains complete isolation from other namespaces:
const appNS = bindValueNS<string>({
prefix: "app",
defaultValue: "",
storage: localStorage,
});
const userNS = bindValueNS<string>({
prefix: "user",
defaultValue: "",
storage: localStorage,
});
// These are completely isolated:
// appNS.bind('name') stores to 'app\x1Fname'
// userNS.bind('name') stores to 'user\x1Fname'
// No risk of key collisions between namespaces
Benefits:
import { bindValue, useStorage } from 'kvozy';
const usernameBinding = bindValue<string>({
key: 'username',
defaultValue: '',
serialize: (v) => v,
deserialize: (s) => s,
});
const LoginForm = () => {
const { value, setValue } = useStorage(usernameBinding);
return (
<div>
<label>
Username:
<input
value={value}
onChange={(e) => setValue(e.target.value)}
/>
</label>
</div>
);
};
import { bindValue, useStorage } from 'kvozy';
const themeBinding = bindValue<string>({
key: 'theme',
defaultValue: 'light',
serialize: (v) => v,
deserialize: (s) => s,
});
const ThemeToggle = () => {
const { value, setValue } = useStorage(themeBinding);
return (
<button onClick={() => setValue(value === 'dark' ? 'light' : 'dark')}>
Switch to {value === 'dark' ? 'Light' : 'Dark'} Mode
</button>
);
};
const ThemeDisplay = () => {
const { value } = useStorage(themeBinding);
return <p>Current theme: {value}</p>;
};
const App = () => (
<div>
<ThemeToggle />
<ThemeDisplay />
</div>
);
Both components stay in sync automatically!
When a localStorage key doesn't exist, defaultValue is returned:
const binding = bindValue<string>({
key: 'non-existent-key',
defaultValue: 'default value',
serialize: (v) => v,
deserialize: (s) => s,
});
console.log(binding.getValue()); // 'default value'
const Component = () => {
const { value } = useStorage(binding);
return <div>{value}</div>;
};
Kvozy supports localStorage, sessionStorage, and in-memory storage:
import { bindValue, useStorage } from 'kvozy';
// localStorage - persists across browser sessions
const localBinding = bindValue<string>({
key: 'theme',
defaultValue: 'light',
serialize: (v) => v,
deserialize: (s) => s,
storage: localStorage,
});
// sessionStorage - persists within the same tab
const sessionBinding = bindValue<string>({
key: 'form-data',
defaultValue: '',
serialize: (v) => v,
deserialize: (s) => s,
storage: sessionStorage,
});
// In-memory - no persistence, graceful fallback
const memoryBinding = bindValue<string>({
key: 'temp-state',
defaultValue: '',
serialize: (v) => v,
deserialize: (s) => s,
});
const LocalComponent = () => {
const { value, setValue } = useStorage(localBinding);
return <div>Theme: {value}</div>;
};
const SessionComponent = () => {
const { value, setValue } = useStorage(sessionBinding);
return <div>Form data: {value || 'empty'}</div>;
};
const MemoryComponent = () => {
const { value, setValue } = useStorage(memoryBinding);
return <div>Temp: {value || 'empty'}</div>;
};
const formBinding = bindValue<string>({
key: 'form-data',
defaultValue: '',
serialize: (v) => v,
deserialize: (s) => s,
});
const Form = () => {
const { value, setValue } = useStorage(formBinding);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData);
setValue(JSON.stringify(data));
};
const formData = value ? JSON.parse(value) : {};
return (
<form onSubmit={handleSubmit}>
<input name="name" defaultValue={formData.name || ''} />
<input name="email" defaultValue={formData.email || ''} />
<button type="submit">Save</button>
</form>
);
};
const counterBinding = bindValue<number>({
key: 'counter',
defaultValue: 0,
serialize: (v) => String(v),
deserialize: (s) => Number(s),
});
const Counter = () => {
const { value, setValue } = useStorage(counterBinding);
return (
<div>
<p>Count: {value}</p>
<button onClick={() => setValue(value + 1)}>
Increment
</button>
<button onClick={() => setValue(value - 1)}>
Decrement
</button>
<button onClick={() => setValue(0)}>
Reset
</button>
</div>
);
};
Kvozy supports schema evolution through optional versioning and migration functions. This allows you to safely update your data structure without breaking existing users' stored data.
const userBinding = bindValue<User>({
key: "user",
defaultValue: { name: "", age: 0 },
serialize: (v) => JSON.stringify(v),
deserialize: (s) => JSON.parse(s),
version: "1.0.0",
});
When you change your data structure, provide a migration function:
// Version 1.0.0: stored as string
const themeBindingV1 = bindValue<string>({
key: "theme",
defaultValue: "light",
serialize: (v) => v,
deserialize: (s) => s,
});
// Version 2.0.0: store as object with additional metadata
const themeBindingV2 = bindValue<{ value: string; lastUpdated: number }>({
key: "theme",
defaultValue: { value: "light", lastUpdated: Date.now() },
serialize: (v) => JSON.stringify(v),
deserialize: (s) => JSON.parse(s),
version: "2.0.0",
migrate: (oldSerialized, oldVersion) => {
if (oldVersion === "1.0.0" || oldVersion === undefined) {
// Migrate from string to object
return {
value: oldSerialized,
lastUpdated: Date.now(),
};
}
// Fallback to default for unknown versions
return { value: "light", lastUpdated: Date.now() };
},
});
Add new field:
migrate: (oldSerialized, oldVersion) => {
const oldData = JSON.parse(oldSerialized);
return { ...oldData, newField: "default" };
};
Rename field:
migrate: (oldSerialized) => {
const oldData = JSON.parse(oldSerialized);
return { newName: oldData.oldName };
};
Change data type:
migrate: (oldSerialized) => {
const dateString = oldSerialized;
return { date: new Date(dateString) };
};
version is provided, values are stored with a version prefixmigrate function is calledmigrate is undefined or fails, the defaultValue is usedThis ensures your application works even when users have old data formats, and new users get the default structure.
const counterBinding = bindValue<number>({
key: 'counter',
defaultValue: 0,
serialize: (v) => String(v),
deserialize: (s) => Number(s),
});
const Counter = () => {
const { value, setValue } = useStorage(counterBinding);
return (
<div>
<p>Count: {value}</p>
<button onClick={() => setValue(value + 1)}>
Increment
</button>
<button onClick={() => setValue(value - 1)}>
Decrement
</button>
<button onClick={() => setValue(0)}>
Reset
</button>
</div>
);
};
Organize your app by creating separate namespaces for different domains:
// User-related keys
const userNS = bindValueNS<string>({
prefix: 'user',
defaultValue: '',
storage: localStorage,
});
// Application settings keys
const settingsNS = bindValueNS<string>({
prefix: 'settings',
defaultValue: '',
storage: localStorage,
});
const UserPreferences = () => {
const { value: language, setValue: setLanguage } = useStorageNS(userNS, { key: 'language' });
const { value: notificationsEnabled, setValue: setNotifications } = useStorageNS(settingsNS, { key: 'notifications' });
return (
<div>
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="en">English</option>
<option value="es">Spanish</option>
</select>
<button onClick={() => setNotifications(!notificationsEnabled)}>
{notificationsEnabled ? 'Disable' : 'Enable'} Notifications
</button>
</div>
);
};
// User keys: 'user\x1Flanguage', 'user\x1Femail', 'user\x1Ftheme'
// Settings keys: 'settings\x1Fnotifications', 'settings\x1Flanguage', 'settings\x1Ftheme'
All storage logic lives in bindValue class:
class BindValue<T> {
private value: T;
private subscribers: Set<(value: T) => void>;
getValue(): T;
set(value: T): void;
subscribe(callback: (value: T) => void): () => void;
}
Namespace binder for grouping related keys with shared configuration:
bind(key) method to create individual BindValue instances${prefix}\x1F${key}class BindValueNS<T> {
bind(key: string): BindValue<T>;
}
Thin wrapper that connects bindValue to React:
useState{ value, setValue }function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T> {
const [value, setValue] = useState(binding.getValue());
useEffect(() => {
const unsubscribe = binding.subscribe(setValue);
return unsubscribe;
}, [binding]);
const set = (newValue: T) => binding.set(newValue);
return { value, setValue: set };
}
window.localStorage).serialize() fails, the value is kept in memory but not persisted to storage (graceful degradation).useStorageNS(namespace, { key: 'x' }) call creates an independent binding.useStorageVue)useStorageSvelte)FAQs
Simple, minimal React library for binding localStorage keys to React state
We found that kvozy demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
An AI agent is merging PRs into major OSS projects and cold-emailing maintainers to drum up more work.

Research
/Security News
Chrome extension CL Suite by @CLMasters neutralizes 2FA for Facebook and Meta Business accounts while exfiltrating Business Manager contact and analytics data.

Security News
After Matplotlib rejected an AI-written PR, the agent fired back with a blog post, igniting debate over AI contributions and maintainer burden.