Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 87 additions & 54 deletions src/valdi_modules/src/valdi/persistence/web/PersistentStoreNative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,123 +13,156 @@ type Entry = {
expiresAt?: number;
};

const storeMap = new Map<string, Entry>();
const memoryStores = new Map<string, Map<string, Entry>>();
let nowOverrideSec: number | undefined;

const nowSec = () => (nowOverrideSec ?? Math.floor(Date.now() / 1000));
const cloneBuf = (b: ArrayBuffer) => b.slice(0);
const toBuf = (s: string) => enc.encode(s).buffer;
const toStr = (b: ArrayBuffer) => dec.decode(new Uint8Array(b));

const storageKey = (_storeName: string, key: string) => key;

const isExpired = (e: Entry) => e.expiresAt !== undefined && nowSec() >= e.expiresAt;
const sweepIfExpired = (k: string) => {
const e = storeMap.get(k);
if (e && isExpired(e)) storeMap.delete(k);
};

/* ---------- implementation ---------- */
const getMemoryStore = (storeName: string): Map<string, Entry> => {
let store = memoryStores.get(storeName);
if (!store) {
store = new Map<string, Entry>();
memoryStores.set(storeName, store);
}
return store;
};

const impl: PersistentStoreNative = {
store(key, value, ttlSeconds, weight, completion) {
const createStore = (storeName: string): PersistentStoreNative => ({
store(
key: string,
value: ArrayBuffer | string,
ttlSeconds: number | undefined,
weight: number | undefined,
completion: (error?: string) => void,
) {
microtask(() => {
try {
const entry: Entry = {
value: typeof value === "string" ? value : cloneBuf(value),
value: typeof value === 'string' ? value : cloneBuf(value),
weight,
expiresAt:
ttlSeconds === undefined ? undefined :
ttlSeconds <= 0 ? nowSec() : nowSec() + Math.floor(ttlSeconds),
};
storeMap.set(key, entry);
completion(); // success
} catch (e: any) {
completion(String(e?.message ?? e ?? "store failed"));
getMemoryStore(storeName).set(storageKey(storeName, key), entry);
completion();
} catch (e: unknown) {
completion(String(e instanceof Error ? e.message : e ?? 'store failed'));
}
});
},

fetch(key, completion, asString) {
fetch(
key: string,
completion: (value?: ArrayBuffer | string, error?: string) => void,
asString: boolean,
) {
microtask(() => {
sweepIfExpired(key);
const e = storeMap.get(key);
const fullKey = storageKey(storeName, key);
const store = getMemoryStore(storeName);
sweepIfExpired(store, fullKey);
const e = store.get(fullKey);
if (!e) {
completion(undefined, "not found");
completion(undefined, 'not found');
return;
}
let v: ArrayBuffer | string = e.value;

// honor asString flag by converting if needed
if (asString && v instanceof ArrayBuffer) {
v = toStr(v);
} else if (!asString && typeof v === "string") {
} else if (!asString && typeof v === 'string') {
v = toBuf(v);
}

completion(v, undefined);
});
},

fetchAll(completion) {
fetchAll(completion: (value: PropertyList) => void) {
microtask(() => {
// collect keys first (no for..of, no Array.from)
const keys: string[] = [];
storeMap.forEach((_e, k) => keys.push(k));
for (let i = 0; i < keys.length; i++) {
sweepIfExpired(keys[i]);
}

const out: PropertyList = {};
// safe iteration without for..of
storeMap.forEach((e, k) => {
out[k] = e.value;
const store = getMemoryStore(storeName);
store.forEach((e, k) => {
if (!isExpired(e)) {
out[k] = e.value;
}
});
completion(out);
});
},

// inside setCurrentTime(...), replace the sweep loop with:
setCurrentTime(timeSeconds) {
setCurrentTime(timeSeconds: number) {
nowOverrideSec = Math.floor(timeSeconds);
const keys: string[] = [];
storeMap.forEach((_e, k) => keys.push(k));
for (let i = 0; i < keys.length; i++) {
sweepIfExpired(keys[i]);
}
memoryStores.forEach(store => {
const keys: string[] = [];
store.forEach((_e, k) => keys.push(k));
for (let i = 0; i < keys.length; i++) {
sweepIfExpired(store, keys[i]);
}
});
},

exists(key, completion) {
exists(key: string, completion: (exists: boolean) => void) {
microtask(() => {
sweepIfExpired(key);
completion(storeMap.has(key));
const fullKey = storageKey(storeName, key);
const store = getMemoryStore(storeName);
sweepIfExpired(store, fullKey);
completion(store.has(fullKey));
});
},

remove(key, completion) {
remove(key: string, completion: (error?: string) => void) {
microtask(() => {
try {
storeMap.delete(key);
getMemoryStore(storeName).delete(storageKey(storeName, key));
completion();
} catch (e: any) {
completion(String(e?.message ?? e ?? "remove failed"));
} catch (e: unknown) {
completion(String(e instanceof Error ? e.message : e ?? 'remove failed'));
}
});
},

removeAll(completion) {
removeAll(completion: (error?: string) => void) {
microtask(() => {
try {
storeMap.clear();
memoryStores.delete(storeName);
completion();
} catch (e: any) {
completion(String(e?.message ?? e ?? "removeAll failed"));
} catch (e: unknown) {
completion(String(e instanceof Error ? e.message : e ?? 'removeAll failed'));
}
});
},

};
});

function sweepIfExpired(store: Map<string, Entry>, key: string): void {
const e = store.get(key);
if (e && isExpired(e)) {
store.delete(key);
}
}

export function newPersistentStore(
name: string,
_disableBatchWrites: boolean,
_userScoped: boolean,
_maxWeight: number,
mockedTime: number | undefined,
_mockedUserId: string | undefined,
_enableEncryption: boolean | undefined,
): PersistentStoreNative {
if (mockedTime !== undefined) {
nowOverrideSec = Math.floor(mockedTime);
}
return createStore(name);
}

/** Exported instance + setter so you can swap in a real native binding later. */
export let persistentStoreNative: PersistentStoreNative = impl;
export let persistentStoreNative: PersistentStoreNative = createStore('default');
export function setPersistentStoreNative(newImpl: PersistentStoreNative): void {
persistentStoreNative = newImpl;
}
}
Loading