mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(core): Fix binary data helpers (like prepareBinaryData
) with task runner (#12259)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run
This commit is contained in:
parent
92af245d1a
commit
0f1461f2d5
|
@ -38,6 +38,7 @@
|
||||||
"@sentry/node": "catalog:",
|
"@sentry/node": "catalog:",
|
||||||
"acorn": "8.14.0",
|
"acorn": "8.14.0",
|
||||||
"acorn-walk": "8.3.4",
|
"acorn-walk": "8.3.4",
|
||||||
|
"lodash.set": "4.3.2",
|
||||||
"n8n-core": "workspace:*",
|
"n8n-core": "workspace:*",
|
||||||
"n8n-workflow": "workspace:*",
|
"n8n-workflow": "workspace:*",
|
||||||
"nanoid": "catalog:",
|
"nanoid": "catalog:",
|
||||||
|
@ -45,6 +46,7 @@
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/lodash.set": "4.3.9",
|
||||||
"luxon": "catalog:"
|
"luxon": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import type { IBinaryData } from 'n8n-workflow';
|
||||||
import { setGlobalState, type CodeExecutionMode, type IDataObject } from 'n8n-workflow';
|
import { setGlobalState, type CodeExecutionMode, type IDataObject } from 'n8n-workflow';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { builtinModules } from 'node:module';
|
import { builtinModules } from 'node:module';
|
||||||
|
@ -8,10 +9,15 @@ import type { BaseRunnerConfig } from '@/config/base-runner-config';
|
||||||
import type { JsRunnerConfig } from '@/config/js-runner-config';
|
import type { JsRunnerConfig } from '@/config/js-runner-config';
|
||||||
import { MainConfig } from '@/config/main-config';
|
import { MainConfig } from '@/config/main-config';
|
||||||
import { ExecutionError } from '@/js-task-runner/errors/execution-error';
|
import { ExecutionError } from '@/js-task-runner/errors/execution-error';
|
||||||
|
import { UnsupportedFunctionError } from '@/js-task-runner/errors/unsupported-function.error';
|
||||||
import { ValidationError } from '@/js-task-runner/errors/validation-error';
|
import { ValidationError } from '@/js-task-runner/errors/validation-error';
|
||||||
import type { JSExecSettings } from '@/js-task-runner/js-task-runner';
|
import type { JSExecSettings } from '@/js-task-runner/js-task-runner';
|
||||||
import { JsTaskRunner } from '@/js-task-runner/js-task-runner';
|
import { JsTaskRunner } from '@/js-task-runner/js-task-runner';
|
||||||
import type { DataRequestResponse, InputDataChunkDefinition } from '@/runner-types';
|
import {
|
||||||
|
UNSUPPORTED_HELPER_FUNCTIONS,
|
||||||
|
type DataRequestResponse,
|
||||||
|
type InputDataChunkDefinition,
|
||||||
|
} from '@/runner-types';
|
||||||
import type { Task } from '@/task-runner';
|
import type { Task } from '@/task-runner';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -567,6 +573,120 @@ describe('JsTaskRunner', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('helpers', () => {
|
||||||
|
const binaryDataFile: IBinaryData = {
|
||||||
|
data: 'data',
|
||||||
|
fileName: 'file.txt',
|
||||||
|
mimeType: 'text/plain',
|
||||||
|
};
|
||||||
|
|
||||||
|
const groups = [
|
||||||
|
{
|
||||||
|
method: 'helpers.assertBinaryData',
|
||||||
|
invocation: "helpers.assertBinaryData(0, 'binaryFile')",
|
||||||
|
expectedParams: [0, 'binaryFile'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'helpers.getBinaryDataBuffer',
|
||||||
|
invocation: "helpers.getBinaryDataBuffer(0, 'binaryFile')",
|
||||||
|
expectedParams: [0, 'binaryFile'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'helpers.prepareBinaryData',
|
||||||
|
invocation: "helpers.prepareBinaryData(Buffer.from('123'), 'file.txt', 'text/plain')",
|
||||||
|
expectedParams: [Buffer.from('123'), 'file.txt', 'text/plain'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'helpers.setBinaryDataBuffer',
|
||||||
|
invocation:
|
||||||
|
"helpers.setBinaryDataBuffer({ data: '123', mimeType: 'text/plain' }, Buffer.from('321'))",
|
||||||
|
expectedParams: [{ data: '123', mimeType: 'text/plain' }, Buffer.from('321')],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'helpers.binaryToString',
|
||||||
|
invocation: "helpers.binaryToString(Buffer.from('123'), 'utf8')",
|
||||||
|
expectedParams: [Buffer.from('123'), 'utf8'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: 'helpers.httpRequest',
|
||||||
|
invocation: "helpers.httpRequest({ method: 'GET', url: 'http://localhost' })",
|
||||||
|
expectedParams: [{ method: 'GET', url: 'http://localhost' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const group of groups) {
|
||||||
|
it(`${group.method} for runOnceForAllItems`, async () => {
|
||||||
|
// Arrange
|
||||||
|
const rpcCallSpy = jest
|
||||||
|
.spyOn(defaultTaskRunner, 'makeRpcCall')
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await execTaskWithParams({
|
||||||
|
task: newTaskWithSettings({
|
||||||
|
code: `await ${group.invocation}; return []`,
|
||||||
|
nodeMode: 'runOnceForAllItems',
|
||||||
|
}),
|
||||||
|
taskData: newDataRequestResponse(
|
||||||
|
[{ json: {}, binary: { binaryFile: binaryDataFile } }],
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(rpcCallSpy).toHaveBeenCalledWith('1', group.method, group.expectedParams);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`${group.method} for runOnceForEachItem`, async () => {
|
||||||
|
// Arrange
|
||||||
|
const rpcCallSpy = jest
|
||||||
|
.spyOn(defaultTaskRunner, 'makeRpcCall')
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await execTaskWithParams({
|
||||||
|
task: newTaskWithSettings({
|
||||||
|
code: `await ${group.invocation}; return {}`,
|
||||||
|
nodeMode: 'runOnceForEachItem',
|
||||||
|
}),
|
||||||
|
taskData: newDataRequestResponse(
|
||||||
|
[{ json: {}, binary: { binaryFile: binaryDataFile } }],
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(rpcCallSpy).toHaveBeenCalledWith('1', group.method, group.expectedParams);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('unsupported methods', () => {
|
||||||
|
for (const unsupportedFunction of UNSUPPORTED_HELPER_FUNCTIONS) {
|
||||||
|
it(`should throw an error if ${unsupportedFunction} is used in runOnceForAllItems`, async () => {
|
||||||
|
// Act
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
async () =>
|
||||||
|
await executeForAllItems({
|
||||||
|
code: `${unsupportedFunction}()`,
|
||||||
|
inputItems,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(UnsupportedFunctionError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should throw an error if ${unsupportedFunction} is used in runOnceForEachItem`, async () => {
|
||||||
|
// Act
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
async () =>
|
||||||
|
await executeForEachItem({
|
||||||
|
code: `${unsupportedFunction}()`,
|
||||||
|
inputItems,
|
||||||
|
}),
|
||||||
|
).rejects.toThrow(UnsupportedFunctionError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should allow access to Node.js Buffers', async () => {
|
it('should allow access to Node.js Buffers', async () => {
|
||||||
const outcomeAll = await execTaskWithParams({
|
const outcomeAll = await execTaskWithParams({
|
||||||
task: newTaskWithSettings({
|
task: newTaskWithSettings({
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { ApplicationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error that indicates that a specific function is not available in the
|
||||||
|
* Code Node.
|
||||||
|
*/
|
||||||
|
export class UnsupportedFunctionError extends ApplicationError {
|
||||||
|
constructor(functionName: string) {
|
||||||
|
super(`The function "${functionName}" is not supported in the Code Node`, {
|
||||||
|
level: 'info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import set from 'lodash.set';
|
||||||
import { getAdditionalKeys } from 'n8n-core';
|
import { getAdditionalKeys } from 'n8n-core';
|
||||||
import { WorkflowDataProxy, Workflow, ObservableObject } from 'n8n-workflow';
|
import { WorkflowDataProxy, Workflow, ObservableObject } from 'n8n-workflow';
|
||||||
import type {
|
import type {
|
||||||
|
@ -19,11 +20,14 @@ import * as a from 'node:assert';
|
||||||
import { runInNewContext, type Context } from 'node:vm';
|
import { runInNewContext, type Context } from 'node:vm';
|
||||||
|
|
||||||
import type { MainConfig } from '@/config/main-config';
|
import type { MainConfig } from '@/config/main-config';
|
||||||
import type {
|
import { UnsupportedFunctionError } from '@/js-task-runner/errors/unsupported-function.error';
|
||||||
DataRequestResponse,
|
import {
|
||||||
InputDataChunkDefinition,
|
EXPOSED_RPC_METHODS,
|
||||||
PartialAdditionalData,
|
UNSUPPORTED_HELPER_FUNCTIONS,
|
||||||
TaskResultData,
|
type DataRequestResponse,
|
||||||
|
type InputDataChunkDefinition,
|
||||||
|
type PartialAdditionalData,
|
||||||
|
type TaskResultData,
|
||||||
} from '@/runner-types';
|
} from '@/runner-types';
|
||||||
import { type Task, TaskRunner } from '@/task-runner';
|
import { type Task, TaskRunner } from '@/task-runner';
|
||||||
|
|
||||||
|
@ -38,6 +42,10 @@ import { createRequireResolver } from './require-resolver';
|
||||||
import { validateRunForAllItemsOutput, validateRunForEachItemOutput } from './result-validation';
|
import { validateRunForAllItemsOutput, validateRunForEachItemOutput } from './result-validation';
|
||||||
import { DataRequestResponseReconstruct } from '../data-request/data-request-response-reconstruct';
|
import { DataRequestResponseReconstruct } from '../data-request/data-request-response-reconstruct';
|
||||||
|
|
||||||
|
export interface RPCCallObject {
|
||||||
|
[name: string]: ((...args: unknown[]) => Promise<unknown>) | RPCCallObject;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JSExecSettings {
|
export interface JSExecSettings {
|
||||||
code: string;
|
code: string;
|
||||||
nodeMode: CodeExecutionMode;
|
nodeMode: CodeExecutionMode;
|
||||||
|
@ -439,4 +447,24 @@ export class JsTaskRunner extends TaskRunner {
|
||||||
this.nodeTypes.addNodeTypeDescriptions(nodeTypes);
|
this.nodeTypes.addNodeTypeDescriptions(nodeTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildRpcCallObject(taskId: string) {
|
||||||
|
const rpcObject: RPCCallObject = {};
|
||||||
|
|
||||||
|
for (const rpcMethod of EXPOSED_RPC_METHODS) {
|
||||||
|
set(
|
||||||
|
rpcObject,
|
||||||
|
rpcMethod.split('.'),
|
||||||
|
async (...args: unknown[]) => await this.makeRpcCall(taskId, rpcMethod, args),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rpcMethod of UNSUPPORTED_HELPER_FUNCTIONS) {
|
||||||
|
set(rpcObject, rpcMethod.split('.'), () => {
|
||||||
|
throw new UnsupportedFunctionError(rpcMethod);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpcObject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { INodeTypeBaseDescription } from 'n8n-workflow';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
NeededNodeType,
|
NeededNodeType,
|
||||||
RPC_ALLOW_LIST,
|
AVAILABLE_RPC_METHODS,
|
||||||
TaskDataRequestParams,
|
TaskDataRequestParams,
|
||||||
TaskResultData,
|
TaskResultData,
|
||||||
} from './runner-types';
|
} from './runner-types';
|
||||||
|
@ -105,7 +105,7 @@ export namespace BrokerMessage {
|
||||||
type: 'broker:rpc';
|
type: 'broker:rpc';
|
||||||
callId: string;
|
callId: string;
|
||||||
taskId: string;
|
taskId: string;
|
||||||
name: (typeof RPC_ALLOW_LIST)[number];
|
name: (typeof AVAILABLE_RPC_METHODS)[number];
|
||||||
params: unknown[];
|
params: unknown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ export namespace RunnerMessage {
|
||||||
type: 'runner:rpc';
|
type: 'runner:rpc';
|
||||||
callId: string;
|
callId: string;
|
||||||
taskId: string;
|
taskId: string;
|
||||||
name: (typeof RPC_ALLOW_LIST)[number];
|
name: (typeof AVAILABLE_RPC_METHODS)[number];
|
||||||
params: unknown[];
|
params: unknown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,31 +100,73 @@ export interface PartialAdditionalData {
|
||||||
variables: IDataObject;
|
variables: IDataObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RPC_ALLOW_LIST = [
|
/** RPC methods that are exposed directly to the Code Node */
|
||||||
|
export const EXPOSED_RPC_METHODS = [
|
||||||
|
// assertBinaryData(itemIndex: number, propertyName: string): Promise<IBinaryData>
|
||||||
|
'helpers.assertBinaryData',
|
||||||
|
|
||||||
|
// getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>
|
||||||
|
'helpers.getBinaryDataBuffer',
|
||||||
|
|
||||||
|
// prepareBinaryData(binaryData: Buffer, fileName?: string, mimeType?: string): Promise<IBinaryData>
|
||||||
|
'helpers.prepareBinaryData',
|
||||||
|
|
||||||
|
// setBinaryDataBuffer(metadata: IBinaryData, buffer: Buffer): Promise<IBinaryData>
|
||||||
|
'helpers.setBinaryDataBuffer',
|
||||||
|
|
||||||
|
// binaryToString(body: Buffer, encoding?: string): string
|
||||||
|
'helpers.binaryToString',
|
||||||
|
|
||||||
|
// httpRequest(opts: IHttpRequestOptions): Promise<IN8nHttpFullResponse | IN8nHttpResponse>
|
||||||
|
'helpers.httpRequest',
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Helpers that exist but that we are not exposing to the Code Node */
|
||||||
|
export const UNSUPPORTED_HELPER_FUNCTIONS = [
|
||||||
|
// These rely on checking the credentials from the current node type (Code Node)
|
||||||
|
// and hence they can't even work (Code Node doesn't have credentials)
|
||||||
'helpers.httpRequestWithAuthentication',
|
'helpers.httpRequestWithAuthentication',
|
||||||
'helpers.requestWithAuthenticationPaginated',
|
'helpers.requestWithAuthenticationPaginated',
|
||||||
// "helpers.normalizeItems"
|
|
||||||
// "helpers.constructExecutionMetaData"
|
// This has been removed
|
||||||
// "helpers.assertBinaryData"
|
|
||||||
'helpers.getBinaryDataBuffer',
|
|
||||||
// "helpers.copyInputItems"
|
|
||||||
// "helpers.returnJsonArray"
|
|
||||||
'helpers.getSSHClient',
|
|
||||||
'helpers.createReadStream',
|
|
||||||
// "helpers.getStoragePath"
|
|
||||||
'helpers.writeContentToFile',
|
|
||||||
'helpers.prepareBinaryData',
|
|
||||||
'helpers.setBinaryDataBuffer',
|
|
||||||
'helpers.copyBinaryFile',
|
'helpers.copyBinaryFile',
|
||||||
'helpers.binaryToBuffer',
|
|
||||||
// "helpers.binaryToString"
|
// We can't support streams over RPC without implementing it ourselves
|
||||||
// "helpers.getBinaryPath"
|
'helpers.createReadStream',
|
||||||
'helpers.getBinaryStream',
|
'helpers.getBinaryStream',
|
||||||
|
|
||||||
|
// Makes no sense to support this, as it returns either a stream or a buffer
|
||||||
|
// and we can't support streams over RPC
|
||||||
|
'helpers.binaryToBuffer',
|
||||||
|
|
||||||
|
// These are pretty low-level, so we shouldn't expose them
|
||||||
|
// (require binary data id, which we don't expose)
|
||||||
'helpers.getBinaryMetadata',
|
'helpers.getBinaryMetadata',
|
||||||
|
'helpers.getStoragePath',
|
||||||
|
'helpers.getBinaryPath',
|
||||||
|
|
||||||
|
// We shouldn't allow arbitrary FS writes
|
||||||
|
'helpers.writeContentToFile',
|
||||||
|
|
||||||
|
// Not something we need to expose. Can be done in the node itself
|
||||||
|
// copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[]
|
||||||
|
'helpers.copyInputItems',
|
||||||
|
|
||||||
|
// Code Node does these automatically already
|
||||||
|
'helpers.returnJsonArray',
|
||||||
|
'helpers.normalizeItems',
|
||||||
|
|
||||||
|
// The client is instantiated and lives on the n8n instance, so we can't
|
||||||
|
// expose it over RPC without implementing object marshalling
|
||||||
|
'helpers.getSSHClient',
|
||||||
|
|
||||||
|
// Doesn't make sense to expose
|
||||||
'helpers.createDeferredPromise',
|
'helpers.createDeferredPromise',
|
||||||
'helpers.httpRequest',
|
'helpers.constructExecutionMetaData',
|
||||||
'logNodeOutput',
|
];
|
||||||
] as const;
|
|
||||||
|
/** List of all RPC methods that task runner supports */
|
||||||
|
export const AVAILABLE_RPC_METHODS = [...EXPOSED_RPC_METHODS, 'logNodeOutput'] as const;
|
||||||
|
|
||||||
/** Node types needed for the runner to execute a task. */
|
/** Node types needed for the runner to execute a task. */
|
||||||
export type NeededNodeType = { name: string; version: number };
|
export type NeededNodeType = { name: string; version: number };
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { isSerializedBuffer, toBuffer } from 'n8n-core';
|
||||||
import { ApplicationError, ensureError, randomInt } from 'n8n-workflow';
|
import { ApplicationError, ensureError, randomInt } from 'n8n-workflow';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
|
@ -6,7 +7,7 @@ import { type MessageEvent, WebSocket } from 'ws';
|
||||||
import type { BaseRunnerConfig } from '@/config/base-runner-config';
|
import type { BaseRunnerConfig } from '@/config/base-runner-config';
|
||||||
import type { BrokerMessage, RunnerMessage } from '@/message-types';
|
import type { BrokerMessage, RunnerMessage } from '@/message-types';
|
||||||
import { TaskRunnerNodeTypes } from '@/node-types';
|
import { TaskRunnerNodeTypes } from '@/node-types';
|
||||||
import { RPC_ALLOW_LIST, type TaskResultData } from '@/runner-types';
|
import type { TaskResultData } from '@/runner-types';
|
||||||
|
|
||||||
import { TaskCancelledError } from './js-task-runner/errors/task-cancelled-error';
|
import { TaskCancelledError } from './js-task-runner/errors/task-cancelled-error';
|
||||||
|
|
||||||
|
@ -42,10 +43,6 @@ interface RPCCall {
|
||||||
reject: (error: unknown) => void;
|
reject: (error: unknown) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RPCCallObject {
|
|
||||||
[name: string]: ((...args: unknown[]) => Promise<unknown>) | RPCCallObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OFFER_VALID_TIME_MS = 5000;
|
const OFFER_VALID_TIME_MS = 5000;
|
||||||
const OFFER_VALID_EXTRA_MS = 100;
|
const OFFER_VALID_EXTRA_MS = 100;
|
||||||
|
|
||||||
|
@ -464,7 +461,9 @@ export abstract class TaskRunner extends EventEmitter {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await dataPromise;
|
const returnValue = await dataPromise;
|
||||||
|
|
||||||
|
return isSerializedBuffer(returnValue) ? toBuffer(returnValue) : returnValue;
|
||||||
} finally {
|
} finally {
|
||||||
this.rpcCalls.delete(callId);
|
this.rpcCalls.delete(callId);
|
||||||
}
|
}
|
||||||
|
@ -486,24 +485,6 @@ export abstract class TaskRunner extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRpcCallObject(taskId: string) {
|
|
||||||
const rpcObject: RPCCallObject = {};
|
|
||||||
for (const r of RPC_ALLOW_LIST) {
|
|
||||||
const splitPath = r.split('.');
|
|
||||||
let obj = rpcObject;
|
|
||||||
|
|
||||||
splitPath.forEach((s, index) => {
|
|
||||||
if (index !== splitPath.length - 1) {
|
|
||||||
obj[s] = {};
|
|
||||||
obj = obj[s];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
obj[s] = async (...args: unknown[]) => await this.makeRpcCall(taskId, r, args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return rpcObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Close the connection gracefully and wait until has been closed */
|
/** Close the connection gracefully and wait until has been closed */
|
||||||
async stop() {
|
async stop() {
|
||||||
this.clearIdleTimer();
|
this.clearIdleTimer();
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { get, set } from 'lodash';
|
||||||
|
|
||||||
|
import type { NodeTypes } from '@/node-types';
|
||||||
|
import type { Task } from '@/runners/task-managers/task-manager';
|
||||||
|
import { TaskManager } from '@/runners/task-managers/task-manager';
|
||||||
|
|
||||||
|
class TestTaskManager extends TaskManager {
|
||||||
|
sentMessages: unknown[] = [];
|
||||||
|
|
||||||
|
sendMessage(message: unknown) {
|
||||||
|
this.sentMessages.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TaskManager', () => {
|
||||||
|
let instance: TestTaskManager;
|
||||||
|
const mockNodeTypes = mock<NodeTypes>();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
instance = new TestTaskManager(mockNodeTypes);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleRpc', () => {
|
||||||
|
test.each([
|
||||||
|
['logNodeOutput', ['hello world']],
|
||||||
|
['helpers.assertBinaryData', [0, 'propertyName']],
|
||||||
|
['helpers.getBinaryDataBuffer', [0, 'propertyName']],
|
||||||
|
['helpers.prepareBinaryData', [Buffer.from('data').toJSON(), 'filename', 'mimetype']],
|
||||||
|
['helpers.setBinaryDataBuffer', [{ data: '123' }, Buffer.from('data').toJSON()]],
|
||||||
|
['helpers.binaryToString', [Buffer.from('data').toJSON(), 'utf8']],
|
||||||
|
['helpers.httpRequest', [{ url: 'http://localhost' }]],
|
||||||
|
])('should handle %s rpc call', async (methodName, args) => {
|
||||||
|
const executeFunctions = set({}, methodName.split('.'), jest.fn());
|
||||||
|
|
||||||
|
const mockTask = mock<Task>({
|
||||||
|
taskId: 'taskId',
|
||||||
|
data: {
|
||||||
|
executeFunctions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
instance.tasks.set('taskId', mockTask);
|
||||||
|
|
||||||
|
await instance.handleRpc('taskId', 'callId', methodName, args);
|
||||||
|
|
||||||
|
expect(instance.sentMessages).toEqual([
|
||||||
|
{
|
||||||
|
callId: 'callId',
|
||||||
|
data: undefined,
|
||||||
|
status: 'success',
|
||||||
|
taskId: 'taskId',
|
||||||
|
type: 'requester:rpcresponse',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(get(executeFunctions, methodName.split('.'))).toHaveBeenCalledWith(...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts any serialized buffer arguments into buffers', async () => {
|
||||||
|
const mockPrepareBinaryData = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const mockTask = mock<Task>({
|
||||||
|
taskId: 'taskId',
|
||||||
|
data: {
|
||||||
|
executeFunctions: {
|
||||||
|
helpers: {
|
||||||
|
prepareBinaryData: mockPrepareBinaryData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
instance.tasks.set('taskId', mockTask);
|
||||||
|
|
||||||
|
await instance.handleRpc('taskId', 'callId', 'helpers.prepareBinaryData', [
|
||||||
|
Buffer.from('data').toJSON(),
|
||||||
|
'filename',
|
||||||
|
'mimetype',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(mockPrepareBinaryData).toHaveBeenCalledWith(
|
||||||
|
Buffer.from('data'),
|
||||||
|
'filename',
|
||||||
|
'mimetype',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
it('sends method not allowed error if method is not in the allow list', async () => {
|
||||||
|
const mockTask = mock<Task>({
|
||||||
|
taskId: 'taskId',
|
||||||
|
data: {
|
||||||
|
executeFunctions: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
instance.tasks.set('taskId', mockTask);
|
||||||
|
|
||||||
|
await instance.handleRpc('taskId', 'callId', 'notAllowedMethod', []);
|
||||||
|
|
||||||
|
expect(instance.sentMessages).toEqual([
|
||||||
|
{
|
||||||
|
callId: 'callId',
|
||||||
|
data: 'Method not allowed',
|
||||||
|
status: 'error',
|
||||||
|
taskId: 'taskId',
|
||||||
|
type: 'requester:rpcresponse',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends error if method throws', async () => {
|
||||||
|
const error = new Error('Test error');
|
||||||
|
const mockTask = mock<Task>({
|
||||||
|
taskId: 'taskId',
|
||||||
|
data: {
|
||||||
|
executeFunctions: {
|
||||||
|
helpers: {
|
||||||
|
assertBinaryData: jest.fn().mockRejectedValue(error),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
instance.tasks.set('taskId', mockTask);
|
||||||
|
|
||||||
|
await instance.handleRpc('taskId', 'callId', 'helpers.assertBinaryData', []);
|
||||||
|
|
||||||
|
expect(instance.sentMessages).toEqual([
|
||||||
|
{
|
||||||
|
callId: 'callId',
|
||||||
|
data: error,
|
||||||
|
status: 'error',
|
||||||
|
taskId: 'taskId',
|
||||||
|
type: 'requester:rpcresponse',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,6 @@
|
||||||
import type { TaskResultData, RequesterMessage, BrokerMessage, TaskData } from '@n8n/task-runner';
|
import type { TaskResultData, RequesterMessage, BrokerMessage, TaskData } from '@n8n/task-runner';
|
||||||
import { RPC_ALLOW_LIST } from '@n8n/task-runner';
|
import { AVAILABLE_RPC_METHODS } from '@n8n/task-runner';
|
||||||
|
import { isSerializedBuffer, toBuffer } from 'n8n-core';
|
||||||
import { createResultOk, createResultError } from 'n8n-workflow';
|
import { createResultOk, createResultError } from 'n8n-workflow';
|
||||||
import type {
|
import type {
|
||||||
EnvProviderState,
|
EnvProviderState,
|
||||||
|
@ -288,7 +289,7 @@ export abstract class TaskManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!RPC_ALLOW_LIST.includes(name)) {
|
if (!AVAILABLE_RPC_METHODS.includes(name)) {
|
||||||
this.sendMessage({
|
this.sendMessage({
|
||||||
type: 'requester:rpcresponse',
|
type: 'requester:rpcresponse',
|
||||||
taskId,
|
taskId,
|
||||||
|
@ -322,6 +323,15 @@ export abstract class TaskManager {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert any serialized buffers back to buffers
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
const paramValue = params[i];
|
||||||
|
if (isSerializedBuffer(paramValue)) {
|
||||||
|
params[i] = toBuffer(paramValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const data = (await func.call(funcs, ...params)) as unknown;
|
const data = (await func.call(funcs, ...params)) as unknown;
|
||||||
|
|
||||||
this.sendMessage({
|
this.sendMessage({
|
||||||
|
|
24
packages/core/src/SerializedBuffer.ts
Normal file
24
packages/core/src/SerializedBuffer.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
/** A nodejs Buffer gone through JSON.stringify */
|
||||||
|
export type SerializedBuffer = {
|
||||||
|
type: 'Buffer';
|
||||||
|
data: number[]; // Array like Uint8Array, each item is uint8 (0-255)
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Converts the given SerializedBuffer to nodejs Buffer */
|
||||||
|
export function toBuffer(serializedBuffer: SerializedBuffer): Buffer {
|
||||||
|
return Buffer.from(serializedBuffer.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObjectLiteral(item: unknown): item is { [key: string]: unknown } {
|
||||||
|
return typeof item === 'object' && item !== null && !Array.isArray(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSerializedBuffer(candidate: unknown): candidate is SerializedBuffer {
|
||||||
|
return (
|
||||||
|
isObjectLiteral(candidate) &&
|
||||||
|
'type' in candidate &&
|
||||||
|
'data' in candidate &&
|
||||||
|
candidate.type === 'Buffer' &&
|
||||||
|
Array.isArray(candidate.data)
|
||||||
|
);
|
||||||
|
}
|
|
@ -24,3 +24,4 @@ export * from './ExecutionMetadata';
|
||||||
export * from './node-execution-context';
|
export * from './node-execution-context';
|
||||||
export * from './PartialExecutionUtils';
|
export * from './PartialExecutionUtils';
|
||||||
export { ErrorReporter } from './error-reporter';
|
export { ErrorReporter } from './error-reporter';
|
||||||
|
export * from './SerializedBuffer';
|
||||||
|
|
55
packages/core/test/SerializedBuffer.test.ts
Normal file
55
packages/core/test/SerializedBuffer.test.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import type { SerializedBuffer } from '@/SerializedBuffer';
|
||||||
|
import { toBuffer, isSerializedBuffer } from '@/SerializedBuffer';
|
||||||
|
|
||||||
|
// Mock data for tests
|
||||||
|
const validSerializedBuffer: SerializedBuffer = {
|
||||||
|
type: 'Buffer',
|
||||||
|
data: [65, 66, 67], // Corresponds to 'ABC' in ASCII
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('serializedBufferToBuffer', () => {
|
||||||
|
it('should convert a SerializedBuffer to a Buffer', () => {
|
||||||
|
const buffer = toBuffer(validSerializedBuffer);
|
||||||
|
expect(buffer).toBeInstanceOf(Buffer);
|
||||||
|
expect(buffer.toString()).toBe('ABC');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serialize stringified buffer to the same buffer', () => {
|
||||||
|
const serializedBuffer = JSON.stringify(Buffer.from('n8n on the rocks'));
|
||||||
|
const buffer = toBuffer(JSON.parse(serializedBuffer));
|
||||||
|
expect(buffer).toBeInstanceOf(Buffer);
|
||||||
|
expect(buffer.toString()).toBe('n8n on the rocks');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isSerializedBuffer', () => {
|
||||||
|
it('should return true for a valid SerializedBuffer', () => {
|
||||||
|
expect(isSerializedBuffer(validSerializedBuffer)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
[{ data: [1, 2, 3] }],
|
||||||
|
[{ data: [1, 2, 256] }],
|
||||||
|
[{ type: 'Buffer', data: 'notAnArray' }],
|
||||||
|
[{ data: 42 }],
|
||||||
|
[{ data: 'test' }],
|
||||||
|
[{ data: true }],
|
||||||
|
[null],
|
||||||
|
[undefined],
|
||||||
|
[42],
|
||||||
|
[{}],
|
||||||
|
])('should return false for %s', (value) => {
|
||||||
|
expect(isSerializedBuffer(value)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Integration: serializedBufferToBuffer and isSerializedBuffer', () => {
|
||||||
|
it('should correctly validate and convert a SerializedBuffer', () => {
|
||||||
|
if (isSerializedBuffer(validSerializedBuffer)) {
|
||||||
|
const buffer = toBuffer(validSerializedBuffer);
|
||||||
|
expect(buffer.toString()).toBe('ABC');
|
||||||
|
} else {
|
||||||
|
fail('Expected validSerializedBuffer to be a SerializedBuffer');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
118
pnpm-lock.yaml
118
pnpm-lock.yaml
|
@ -672,6 +672,9 @@ importers:
|
||||||
acorn-walk:
|
acorn-walk:
|
||||||
specifier: 8.3.4
|
specifier: 8.3.4
|
||||||
version: 8.3.4
|
version: 8.3.4
|
||||||
|
lodash.set:
|
||||||
|
specifier: 4.3.2
|
||||||
|
version: 4.3.2
|
||||||
n8n-core:
|
n8n-core:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../core
|
version: link:../../core
|
||||||
|
@ -688,6 +691,9 @@ importers:
|
||||||
specifier: '>=8.17.1'
|
specifier: '>=8.17.1'
|
||||||
version: 8.17.1
|
version: 8.17.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/lodash.set':
|
||||||
|
specifier: 4.3.9
|
||||||
|
version: 4.3.9
|
||||||
luxon:
|
luxon:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 3.4.4
|
version: 3.4.4
|
||||||
|
@ -1114,7 +1120,7 @@ importers:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@langchain/core':
|
'@langchain/core':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.3.19(openai@4.73.1(zod@3.23.8))
|
version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
|
||||||
'@n8n/client-oauth2':
|
'@n8n/client-oauth2':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../@n8n/client-oauth2
|
version: link:../@n8n/client-oauth2
|
||||||
|
@ -1966,7 +1972,7 @@ importers:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@langchain/core':
|
'@langchain/core':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 0.3.19(openai@4.73.1)
|
version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
|
||||||
'@types/deep-equal':
|
'@types/deep-equal':
|
||||||
specifier: ^1.0.1
|
specifier: ^1.0.1
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
|
@ -5621,6 +5627,9 @@ packages:
|
||||||
'@types/lodash-es@4.17.6':
|
'@types/lodash-es@4.17.6':
|
||||||
resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
|
resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
|
||||||
|
|
||||||
|
'@types/lodash.set@4.3.9':
|
||||||
|
resolution: {integrity: sha512-KOxyNkZpbaggVmqbpr82N2tDVTx05/3/j0f50Es1prxrWB0XYf9p3QNxqcbWb7P1Q9wlvsUSlCFnwlPCIJ46PQ==}
|
||||||
|
|
||||||
'@types/lodash@4.14.195':
|
'@types/lodash@4.14.195':
|
||||||
resolution: {integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==}
|
resolution: {integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==}
|
||||||
|
|
||||||
|
@ -9736,6 +9745,9 @@ packages:
|
||||||
lodash.orderby@4.6.0:
|
lodash.orderby@4.6.0:
|
||||||
resolution: {integrity: sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==}
|
resolution: {integrity: sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==}
|
||||||
|
|
||||||
|
lodash.set@4.3.2:
|
||||||
|
resolution: {integrity: sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==}
|
||||||
|
|
||||||
lodash.throttle@4.1.1:
|
lodash.throttle@4.1.1:
|
||||||
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
|
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
|
||||||
|
|
||||||
|
@ -16206,38 +16218,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- openai
|
- openai
|
||||||
|
|
||||||
'@langchain/core@0.3.19(openai@4.73.1(zod@3.23.8))':
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: 5.2.0
|
|
||||||
camelcase: 6.3.0
|
|
||||||
decamelize: 1.2.0
|
|
||||||
js-tiktoken: 1.0.12
|
|
||||||
langsmith: 0.2.3(openai@4.73.1(zod@3.23.8))
|
|
||||||
mustache: 4.2.0
|
|
||||||
p-queue: 6.6.2
|
|
||||||
p-retry: 4.6.2
|
|
||||||
uuid: 10.0.0
|
|
||||||
zod: 3.23.8
|
|
||||||
zod-to-json-schema: 3.23.3(zod@3.23.8)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- openai
|
|
||||||
|
|
||||||
'@langchain/core@0.3.19(openai@4.73.1)':
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: 5.2.0
|
|
||||||
camelcase: 6.3.0
|
|
||||||
decamelize: 1.2.0
|
|
||||||
js-tiktoken: 1.0.12
|
|
||||||
langsmith: 0.2.3(openai@4.73.1)
|
|
||||||
mustache: 4.2.0
|
|
||||||
p-queue: 6.6.2
|
|
||||||
p-retry: 4.6.2
|
|
||||||
uuid: 10.0.0
|
|
||||||
zod: 3.23.8
|
|
||||||
zod-to-json-schema: 3.23.3(zod@3.23.8)
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- openai
|
|
||||||
|
|
||||||
'@langchain/google-common@0.1.3(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(zod@3.23.8)':
|
'@langchain/google-common@0.1.3(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(zod@3.23.8)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
|
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
|
||||||
|
@ -18361,6 +18341,10 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/lodash': 4.14.195
|
'@types/lodash': 4.14.195
|
||||||
|
|
||||||
|
'@types/lodash.set@4.3.9':
|
||||||
|
dependencies:
|
||||||
|
'@types/lodash': 4.14.195
|
||||||
|
|
||||||
'@types/lodash@4.14.195': {}
|
'@types/lodash@4.14.195': {}
|
||||||
|
|
||||||
'@types/long@4.0.2': {}
|
'@types/long@4.0.2': {}
|
||||||
|
@ -19466,14 +19450,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
|
||||||
axios@1.7.7:
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: 1.15.6(debug@4.3.6)
|
|
||||||
form-data: 4.0.0
|
|
||||||
proxy-from-env: 1.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
|
|
||||||
axios@1.7.7(debug@4.3.6):
|
axios@1.7.7(debug@4.3.6):
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.6(debug@4.3.6)
|
follow-redirects: 1.15.6(debug@4.3.6)
|
||||||
|
@ -21187,7 +21163,7 @@ snapshots:
|
||||||
|
|
||||||
eslint-import-resolver-node@0.3.9:
|
eslint-import-resolver-node@0.3.9:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
is-core-module: 2.13.1
|
is-core-module: 2.13.1
|
||||||
resolve: 1.22.8
|
resolve: 1.22.8
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
@ -21212,7 +21188,7 @@ snapshots:
|
||||||
|
|
||||||
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
|
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.7.2)
|
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.7.2)
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
|
@ -21232,7 +21208,7 @@ snapshots:
|
||||||
array.prototype.findlastindex: 1.2.3
|
array.prototype.findlastindex: 1.2.3
|
||||||
array.prototype.flat: 1.3.2
|
array.prototype.flat: 1.3.2
|
||||||
array.prototype.flatmap: 1.3.2
|
array.prototype.flatmap: 1.3.2
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
doctrine: 2.1.0
|
doctrine: 2.1.0
|
||||||
eslint: 8.57.0
|
eslint: 8.57.0
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
|
@ -22011,7 +21987,7 @@ snapshots:
|
||||||
array-parallel: 0.1.3
|
array-parallel: 0.1.3
|
||||||
array-series: 0.1.5
|
array-series: 0.1.5
|
||||||
cross-spawn: 4.0.2
|
cross-spawn: 4.0.2
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -22392,7 +22368,7 @@ snapshots:
|
||||||
|
|
||||||
infisical-node@1.3.0:
|
infisical-node@1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 1.7.7
|
axios: 1.7.7(debug@4.3.6)
|
||||||
dotenv: 16.3.1
|
dotenv: 16.3.1
|
||||||
tweetnacl: 1.0.3
|
tweetnacl: 1.0.3
|
||||||
tweetnacl-util: 0.15.1
|
tweetnacl-util: 0.15.1
|
||||||
|
@ -23369,28 +23345,6 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
openai: 4.73.1(encoding@0.1.13)(zod@3.23.8)
|
openai: 4.73.1(encoding@0.1.13)(zod@3.23.8)
|
||||||
|
|
||||||
langsmith@0.2.3(openai@4.73.1(zod@3.23.8)):
|
|
||||||
dependencies:
|
|
||||||
'@types/uuid': 10.0.0
|
|
||||||
commander: 10.0.1
|
|
||||||
p-queue: 6.6.2
|
|
||||||
p-retry: 4.6.2
|
|
||||||
semver: 7.6.0
|
|
||||||
uuid: 10.0.0
|
|
||||||
optionalDependencies:
|
|
||||||
openai: 4.73.1(zod@3.23.8)
|
|
||||||
|
|
||||||
langsmith@0.2.3(openai@4.73.1):
|
|
||||||
dependencies:
|
|
||||||
'@types/uuid': 10.0.0
|
|
||||||
commander: 10.0.1
|
|
||||||
p-queue: 6.6.2
|
|
||||||
p-retry: 4.6.2
|
|
||||||
semver: 7.6.0
|
|
||||||
uuid: 10.0.0
|
|
||||||
optionalDependencies:
|
|
||||||
openai: 4.73.1(zod@3.23.8)
|
|
||||||
|
|
||||||
lazy-ass@1.6.0: {}
|
lazy-ass@1.6.0: {}
|
||||||
|
|
||||||
ldapts@4.2.6:
|
ldapts@4.2.6:
|
||||||
|
@ -23572,6 +23526,8 @@ snapshots:
|
||||||
|
|
||||||
lodash.orderby@4.6.0: {}
|
lodash.orderby@4.6.0: {}
|
||||||
|
|
||||||
|
lodash.set@4.3.2: {}
|
||||||
|
|
||||||
lodash.throttle@4.1.1: {}
|
lodash.throttle@4.1.1: {}
|
||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
|
@ -24723,22 +24679,6 @@ snapshots:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
openai@4.73.1(zod@3.23.8):
|
|
||||||
dependencies:
|
|
||||||
'@types/node': 18.16.16
|
|
||||||
'@types/node-fetch': 2.6.4
|
|
||||||
abort-controller: 3.0.0
|
|
||||||
agentkeepalive: 4.2.1
|
|
||||||
form-data-encoder: 1.7.2
|
|
||||||
formdata-node: 4.4.1
|
|
||||||
node-fetch: 2.7.0(encoding@0.1.13)
|
|
||||||
optionalDependencies:
|
|
||||||
zod: 3.23.8
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- encoding
|
|
||||||
- supports-color
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
openapi-sampler@1.5.1:
|
openapi-sampler@1.5.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/json-schema': 7.0.15
|
'@types/json-schema': 7.0.15
|
||||||
|
@ -24919,7 +24859,7 @@ snapshots:
|
||||||
|
|
||||||
pdf-parse@1.1.1:
|
pdf-parse@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
node-ensure: 0.0.0
|
node-ensure: 0.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
@ -25121,7 +25061,7 @@ snapshots:
|
||||||
|
|
||||||
posthog-node@3.2.1:
|
posthog-node@3.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
axios: 1.7.7
|
axios: 1.7.7(debug@4.3.6)
|
||||||
rusha: 0.8.14
|
rusha: 0.8.14
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
@ -25761,7 +25701,7 @@ snapshots:
|
||||||
|
|
||||||
rhea@1.0.24:
|
rhea@1.0.24:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7(supports-color@5.5.0)
|
debug: 3.2.7(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
@ -26139,7 +26079,7 @@ snapshots:
|
||||||
asn1.js: 5.4.1
|
asn1.js: 5.4.1
|
||||||
asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1)
|
asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1)
|
||||||
asn1.js-rfc5280: 3.0.0
|
asn1.js-rfc5280: 3.0.0
|
||||||
axios: 1.7.7
|
axios: 1.7.7(debug@4.3.6)
|
||||||
big-integer: 1.6.51
|
big-integer: 1.6.51
|
||||||
bignumber.js: 9.1.2
|
bignumber.js: 9.1.2
|
||||||
binascii: 0.0.2
|
binascii: 0.0.2
|
||||||
|
|
Loading…
Reference in a new issue