Play+ Caching & Storage Helper
Introduction
In the Play+ ecosystem, how we handle client-side data is as important as how we display it. This helper is based on the concept of Intentional and Resilient Storage, providing a safe and intelligent abstraction layer over the browser's raw localStorage
API.
Direct use of localStorage
is discouraged as it's primitive, error-prone, and insecure for sensitive information. playcache
is designed specifically for caching non-sensitive data like user preferences, UI state, and temporary application data. This aligns with our core design pillars by creating an Intuitive API that prevents common errors, an Adaptive system that can handle data expiration, and a Distinct, consistent pattern for storage across all projects.
Package Info
The Play+ storage helper is included by default in the Golden Path starter kit. For existing projects, it can be installed via its dedicated package.
Description
Package / Path | Description |
---|---|
Golden Path (Recommended) | Pre-installed (/system/play.cache.ts ) |
Uplift Path | npm install @playplus/storage |
To get started with the Uplift Path, you can also run: | |
npx playcache |
Folder Reference
File / Directory | Purpose & Guidelines |
---|---|
system/play.cache.ts | The core storage service. It wraps the localStorage API, providing all helper methods. |
config/play.cache.config.json | An optional file for overriding default caching behaviors, such as the key prefix. |
Helper - Pillars Alignment
Pillar | How This Helper Aligns |
---|---|
Intuitive | Primary Pillar: Replaces the error-prone, manual process of using localStorage with a simple, safe, and memorable API. |
Adaptive | Supports reactive data patterns through its event system, allowing the UI to adapt to storage changes across tabs. |
Distinct | Enforces a single, consistent, and safe way to interact with browser storage across all Play+ applications. |
Helper Overview
The playcache
helper is a smart and resilient wrapper around the browser's localStorage
. Its purpose is to abstract the plumbing of client-side caching, so developers can store and retrieve non-sensitive data without worrying about common pitfalls.
It automates and simplifies:
- Serialization: Automatically runs
JSON.stringify()
on set andJSON.parse()
on get, preventing runtime errors and eliminating boilerplate. - Key Scoping: Automatically prefixes all keys with a unique identifier to prevent collisions with other browser tabs, third-party scripts, or other apps on the same domain.
- Data Expiration (TTL): Supports a "Time-to-Live" for cached items, treating expired data as null.
- Reactivity: Provides an event-based system (
on
/off
) so your app can react to storage changes, even across tabs.
Config Options
Optional global configuration can be provided in config/play.cache.config.json
.
Config Variable | Default Value | Description | Recommended Value |
---|---|---|---|
keyPrefix | playcache | The prefix added to all keys to prevent collisions. | Keep default |
defaultTtl | null | Default Time-to-Live in seconds. null means no expiration. | null |
Helper Methods
Helper Methods
Method Name | Description | Signature |
---|---|---|
set | Stores a serializable value in the cache, with optional TTL (in seconds). | set(key: string, value: any, options?: { ttl: number }): void |
get | Retrieves a value from the cache. Returns null if key doesn’t exist or has expired. | get<T>(key: string): T | null |
remove | Deletes a specific item from the cache by its key. | remove(key: string): void |
clear | Removes all items from the cache that have the Play+ key prefix. | clear(): void |
on | Subscribes to changes for a specific key. Callback is triggered on updates (even across tabs). | on(key: string, callback: (newValue: any) => void): void |
off | Unsubscribes a callback for a specific key to prevent memory leaks. | off(key: string, callback: (newValue: any) => void): void |
Usage Examples
React: A Custom Hook for Reactive Caching
// hooks/usePlayCache.ts
import { useState, useEffect, useCallback } from 'react';
import { playcache } from '../system/play.cache';
export function usePlayCache<T>(key: string, defaultValue: T): [T, (newValue: T) => void] {
const [value, setValue] = useState<T>(() => playcache.get<T>(key) ?? defaultValue);
const updateValue = useCallback((newValue: T) => {
playcache.set(key, newValue);
}, [key]);
useEffect(() => {
const handleUpdate = (newValue: T) => {
setValue(newValue ?? defaultValue);
};
playcache.on(key, handleUpdate);
return () => playcache.off(key, handleUpdate);
}, [key, defaultValue]);
return [value, updateValue];
}
function ThemeSwitcher() {
const [theme, setTheme] = usePlayCache('ui-theme', 'light');
return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Current theme: {theme}</button>;
}
Angular: A Reactive Service
// core/services/playcache.service.ts
import { Injectable } from '@angular/core';
import { playcache } from '@playplus/core';
import { Observable, fromEvent, map, startWith } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class PlayCacheService {
observe<T>(key: string, defaultValue: T): Observable<T> {
return fromEvent<StorageEvent>(window, 'storage')
.pipe(map(() => playcache.get<T>(key) ?? defaultValue), startWith(playcache.get<T>(key) ?? defaultValue));
}
set<T>(key: string, value: T, options?: { ttl: number }): void {
playcache.set(key, value, options);
}
}
Additional Info
Why We Created This Helper
Using window.localStorage
directly introduces several issues:
- ❌ Manual JSON serialization/deserialization leads to bugs.
- ❌ No native TTL or expiration.
- ❌ Global key collisions.
- ❌ No reactivity within the same tab.
- ❌ Encourages storing sensitive data insecurely.
playcache
solves these by adding scoping, reactivity, TTLs, and a safe abstraction.
Security: For Non-Sensitive Data Only
playcache
does not encrypt data and is not safe for sensitive information. Use playguard
for secure token and auth storage via HttpOnly cookies.
Caching Strategies & Best Practices
- ✅ Cache static reference data (e.g., dropdowns, themes)
- ✅ Use TTLs to avoid stale state
- ✅ Clear cache intentionally (e.g., on logout)
- ❌ Never cache sensitive or dynamic data (e.g., PII, tokens)
- ❌ Don’t exceed 5MB browser storage limits
Developer Checklist
- Am I only storing non-sensitive data (like UI preferences or temporary reference data)?
- For data that can become stale, have I set an appropriate TTL?
- Am I using
playcache
instead oflocalStorage
directly? - Am I using
on()
andoff()
or reactive patterns if my UI needs to respond to changes? - Am I clearing or removing cache when appropriate (e.g., logout)?