fix(core): Prevent proxy layer accumulation in ObservableObject (#30129)

This commit is contained in:
Tomi Turtiainen 2026-05-11 17:29:28 +03:00 committed by GitHub
parent bad43d0c81
commit 0a761355c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 16 additions and 9 deletions

View File

@ -2,13 +2,7 @@ import isObject from 'lodash/isObject';
import set from 'lodash/set';
import { DateTime, Duration, Interval } from 'luxon';
import { getAdditionalKeys } from 'n8n-core';
import {
WorkflowDataProxy,
Workflow,
ObservableObject,
Expression,
jsonStringify,
} from 'n8n-workflow';
import { WorkflowDataProxy, Workflow, Expression, jsonStringify } from 'n8n-workflow';
import type {
CodeExecutionMode,
IWorkflowExecuteAdditionalData,
@ -252,8 +246,6 @@ export class JsTaskRunner extends TaskRunner {
nodeTypes: this.nodeTypes,
});
workflow.staticData = ObservableObject.create(workflow.staticData);
const result =
settings.nodeMode === 'runOnceForAllItems'
? await this.runForAllItems(taskId, settings, data, workflow, abortSignal)

View File

@ -19,6 +19,7 @@ export function create(
for (const key in target) {
if (typeof target[key] === 'object' && target[key] !== null) {
if ('__dataChanged' in (target[key] as object)) continue;
target[key] = create(
target[key] as IDataObject,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing

View File

@ -154,6 +154,20 @@ describe('ObservableObject', () => {
expect((testObject.a! as IDataObject).b).toEqual({ c: 2 });
});
test('should not stack overflow when create is called repeatedly on the same object', () => {
const source = { a: { b: { c: 1 } } };
for (let i = 0; i < 10_000; i++) {
ObservableObject.create(source);
}
const observable = ObservableObject.create(source);
expect(observable.__dataChanged).toBeFalsy();
((observable.a! as IDataObject).b! as IDataObject).c = 2;
expect(observable.__dataChanged).toBeTruthy();
expect(((observable.a! as IDataObject).b! as IDataObject).c).toEqual(2);
});
// test('xxxxxx', () => {
// const testObject = ObservableObject.create({ a: { } }, undefined, { ignoreEmptyOnFirstChild: true });
// expect(testObject.__dataChanged).toBeFalsy();