2025-01-18 23:52:44 +01:00
|
|
|
import * as plugins from './logcontext.plugins.js';
|
|
|
|
|
|
|
|
export class AsyncStore {
|
2025-01-19 20:06:18 +01:00
|
|
|
private static idCounter = 0;
|
|
|
|
private id: number;
|
2025-01-18 23:52:44 +01:00
|
|
|
private parentStore?: AsyncStore;
|
|
|
|
private deletedKeys: string[] = [];
|
2025-01-19 20:06:18 +01:00
|
|
|
private dataObject: { [key: string]: any } = {};
|
2025-01-18 23:52:44 +01:00
|
|
|
|
|
|
|
constructor(parentStore?: AsyncStore) {
|
|
|
|
this.parentStore = parentStore;
|
2025-01-19 20:06:18 +01:00
|
|
|
this.id = AsyncStore.idCounter++;
|
2025-01-18 23:52:44 +01:00
|
|
|
}
|
|
|
|
|
2025-01-19 20:06:18 +01:00
|
|
|
/**
|
|
|
|
* Logs debug info if process.env.DEBUG is set.
|
|
|
|
*/
|
|
|
|
private logDebug(functionName: string, before: Record<string, any>, after: Record<string, any>) {
|
|
|
|
if (process.env.DEBUG) {
|
|
|
|
console.log(`Store ID: ${this.id}`);
|
|
|
|
console.log(`Function: ${functionName}`);
|
|
|
|
console.log('--- Before ---');
|
|
|
|
console.log(before);
|
|
|
|
console.log('--- After ---');
|
|
|
|
console.log(after);
|
|
|
|
console.log('-----------------------');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleans up the deleted keys if they no longer exist in any parent store.
|
|
|
|
*/
|
2025-01-18 23:52:44 +01:00
|
|
|
private cleanUp() {
|
|
|
|
for (const key of this.deletedKeys) {
|
|
|
|
if (this.parentStore && this.parentStore.get(key)) {
|
2025-01-19 20:06:18 +01:00
|
|
|
// Parent still has it, so keep in deletedKeys
|
2025-01-18 23:52:44 +01:00
|
|
|
} else {
|
2025-01-19 20:06:18 +01:00
|
|
|
const index = this.deletedKeys.indexOf(key);
|
|
|
|
if (index !== -1) {
|
|
|
|
this.deletedKeys.splice(index, 1);
|
|
|
|
}
|
2025-01-18 23:52:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-19 20:06:18 +01:00
|
|
|
/**
|
|
|
|
* Adds or updates a value under a specific key in this store.
|
|
|
|
*/
|
2025-01-18 23:52:44 +01:00
|
|
|
public add(keyArg: string, objectArg: any) {
|
2025-01-19 20:06:18 +01:00
|
|
|
// capture the before state
|
|
|
|
const before = { ...this.dataObject, deletedKeys: [...this.deletedKeys] };
|
|
|
|
|
2025-01-18 23:52:44 +01:00
|
|
|
this.cleanUp();
|
2025-01-19 20:06:18 +01:00
|
|
|
// If this key was previously deleted, remove it from deletedKeys.
|
2025-01-18 23:52:44 +01:00
|
|
|
if (this.deletedKeys.includes(keyArg)) {
|
|
|
|
this.deletedKeys = this.deletedKeys.filter((key) => key !== keyArg);
|
|
|
|
}
|
|
|
|
this.dataObject[keyArg] = objectArg;
|
2025-01-19 20:06:18 +01:00
|
|
|
|
|
|
|
// capture the after state
|
|
|
|
const after = { ...this.dataObject, deletedKeys: [...this.deletedKeys] };
|
|
|
|
this.logDebug('add', before, after);
|
2025-01-18 23:52:44 +01:00
|
|
|
}
|
|
|
|
|
2025-01-19 20:06:18 +01:00
|
|
|
/**
|
|
|
|
* Deletes a key from the current store.
|
|
|
|
* If a parent store has the key, we record it in `deletedKeys` so the child store "shadows" it.
|
|
|
|
*/
|
2025-01-18 23:52:44 +01:00
|
|
|
public delete(paramName: string) {
|
2025-01-19 20:06:18 +01:00
|
|
|
// capture the before state
|
|
|
|
const before = { ...this.dataObject, deletedKeys: [...this.deletedKeys] };
|
|
|
|
|
2025-01-18 23:52:44 +01:00
|
|
|
this.cleanUp();
|
2025-01-19 20:06:18 +01:00
|
|
|
if (this.parentStore?.get(paramName)) {
|
|
|
|
// The parent store has this key; let's mark it as deleted in the child
|
2025-01-18 23:52:44 +01:00
|
|
|
this.deletedKeys.push(paramName);
|
|
|
|
}
|
|
|
|
delete this.dataObject[paramName];
|
2025-01-19 20:06:18 +01:00
|
|
|
|
|
|
|
// capture the after state
|
|
|
|
const after = { ...this.dataObject, deletedKeys: [...this.deletedKeys] };
|
|
|
|
this.logDebug('delete', before, after);
|
2025-01-18 23:52:44 +01:00
|
|
|
}
|
|
|
|
|
2025-01-19 20:06:18 +01:00
|
|
|
/**
|
|
|
|
* Gets the value of a key, checking this store first, then the parent store if necessary.
|
|
|
|
* Will log the store state before/after for debugging.
|
|
|
|
*/
|
2025-01-18 23:52:44 +01:00
|
|
|
public get(paramName: string) {
|
2025-01-19 20:06:18 +01:00
|
|
|
// capture the before state
|
|
|
|
const before = { ...this.dataObject, deletedKeys: [...this.deletedKeys] };
|
|
|
|
|
2025-01-18 23:52:44 +01:00
|
|
|
this.cleanUp();
|
2025-01-19 20:06:18 +01:00
|
|
|
// figure out if paramName is deleted or present
|
|
|
|
let result: any;
|
2025-01-18 23:52:44 +01:00
|
|
|
if (this.deletedKeys.includes(paramName)) {
|
2025-01-19 20:06:18 +01:00
|
|
|
result = undefined;
|
|
|
|
} else {
|
|
|
|
result = this.dataObject[paramName] ?? this.parentStore?.get(paramName);
|
2025-01-18 23:52:44 +01:00
|
|
|
}
|
2025-01-19 20:06:18 +01:00
|
|
|
|
|
|
|
// capture the after state; we can also show the `result` in the log
|
|
|
|
const after = {
|
|
|
|
...this.dataObject,
|
|
|
|
deletedKeys: [...this.deletedKeys],
|
|
|
|
retrievedKey: paramName,
|
|
|
|
result
|
|
|
|
};
|
|
|
|
this.logDebug('get', before, after);
|
|
|
|
|
|
|
|
return result;
|
2025-01-18 23:52:44 +01:00
|
|
|
}
|
|
|
|
|
2025-01-19 20:06:18 +01:00
|
|
|
/**
|
|
|
|
* Returns all keys and values, merged with the parent store, but
|
|
|
|
* does NOT include keys that are "deleted" in the child.
|
|
|
|
* Child store should override parent if the same key exists in both.
|
|
|
|
*/
|
2025-01-18 23:52:44 +01:00
|
|
|
public getAll() {
|
|
|
|
this.cleanUp();
|
2025-01-19 20:06:18 +01:00
|
|
|
// first, get parent's data as a shallow copy
|
|
|
|
const parentData = { ...(this.parentStore?.getAll() || {}) };
|
|
|
|
|
|
|
|
// remove keys from parent data that this child has deleted
|
|
|
|
for (const key of this.deletedKeys) {
|
|
|
|
delete parentData[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
// child's data overrides parent data for any matching keys
|
|
|
|
return {
|
|
|
|
...parentData,
|
|
|
|
...this.dataObject
|
|
|
|
};
|
2025-01-18 23:52:44 +01:00
|
|
|
}
|
2025-01-19 20:06:18 +01:00
|
|
|
}
|