Skip to content
Merged
Show file tree
Hide file tree
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
21 changes: 8 additions & 13 deletions src/common/models/executionMemoization.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { FunctionMetadata } from './executionFunction.model';

export const memoizationKey = Symbol('execution-engine/memoize');

/** Default expiration that ensures multiple rapid calls can reuse the stored result */
export const memoizationDefaultExpirationMs = 100;
/** Maximum allowable expiration time Prevent excessive retention */
export const memoizationMaxExpirationMs = 1000;
/** Default expiration in milliseconds that ensures multiple rapid calls can reuse the stored result */
export const memoizationDefaultTTL = 100;
/** Maximum allowable expiration time in milliseconds to prevent excessive retention */
export const memoizationMaxTTL = 1000;

/**
* Represents the context of a memoized function execution.
Expand All @@ -17,22 +17,17 @@ export interface MemoizationContext <O> {
value?: Promise<O> | O;
}


/**
* A handler function that processes the memoization context.
*/
export type MemoizationHandler<O> = (info: MemoizationContext<O>) => void;

export interface MemoizeOptions<O> {
/** Unique identifier for the function being memoized */
functionId: string;

/**
* Optional expiration time in milliseconds for the cached result.
* Optional small expiration time in milliseconds for the memoized result.
* @remarks:
* Default is 100ms, capped at 1000ms to prevent excessive retention.
*/
expirationMs?: number;
ttl?: number;

/** Custom handler for memoization logic */
memoizationHandler?: MemoizationHandler<O>;
onMemoizeEvent?: (info: MemoizationContext<O>) => void;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memoize } from './memoizeDecorator';
import { memoize } from './memoize.decorator';
import { MemoizationContext } from '../common/models/executionMemoization.model';

describe('memoize decorator', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import { executeMemoize } from './memoize';
import { FunctionMetadata } from '../common/models/executionFunction.model';
import { MemoizationHandler } from '../common/models/executionMemoization.model';
import { MemoizeOptions } from '../common/models/executionMemoization.model';
import { attachFunctionMetadata, extractClassMethodMetadata } from '../common/utils/functionMetadata';

/**
* Decorator to memoize method executions and prevent redundant calls.
*
* @param memoizationHandler - Optional callback triggered after checking memory
* @param expirationMs - Duration (in milliseconds) before clearing the stored result,
* @param onMemoizeEvent - Optional callback triggered after checking memory
* @param ttl - Small duration (in milliseconds) before clearing the stored result,
* capped at 1000ms to prevent excessive retention.
* @returns A method decorator for applying memoization.
*
* @remarks
* Uses `executeMemoize` internally to store and reuse results.
* A short delay (e.g., 100ms) ensures that multiple rapid calls can reuse the stored result.
*/
export function memoize<O>(memoizationHandler?: MemoizationHandler<O>, expirationMs?: number): MethodDecorator {
export function memoize<O>(onMemoizeEvent?: MemoizeOptions<O>['onMemoizeEvent'], ttl?: number): MethodDecorator {
return function <T extends Record<string, unknown>>(target: T, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: unknown[]): ReturnType<typeof originalMethod> {
const thisMethodMetadata: FunctionMetadata = extractClassMethodMetadata(target.constructor.name, propertyKey, originalMethod);
return (executeMemoize.bind(this) as typeof executeMemoize<O>)(originalMethod.bind(this), args, {
functionId: thisMethodMetadata.methodSignature,
memoizationHandler: attachFunctionMetadata.bind(this)(memoizationHandler, thisMethodMetadata),
expirationMs
onMemoizeEvent: attachFunctionMetadata.bind(this)(onMemoizeEvent, thisMethodMetadata),
ttl
});
};
};
Expand Down
19 changes: 8 additions & 11 deletions src/execution/memoize.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { execute } from './execute';
import {
memoizationDefaultExpirationMs,
memoizationDefaultTTL,
memoizationKey,
memoizationMaxExpirationMs,
memoizationMaxTTL,
MemoizeOptions
} from '../common/models/executionMemoization.model';
import { generateHashId } from '../common/utils/crypto';
Expand All @@ -26,10 +26,7 @@ export function executeMemoize<O>(
*
* @param blockFunction - The function to execute and memoize.
* @param inputs - Arguments used to generate a unique memoization key.
* @param options - Additional options including a unique function identifier.
* @param options.expirationMs - Duration (in milliseconds) before clearing the stored result,
* capped at 1000ms to prevent excessive retention.
* @param options.memoizationHandler - Optional callback triggered after checking memoization memory.
* @param options - Additional options.
* @returns The memoized result or a newly computed value.
*
* @remarks
Expand All @@ -42,7 +39,7 @@ export function executeMemoize<O>(
inputs: Array<unknown> = [],
options: MemoizeOptions<O>
): Promise<O> | O {
const expirationMs = Math.min(options.expirationMs ?? memoizationDefaultExpirationMs, memoizationMaxExpirationMs); // Default short delay and Prevent excessive retention
const ttl = Math.min(options.ttl ?? memoizationDefaultTTL, memoizationMaxTTL); // Default short delay and Prevent excessive retention
const memoizationFullStore: Map<string, Map<string, Promise<O> | O>> = (this[memoizationKey] ??= new Map<
string,
Map<string, Promise<O> | O>
Expand All @@ -51,9 +48,9 @@ export function executeMemoize<O>(
const inputsHash = generateHashId(...inputs);
const memoizedValue = memoizationStore.get(inputsHash);

if (typeof options.memoizationHandler === 'function') {
if (typeof options.onMemoizeEvent === 'function') {
const functionMetadata = extractFunctionMetadata(blockFunction);
options.memoizationHandler({ metadata: functionMetadata, inputsHash, isMemoized: !!memoizedValue, value: memoizedValue });
options.onMemoizeEvent({ metadata: functionMetadata, inputsHash, isMemoized: !!memoizedValue, value: memoizedValue });
}

if (memoizedValue) {
Expand All @@ -72,9 +69,9 @@ export function executeMemoize<O>(
this[memoizationKey].set(options.functionId, memoizationStore);

if (callResponseOrPromise instanceof Promise) {
return callResponseOrPromise.finally(() => setTimeout(() => memoizationStore.delete(inputsHash), expirationMs));
return callResponseOrPromise.finally(() => setTimeout(() => memoizationStore.delete(inputsHash), ttl));
} else {
setTimeout(() => memoizationStore.delete(inputsHash), expirationMs);
setTimeout(() => memoizationStore.delete(inputsHash), ttl);
return callResponseOrPromise;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export * from './engine/traceableEngine';
export * from './execution/cache.decorator';
export * from './execution/cache';
export * from './execution/execute';
export * from './execution/memoize.decorator';
export * from './execution/memoize';
export * from './execution/memoizeDecorator';
export * from './timer/executionTimer';
export * from './trace/trace';
export * from './trace/traceDecorator';
Expand Down
Loading