Pulse/frontend-modern/src/hooks/usePersistentSignal.ts
rcourtman 57429900a6 feat: add adaptive polling scheduler infrastructure (Phase 2 Tasks 1-3)
Implements adaptive scheduling foundation for Phase 2:
- Poll cycle metrics: duration, staleness, queue depth, in-flight counters
- Adaptive scheduler with pluggable staleness/interval/enqueue interfaces
- Config support: ADAPTIVE_POLLING_ENABLED flag + min/max/base intervals
- Feature flag defaults to disabled for safe rollout
- Scheduler wiring into Monitor with conditional instantiation

Tasks 1-3 of 10 complete. Ready for staleness tracker implementation.
2025-10-20 15:13:37 +00:00

75 lines
2.1 KiB
TypeScript

import { Accessor, Setter, createEffect, createSignal } from 'solid-js';
export type PersistentSignalOptions<T> = {
/**
* Custom serialization function. Defaults to `String(value)`.
*/
serialize?: (value: T) => string;
/**
* Custom deserialization function. Defaults to casting the stored string.
*/
deserialize?: (value: string) => T;
/**
* Optional equality comparison passed to Solid's `createSignal`.
*/
equals?: (prev: T, next: T) => boolean;
/**
* Alternate storage implementation (defaults to `window.localStorage`).
*/
storage?: Storage;
};
/**
* Creates a Solid signal that persists its value to localStorage (or a custom storage).
* The signal reads the initial value synchronously from storage when available.
*/
export function usePersistentSignal<T>(
key: string,
defaultValue: T,
options: PersistentSignalOptions<T> = {},
): [Accessor<T>, Setter<T>] {
const storage =
options.storage ?? (typeof window !== 'undefined' ? window.localStorage : undefined);
const serialize = options.serialize ?? ((value: T) => String(value));
const deserialize = options.deserialize ?? ((value: string) => value as unknown as T);
const initialValue = (() => {
if (!storage) {
return defaultValue;
}
try {
const raw = storage.getItem(key);
if (raw === null) {
return defaultValue;
}
return deserialize(raw);
} catch (err) {
console.warn(`[usePersistentSignal] Failed to read "${key}" from storage`, err);
return defaultValue;
}
})();
const signalOptions = options.equals ? { equals: options.equals } : undefined;
const [value, setValue] = createSignal<T>(initialValue, signalOptions);
createEffect(() => {
if (!storage) {
return;
}
const current = value();
try {
if (current === undefined || current === null) {
storage.removeItem(key);
} else {
storage.setItem(key, serialize(current));
}
} catch (err) {
console.warn(`[usePersistentSignal] Failed to persist "${key}"`, err);
}
});
return [value, setValue];
}