Files
strata-compile/runtime/store/store.sts
Carlos Gutierrez 9e451469f5 git commit -m "feat: initial release of Strata framework v0.1.0
- Static compiler with STRC pattern (Static Template Resolution with
   Compartmentalized Layers)
   - Template syntax: { } interpolation, { s-for }, { s-if/s-elif/s-else
    }
   - File types: .strata, .compiler.sts, .service.sts, .api.sts, .sts,
   .scss
   - CLI tools: strata dev, strata build, strata g (generators)
   - create-strata scaffolding CLI with Pokemon API example
   - Dev server with WebSocket HMR (Hot Module Replacement)
   - Documentation: README, ARCHITECTURE, CHANGELOG, CONTRIBUTING,
   LICENSE
2026-01-16 09:01:29 -05:00

261 lines
6.8 KiB
Plaintext

/**
* Strata Store
* Fast state management with encryption and cross-tab sync
*/
import { strata } from '../core/strata.sts';
type Subscriber<T> = (state: T, prevState: T) => void;
type Selector<T, R> = (state: T) => R;
interface StoreOptions<T> {
state: T;
actions?: Record<string, (this: T & StoreActions<T>, ...args: any[]) => any>;
encrypt?: boolean | string[]; // true = all, string[] = specific fields
persist?: boolean | string[]; // true = all, string[] = specific fields
shared?: boolean | string[]; // true = all, string[] = specific fields across tabs
}
interface StoreActions<T> {
$set: (partial: Partial<T>) => void;
$reset: () => void;
$subscribe: (subscriber: Subscriber<T>) => () => void;
}
export interface StrataStore<T> extends StoreActions<T> {
getState: () => T;
setState: (partial: Partial<T>) => void;
}
const stores: Map<string, StrataStore<any>> = new Map();
/**
* Create a new store
*/
export function createStore<T extends object>(
name: string,
options: StoreOptions<T>
): StrataStore<T> & T {
if (stores.has(name)) {
return stores.get(name) as StrataStore<T> & T;
}
const initialState = { ...options.state };
let state = { ...initialState };
const subscribers: Set<Subscriber<T>> = new Set();
// Track which fields should be encrypted/persisted/shared
const encryptFields = normalizeFields(options.encrypt);
const persistFields = normalizeFields(options.persist);
const sharedFields = normalizeFields(options.shared);
function normalizeFields(opt: boolean | string[] | undefined): Set<string> | 'all' | null {
if (opt === true) return 'all';
if (Array.isArray(opt)) return new Set(opt);
return null;
}
function shouldEncrypt(field: string): boolean {
if (encryptFields === 'all') return true;
if (encryptFields instanceof Set) return encryptFields.has(field);
return false;
}
function shouldPersist(field: string): boolean {
if (persistFields === 'all') return true;
if (persistFields instanceof Set) return persistFields.has(field);
return false;
}
function shouldShare(field: string): boolean {
if (sharedFields === 'all') return true;
if (sharedFields instanceof Set) return sharedFields.has(field);
return false;
}
function notify(prevState: T) {
for (const subscriber of subscribers) {
subscriber(state, prevState);
}
}
function setState(partial: Partial<T>) {
const prevState = { ...state };
const changes = Object.entries(partial);
for (const [key, value] of changes) {
(state as any)[key] = value;
// Sync to SharedWorker
if (shouldPersist(key) || shouldShare(key)) {
strata.sendToWorker({
type: 'store:set',
storeName: name,
key,
value,
scope: shouldShare(key) ? 'shared' : 'tab',
encrypt: shouldEncrypt(key),
});
}
}
notify(prevState);
}
function getState(): T {
return { ...state };
}
// Core store object
const store: StrataStore<T> = {
getState,
setState,
$set: setState,
$reset: () => {
setState(initialState as Partial<T>);
},
$subscribe: (subscriber: Subscriber<T>) => {
subscribers.add(subscriber);
return () => subscribers.delete(subscriber);
},
};
// Bind actions
if (options.actions) {
for (const [actionName, actionFn] of Object.entries(options.actions)) {
(store as any)[actionName] = function (...args: any[]) {
return actionFn.apply(
new Proxy(state, {
set(target, prop, value) {
setState({ [prop]: value } as Partial<T>);
return true;
},
get(target, prop) {
if (prop === '$set') return setState;
if (prop === '$reset') return store.$reset;
return (target as any)[prop];
},
}),
args
);
};
}
}
// Create proxy for direct property access
const proxy = new Proxy(store as StrataStore<T> & T, {
get(target, prop: string) {
if (prop in target) return (target as any)[prop];
return state[prop as keyof T];
},
set(target, prop: string, value) {
if (prop in state) {
setState({ [prop]: value } as Partial<T>);
return true;
}
return false;
},
});
// Listen for cross-tab updates
strata.onBroadcast('store:changed', (data: any) => {
if (data.storeName === name && shouldShare(data.key)) {
const prevState = { ...state };
(state as any)[data.key] = data.value;
notify(prevState);
}
});
// Load persisted state
loadPersistedState(name, state, persistFields, sharedFields);
stores.set(name, proxy);
return proxy;
}
/**
* Hook to use store with optional selector
*/
export function useStore<T, R = T>(
store: StrataStore<T>,
selector?: Selector<T, R>
): R {
const state = store.getState();
return selector ? selector(state) : (state as unknown as R);
}
/**
* Subscribe to store changes with selector
*/
export function subscribeToStore<T, R>(
store: StrataStore<T>,
selector: Selector<T, R>,
callback: (value: R, prevValue: R) => void
): () => void {
let prevValue = selector(store.getState());
return store.$subscribe((state, prevState) => {
const newValue = selector(state);
const oldValue = selector(prevState);
// Only call if selected value changed
if (!deepEqual(newValue, oldValue)) {
callback(newValue, prevValue);
prevValue = newValue;
}
});
}
async function loadPersistedState<T>(
storeName: string,
state: T,
persistFields: Set<string> | 'all' | null,
sharedFields: Set<string> | 'all' | null
): Promise<void> {
// Load from SharedWorker on init
const fields = new Set<string>();
if (persistFields === 'all' || sharedFields === 'all') {
Object.keys(state as object).forEach((k) => fields.add(k));
} else {
if (persistFields instanceof Set) persistFields.forEach((k) => fields.add(k));
if (sharedFields instanceof Set) sharedFields.forEach((k) => fields.add(k));
}
// Request each field from worker
// (In production, batch this into single request)
for (const field of fields) {
strata.sendToWorker({
type: 'store:get',
storeName,
key: field,
scope: sharedFields === 'all' || (sharedFields instanceof Set && sharedFields.has(field))
? 'shared'
: 'tab',
});
}
}
function deepEqual(a: any, b: any): boolean {
if (a === b) return true;
if (a === null || b === null) return false;
if (typeof a !== typeof b) return false;
if (typeof a === 'object') {
if (Array.isArray(a) !== Array.isArray(b)) return false;
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
for (const key of keysA) {
if (!deepEqual(a[key], b[key])) return false;
}
return true;
}
return false;
}