Ultra-light localStorage synchronizer – declaratively push a set of key/value pairs and Locsync writes only what actually changed.
Locsync gives you a single function to idempotently ensure a collection of values is present and
current in localStorage while skipping redundant writes. Great for persisting lightweight UI /
session state, feature flags, cached timestamps, and user preferences.
- Write Avoidance – Skips values that haven't changed (reduces churn & flash of stale state)
- Declarative – Call once per render / event loop; it's idempotent
- Deterministic – No timers, no async race conditions
- TypeScript First – Strongly typed schema
- Zero Dependencies – Tiny, auditable
- UMD / ESM / CJS Builds – Works everywhere you need it
Use Locsync when you want a simple, low-level primitive to mirror ephemeral runtime data into
localStorage without building a reactive layer. If you need diffs, events, or reactive replay,
pair it with your state management solution.
npm install locsyncpnpm add locsyncyarn add locsyncimport locsync from "locsync";
// Define your schema
const userSettings = {
theme: "dark",
language: "en",
notifications: "enabled"
};
// Sync to localStorage
locsync(userSettings);<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<script src="https://unpkg.com/locsync/dist/index.umd.js"></script>
<script>
// locsync is available globally
locsync({
userId: "12345",
preferences: "compact"
});
</script>
</body>
</html>import locsync from "locsync";
import type { Schema } from "locsync";
const schema: Schema = {
apiKey: "your-api-key",
cacheExpiry: "3600000"
};
locsync(schema);Synchronizes the provided schema with localStorage.
- Parameters:
schema(Schema): An object where keys are localStorage keys and values are the data to store
- Returns:
void
type Schema = Record<string, string>;All values must be strings. If you need to store complex data, stringify it first:
locsync({
userData: JSON.stringify({ name: "John", age: 30 }),
settings: JSON.stringify({ theme: "dark" })
});- For each key in the schema, locsync checks if the value has changed since the last sync
- If changed, it updates localStorage with the new value
- It maintains a "previous value" cache to detect changes efficiently
- Unchanged values are skipped to optimize performance
const preferences = {
"app-theme": "light",
"sidebar-collapsed": "false",
"language": "en-US"
};
locsync(preferences);const formData = {
"form-step": "2",
"user-email": "user@example.com",
"draft-content": JSON.stringify(editorContent)
};
locsync(formData);const appState = {
"current-view": "dashboard",
"last-login": new Date().toISOString(),
"session-id": generateSessionId()
};
locsync(appState);You can provide only string values. Serialize objects/arrays ahead of time:
locsync({
filters: JSON.stringify(activeFilters),
cache: JSON.stringify({ v: 2, expires: Date.now() + 60000 })
});Keep keys scoped to your app / feature to avoid collisions:
const ns = (k: string) => `myapp:${k}`;
locsync({
[ns("session")]: sessionId,
[ns("draft:v1")]: draftContentHash
});If you share code with SSR or Node contexts, wrap the call:
if (typeof window !== "undefined" && "localStorage" in window) {
locsync(schema);
}Call Locsync after you finalize derived values (e.g., in a React effect):
useEffect(() => {
locsync({ "ui:theme": theme, "ui:collapsed": String(isCollapsed) });
}, [theme, isCollapsed]);Locsync never deletes keys. If you version keys (e.g., feature:x:v2) remember to prune old ones
with a manual cleanup script if needed.
- Each call does:
Object.keys+ up to 2localStorageops per changed key - Unchanged keys: 1
getItem+ 0 writes - Changed keys: 1
getItem+ 2 writes (value + previous marker) - Complexity: O(n) where n = number of keys provided
Tips:
- Group related keys and call Locsync once per UI cycle
- Avoid passing large serialized payloads repeatedly; hash them first if size matters
| Symptom | Likely Cause | Fix |
|---|---|---|
| Values not updating | Passing same string each call | This is expected – only changed values flush |
| Quota exceeded | Storing very large JSON blobs | Compress or prune; use IndexedDB for large data |
| Nothing happens SSR | No localStorage on server |
Guard calls (see Non-Browser example) |
| Key collision with other libs | Generic key names | Prefix / namespace keys |
Why store a previous copy (prev-locsync-<key>)?
To detect changes without reading and parsing your original serialized structures. It lets us skip
rewriting unchanged values.
Can I store numbers / booleans?
Yes—convert them to strings first (JS does this implicitly in template literals):
locsync({ count: String(count) });
Does it debounce / throttle?
No. Keep calls minimal yourself—idempotence makes extra calls inexpensive.
Will it work in private mode?
Yes in most browsers, unless storage is disabled. Wrap in a try/catch for hardened scenarios.
How do I remove keys?
Manually call localStorage.removeItem(key); Locsync only syncs provided keys.
- Optional diff / changed keys return value
- Pluggable storage adapter (sessionStorage, memory)
- Key expiration helper utilities
- Devtool / logging flag
Star the repo to follow progress or open an issue to upvote a feature.
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
We welcome contributions! Please see our Contributing Guide for details.
Locsync writes only the literal strings you provide. Do not store secrets, tokens, or PII in
localStorage—it is accessible to any script running on the page.
This project is licensed under the MIT License - see the LICENSE file for details.
See CHANGELOG.md for version history.