mirror of
https://github.com/zadam/trilium.git
synced 2026-02-21 05:04:24 +01:00
chore(standalone): use CLS with per-request context isolation
This commit is contained in:
parent
49ce312ab2
commit
ad41a58904
@ -3,30 +3,75 @@ import { ExecutionContext } from "@triliumnext/core";
|
||||
/**
|
||||
* Browser execution context implementation.
|
||||
*
|
||||
* Unlike the server (which uses cls-hooked for per-request isolation),
|
||||
* the browser is single-threaded with a single user and doesn't need
|
||||
* request-level isolation. We maintain a single persistent context
|
||||
* throughout the page lifetime.
|
||||
* Handles per-request context isolation with support for fire-and-forget async operations
|
||||
* using a context stack and grace-period cleanup to allow unawaited promises to complete.
|
||||
*/
|
||||
export default class BrowserExecutionContext implements ExecutionContext {
|
||||
private store: Map<string, any> = new Map();
|
||||
private contextStack: Map<string, any>[] = [];
|
||||
private cleanupTimers = new WeakMap<Map<string, any>, ReturnType<typeof setTimeout>>();
|
||||
private readonly CLEANUP_GRACE_PERIOD = 1000; // 1 second for fire-and-forget operations
|
||||
|
||||
private getCurrentContext(): Map<string, any> {
|
||||
if (this.contextStack.length === 0) {
|
||||
throw new Error("ExecutionContext not initialized");
|
||||
}
|
||||
return this.contextStack[this.contextStack.length - 1];
|
||||
}
|
||||
|
||||
get<T = any>(key: string): T {
|
||||
return this.store.get(key);
|
||||
return this.getCurrentContext().get(key);
|
||||
}
|
||||
|
||||
set(key: string, value: any): void {
|
||||
this.store.set(key, value);
|
||||
this.getCurrentContext().set(key, value);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.store.clear();
|
||||
this.contextStack = [];
|
||||
}
|
||||
|
||||
init<T>(callback: () => T): T {
|
||||
// In browser, we don't need per-request isolation.
|
||||
// Just execute the callback with the persistent context.
|
||||
// This allows fire-and-forget operations to access context.
|
||||
return callback();
|
||||
const context = new Map<string, any>();
|
||||
this.contextStack.push(context);
|
||||
|
||||
// Cancel any pending cleanup timer for this context
|
||||
const existingTimer = this.cleanupTimers.get(context);
|
||||
if (existingTimer) {
|
||||
clearTimeout(existingTimer);
|
||||
this.cleanupTimers.delete(context);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = callback();
|
||||
|
||||
// If the result is a Promise
|
||||
if (result && typeof result === 'object' && 'then' in result && 'catch' in result) {
|
||||
const promise = result as unknown as Promise<any>;
|
||||
return promise.finally(() => {
|
||||
this.scheduleContextCleanup(context);
|
||||
}) as T;
|
||||
} else {
|
||||
// For synchronous results, schedule delayed cleanup to allow fire-and-forget operations
|
||||
this.scheduleContextCleanup(context);
|
||||
return result;
|
||||
}
|
||||
} catch (error) {
|
||||
// Always clean up on error with grace period
|
||||
this.scheduleContextCleanup(context);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private scheduleContextCleanup(context: Map<string, any>): void {
|
||||
const timer = setTimeout(() => {
|
||||
// Remove from stack if still present
|
||||
const index = this.contextStack.indexOf(context);
|
||||
if (index !== -1) {
|
||||
this.contextStack.splice(index, 1);
|
||||
}
|
||||
this.cleanupTimers.delete(context);
|
||||
}, this.CLEANUP_GRACE_PERIOD);
|
||||
|
||||
this.cleanupTimers.set(context, timer);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user