Skip to content
Open
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
4 changes: 3 additions & 1 deletion packages/patch-injection/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './definition';
// export * from './definition';
export * from './makeFunction';
export * from './midleware';
// export * from './addPatch';
48 changes: 17 additions & 31 deletions packages/patch-injection/src/makeFunction.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,26 @@
import { addPatch } from './addPatch';
import { calledFunctions, functions } from './data';
import type { BaseFunction, PatchData, PatchFunction, PatchedFunction } from './definition';
import type { BaseFunction, PatchFunction, PatchedFunction } from './definition';
import { withMiddleware } from './midleware';

export const makeFunction = <T extends BaseFunction>(fn: T): PatchedFunction<T> => {
const patches = new Set<PatchData<T>>();

patches.add({
patchFunction: (_next, ...args) => fn(...args),
});

const result = ((...args: Parameters<T>): ReturnType<T> => {
let newFn: T = fn;

for (const patch of patches) {
if (patch.condition && !patch.condition()) {
continue;
const wrapped = withMiddleware(fn);
const patch = (fn: PatchFunction<T>, condition?: () => boolean) => {
return wrapped.use((ctx, next) => {
if (!condition || condition()) {
return fn(next as unknown as T, ...ctx);
}

const nextFn = newFn;
newFn = ((...args: Parameters<T>) => patch.patchFunction(nextFn, ...args)) as T;
}

calledFunctions.add(result);
return newFn(...args);
}) as PatchedFunction<T>;

functions.set(result, patches as Set<PatchData<BaseFunction>>);

result.patch = (patch: PatchFunction<T>, condition?: () => boolean) => addPatch(result, patch, condition);

result.originalSignature = (() => {
return next(...ctx);
});
};
const originalSignature = (() => {
throw new Error('OriginalSignature of patched functions is not meant to be executed directly.');
}) as unknown as T;
result.patchSignature = (() => {
const patchSignature = (() => {
throw new Error('PatchSignature of patched functions is not meant to be executed directly.');
}) as unknown as PatchFunction<T>;

return result;
return Object.assign(wrapped, {
patch,
originalSignature,
patchSignature,
}) as PatchedFunction<T>;
};
36 changes: 36 additions & 0 deletions packages/patch-injection/src/midleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type Middleware<F extends (...args: any[]) => any> = (ctx: Parameters<F>, next: NextFunction<F>) => ReturnType<F>;
type NextFunction<F extends (...args: any[]) => any> = (...args: Parameters<F> | []) => ReturnType<F>;

export function withMiddleware<F extends (...args: any[]) => any>(fn: F) {
const middlewares: Middleware<F>[] = [];

const buildRunner = (): ((ctx: Parameters<F>) => ReturnType<F>) => {
return middlewares.reduce(
(next, middleware) => {
return (ctx) => middleware(ctx, (...args) => next(args.length ? (args as Parameters<F>) : ctx));
},
(ctx: Parameters<F>) => fn(...ctx),
);
};

let runner = buildRunner();

const use = (middleware: Middleware<F>) => {
middlewares.push(middleware);

runner = buildRunner();
return () => {
const index = middlewares.indexOf(middleware);
if (index > -1) {
middlewares.splice(index, 1);
Comment on lines +18 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Disposer can remove the wrong middleware when the same function is registered twice.

Line 23 uses indexOf(middleware). If the same middleware is registered multiple times, the disposer for the later registration removes the first entry. Wrap the middleware per registration (or store an entry token) so disposal removes the correct instance.

🐛 Ensure disposer targets the exact registration
 const use = (middleware: Middleware<F>) => {
-	middlewares.push(middleware);
+	const entry: Middleware<F> = (ctx, next) => middleware(ctx, next);
+	middlewares.push(entry);

 	runner = buildRunner();
 	return () => {
-		const index = middlewares.indexOf(middleware);
+		const index = middlewares.indexOf(entry);
 		if (index > -1) {
 			middlewares.splice(index, 1);
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const use = (middleware: Middleware<F>) => {
middlewares.push(middleware);
runner = buildRunner();
return () => {
const index = middlewares.indexOf(middleware);
if (index > -1) {
middlewares.splice(index, 1);
const use = (middleware: Middleware<F>) => {
const entry: Middleware<F> = (ctx, next) => middleware(ctx, next);
middlewares.push(entry);
runner = buildRunner();
return () => {
const index = middlewares.indexOf(entry);
if (index > -1) {
middlewares.splice(index, 1);
🤖 Prompt for AI Agents
In `@packages/patch-injection/src/midleware.ts` around lines 18 - 25, The disposer
currently uses middlewares.indexOf(middleware) which will remove the first
matching function when the same middleware is registered multiple times; change
use so each registration gets a unique entry (e.g., wrap the provided middleware
in a per-registration object/wrapper or generate a token) and push that unique
entry into middlewares (use the unique wrapper/token when calling buildRunner);
have the returned disposer remove that exact entry (indexOf(wrapper/token)) so
removals target the correct registration and then rebuild runner with
buildRunner().

}
runner = buildRunner();
};
};

const run = (...args: Parameters<F>): ReturnType<F> => {
return runner(args);
};

return Object.assign(run as F, { use, fn });
}
Loading