PersistenceHandler
PersistenceHandler is Oidis' cross-environment persistent key-value storage system. It provides automatic serialization of complex objects, auto-selects the best available storage backend (localStorage or cookies), and includes data integrity validation via CRC checksums. The system works on both frontend and backend and is used extensively throughout the framework for UI state persistence — form values, last-viewed records, viewer state, and autofill data.
PersistenceHandler is browser/environment-level storage for transient UI state and session data — a key-value layer over localStorage / cookies. It is distinct from ApplicationSettings (app-wide, DB-backed, admin-editable), User Profile (per-user, DB-backed, personal data), and IndexedDB (model-level frontend persistence with @Entity and CRUD operations). See the comparison table below for when to use each.
How It Works
The entry point is PersistenceFactory, a static factory that creates and caches IPersistenceHandler instances. Each handler is identified by a persistence type (a string or PersistenceType enum value) and an optional owner string. The factory generates a SHA-1 key from these two values and reuses the same handler instance for the same key.
On first use, the factory auto-detects the best storage backend:
- localStorage (
StorageHandler) — preferred, used whenlocalStorageis available - Cookies (
CookiesHandler) — fallback, used when localStorage is not supported (e.g., JRE environments)
Detection happens once and applies to all handlers created during the session.
import { PersistenceFactory } from "@io-oidis-commons/.../PersistenceApi/PersistenceFactory.js";
import { PersistenceType } from "@io-oidis-gui/.../Enums/PersistenceType.js";
// Get a persistence handler for form values
const handler : IPersistenceHandler = PersistenceFactory.getPersistence(PersistenceType.FORM_VALUES);
// Store a value
handler.Variable("lastSearch", "active users");
// Retrieve a value
const search : string = handler.Variable("lastSearch");
The API
All persistence handlers implement IPersistenceHandler. The interface provides a concise set of methods for storing, retrieving, and managing persistent data:
| Method | Signature | Description |
|---|---|---|
Variable | ($name, $value?, $asyncHandler?) : any | Core method. Get or set a variable. Pass only $name to read; pass $name + $value to write. Accepts ArrayList for bulk writes. |
Exists | ($name) : boolean | Check whether a variable exists in this handler |
Destroy | ($name) : void | Remove a variable (or ArrayList of variables) |
Clear | () : void | Remove all data from this handler |
ExpireTime | ($value?) : number | Get or set TTL. Pass a relative time string (e.g., "+1 hour", "+30 minutes") to set expiration. |
DisableCRC | () : void | Disable CRC integrity checking for this handler |
getSessionId | () : string | Returns the handler's internal session ID (SHA-1 hash) |
getSize | () : number | Returns the size of stored data in characters |
getLoadTime | () : number | Returns the time in milliseconds needed to load the data |
getRawData | ($asyncHandler?) : string | Returns the raw serialized data. Pass a callback for async mode. |
LoadPersistenceAsynchronously | ($asyncHandler, $sourceFilePath?) : void | Load persistence data asynchronously, optionally from a file |
The Variable Method in Detail
Variable is the primary method for interacting with persistence. Its behavior depends on the arguments:
// READ — pass only the variable name
const value = handler.Variable("selectedTab");
// WRITE — pass name and value
handler.Variable("selectedTab", "inventory");
// BULK WRITE — pass an ArrayList of key-value pairs
const batch = new ArrayList<any>();
batch.Add("inventory", "selectedTab");
batch.Add(25, "pageSize");
handler.Variable(batch);
// ASYNC WRITE — pass name, value, and callback
handler.Variable("largeDataSet", complexObject, () : void => {
LogIt.Info("Data persisted asynchronously");
});
Storage Backends
StorageHandler (localStorage)
The preferred backend. Stores data directly in the browser's localStorage API with no size chunking needed. Each persistence handler maps to a single localStorage key identified by its SHA-1 session ID.
localStorage.getItem("a1b2c3d4...") → serialized session data
StorageHandler.IsSupported() checks whether localStorage is accessible. If not (e.g., private browsing restrictions, JRE environment), the factory falls back to CookiesHandler.
CookiesHandler (Cookie Storage)
The fallback backend for environments without localStorage. Because cookies have a ~4 KB per-cookie limit, CookiesHandler automatically chunks the serialized data into 4 KB segments stored across multiple cookies:
a1b2c3d4...0 → first 4KB chunk
a1b2c3d4...1 → second 4KB chunk
a1b2c3d4...2 → third 4KB chunk (if needed)
Cookie data is additionally Base64-encoded before storage. Cookies are set with a 5-year expiration by default.
Forcing a Backend
In rare cases, you can force a specific backend before any persistence handler is created:
import { PersistenceFactory } from "@io-oidis-commons/.../PersistenceApi/PersistenceFactory.js";
import { PersistenceHandlerType } from "@io-oidis-commons/.../Enums/PersistenceHandlerType.js";
PersistenceFactory.setPersistenceHandler(PersistenceHandlerType.COOKIES);
Call setPersistenceHandler before any call to getPersistence. Once the handler type is auto-detected and handlers are created, changing it has no effect on existing instances.
Serialization and Data Integrity
Object Serialization
PersistenceHandler uses ObjectEncoder / ObjectDecoder to serialize complex objects — including ArrayList instances, nested structures, and typed data — into string format suitable for storage. CookiesHandler adds an additional Base64 encoding layer on top.
Both synchronous and asynchronous serialization are supported. The async path is useful for large datasets that might block the main thread.
CRC Integrity Validation
By default, every handler computes a CRC checksum over the serialized variables on each write and validates it on each read. If the checksum does not match (indicating external tampering or corruption), the handler:
- Creates a backup of the corrupted data (prefixed with
"back"in the storage key) - Removes the corrupted data
- Throws a
FatalErrorwith a descriptive message
This protects against external tools or browser extensions modifying stored data.
// Disable CRC if you know external tools will modify the data
handler.DisableCRC();
TTL (Time-to-Live)
Each handler supports optional expiration. When ExpireTime is set and the stored TTL timestamp has passed, all data is automatically cleared on the next access:
handler.ExpireTime("+2 hours");
handler.Variable("tempToken", "abc123");
// Data will be cleared after 2 hours
Common Usage Patterns
Class-Scoped Persistence
The most common pattern uses the class name as the persistence key, keeping each controller's state isolated:
import { PersistenceFactory } from "@io-oidis-commons/.../PersistenceApi/PersistenceFactory.js";
export class InventoryController extends BasePanelController {
private persistence : IPersistenceHandler;
public override OnPanelReady() : void {
this.persistence = PersistenceFactory.getPersistence(this.getClassName());
// Restore last-viewed item
const lastId = this.persistence.Variable("lastViewedId");
if (!ObjectValidator.IsEmptyOrNull(lastId)) {
this.LoadItem(lastId);
}
}
protected onItemSelected($id : string) : void {
this.persistence.Variable("lastViewedId", $id);
}
}
Enum-Based Persistence Types
Use the built-in PersistenceType enum for shared persistence scopes that multiple components can access:
import { PersistenceType } from "@io-oidis-gui/.../Enums/PersistenceType.js";
// Store form autofill data — accessible from any component using AUTOFILL type
const autofill = PersistenceFactory.getPersistence(PersistenceType.AUTOFILL);
autofill.Variable("lastEmail", "user@example.com");
// Store general GUI state
const gui = PersistenceFactory.getPersistence(PersistenceType.GUI_COMMONS);
gui.Variable("sidebarCollapsed", true);
Global Cleanup
Clear all registered persistence handlers at once — useful during logout or session reset:
PersistenceFactory.DestroyAll();
Built-in Persistence Types
The framework defines persistence types across two packages. The commons package provides base types, and the gui package extends them with GUI-specific scopes:
Commons Types (io-oidis-commons)
| Type | Value | Purpose |
|---|---|---|
BROWSER | "browser" | Browser session identification |
CLIENT_IP | "clientip" | Client IP-based session |
GUI Types (io-oidis-gui)
| Type | Value | Purpose |
|---|---|---|
GUI_COMMONS | "guicommons" | General GUI state (sidebar, layout preferences) |
VIEWER | "viewer" | Viewer/display state (zoom, scroll position) |
AUTOFILL | "autofill" | Form autofill data |
FORM_VALUES | "formvalues" | Form field persistence (draft values, filters) |
ERROR_FLAGS | "errorflags" | Validation error state tracking |
Application projects can extend PersistenceType with project-specific scopes, following the same inheritance pattern used by io-oidis-gui:
import { PersistenceType as Parent } from "@io-oidis-gui/.../Enums/PersistenceType.js";
export class PersistenceType extends Parent {
public static readonly DASHBOARD_LAYOUT : string = "dashboardlayout";
public static readonly REPORT_FILTERS : string = "reportfilters";
}
PersistenceHandler vs ApplicationSettings vs User Profile
| Aspect | PersistenceHandler | ApplicationSettings | User Profile |
|---|---|---|---|
| Scope | Per-browser, per-environment | App-wide | Per-user |
| Storage | localStorage / cookies | MongoDB | MongoDB |
| Survives | Browser session (until cleared) | Server restarts | Server restarts |
| Managed by | Code (automatic) | Admin panel | Admin or self-service |
| Use case | UI state, form drafts, autofill | Mail settings, feature flags, defaults | Personal data, avatar, preferences |
| Package | io-oidis-commons | io-oidis-hub | io-oidis-hub |
Quick Reference
| File | Package | Purpose |
|---|---|---|
PersistenceFactory.ts | io-oidis-commons | Factory, auto-detection, handler caching |
IPersistenceHandler.ts | io-oidis-commons | Public API interface |
BasePersistenceHandler.ts | io-oidis-commons | Shared logic (CRC, TTL, encode/decode) |
StorageHandler.ts | io-oidis-commons | localStorage implementation |
CookiesHandler.ts | io-oidis-commons | Cookie implementation (with auto-chunking) |
PersistenceType.ts | io-oidis-commons | Base enum (BROWSER, CLIENT_IP) |
PersistenceType.ts | io-oidis-gui | Extended enum (FORM_VALUES, AUTOFILL, etc.) |
See also: Application Settings | User Profile | ObjectValidator | StringUtils