mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
chore(core): Enable TypeScript strict mode in packages/cli (no-changelog) (#27876)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
553976d065
commit
370b281216
|
|
@ -447,6 +447,10 @@ export type AuthenticatedRequest<
|
|||
tokenGrant?: TokenGrant;
|
||||
};
|
||||
|
||||
export function isAuthenticatedRequest(req: express.Request): req is AuthenticatedRequest {
|
||||
return 'user' in req && req.user !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified to prevent excessively deep type instantiation error from
|
||||
* `INodeExecutionData` in `IPinData` in a TypeORM entity field.
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ import 'reflect-metadata';
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type Constructable<T = unknown> = new (...args: any[]) => T;
|
||||
|
||||
type AbstractConstructable<T = unknown> = abstract new (...args: unknown[]) => T;
|
||||
export type AbstractConstructable<T = unknown> = abstract new (...args: unknown[]) => T;
|
||||
|
||||
type ServiceIdentifier<T = unknown> = Constructable<T> | AbstractConstructable<T>;
|
||||
export type ServiceIdentifier<T = unknown> = Constructable<T> | AbstractConstructable<T>;
|
||||
|
||||
type Factory<T = unknown> = (...args: unknown[]) => T;
|
||||
|
||||
|
|
|
|||
|
|
@ -176,17 +176,17 @@ describe('NodeTypes', () => {
|
|||
expect(result.description.outputs).toEqual(['ai_tool']);
|
||||
});
|
||||
|
||||
it('should return a declarative node-type with an `.execute` method', () => {
|
||||
it('should return a declarative node-type with an `.execute` method', async () => {
|
||||
const result = nodeTypes.getByNameAndVersion('n8n-nodes-base.declarativeNode');
|
||||
expect(result).toBe(declarativeNode.type);
|
||||
expect(result.execute).toBeDefined();
|
||||
|
||||
const runNodeSpy = jest.spyOn(RoutingNode.prototype, 'runNode').mockResolvedValue([]);
|
||||
result.execute!.call(mock());
|
||||
await result.execute!.call(mock());
|
||||
expect(runNodeSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return a declarative node-type as a tool with an `.execute` method', () => {
|
||||
it('should return a declarative node-type as a tool with an `.execute` method', async () => {
|
||||
const result = nodeTypes.getByNameAndVersion('n8n-nodes-base.declarativeNodeTool');
|
||||
expect(result).not.toEqual(declarativeNode.type);
|
||||
expect(result.description.name).toEqual('n8n-nodes-base.declarativeNodeTool');
|
||||
|
|
@ -197,7 +197,7 @@ describe('NodeTypes', () => {
|
|||
expect(result.execute).toBeDefined();
|
||||
|
||||
const runNodeSpy = jest.spyOn(RoutingNode.prototype, 'runNode').mockResolvedValue([]);
|
||||
result.execute!.call(mock());
|
||||
await result.execute!.call(mock());
|
||||
expect(runNodeSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -67,8 +67,6 @@ export abstract class AbstractServer {
|
|||
|
||||
private fullyReady = false;
|
||||
|
||||
readonly uniqueInstanceId: string;
|
||||
|
||||
constructor() {
|
||||
this.app = express();
|
||||
this.app.disable('x-powered-by');
|
||||
|
|
|
|||
|
|
@ -40,9 +40,9 @@ describe('ConcurrencyControlService', () => {
|
|||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it.each(['production', 'evaluation'])(
|
||||
it.each<ConcurrencyQueueType>(['production', 'evaluation'])(
|
||||
'should be enabled if %s cap is positive',
|
||||
(type: ConcurrencyQueueType) => {
|
||||
(type) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -72,9 +72,9 @@ describe('ConcurrencyControlService', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.each(['production', 'evaluation'])(
|
||||
it.each<ConcurrencyQueueType>(['production', 'evaluation'])(
|
||||
'should throw if %s cap is 0',
|
||||
(type: ConcurrencyQueueType) => {
|
||||
(type) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -126,9 +126,9 @@ describe('ConcurrencyControlService', () => {
|
|||
expect(service.isEnabled).toBe(false);
|
||||
});
|
||||
|
||||
it.each(['production', 'evaluation'])(
|
||||
it.each<ConcurrencyQueueType>(['production', 'evaluation'])(
|
||||
'should be disabled if %s cap is lower than -1',
|
||||
(type: ConcurrencyQueueType) => {
|
||||
(type) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -186,9 +186,9 @@ describe('ConcurrencyControlService', () => {
|
|||
|
||||
describe('if enabled', () => {
|
||||
describe('throttle', () => {
|
||||
it.each(['cli', 'error', 'integrated', 'internal', 'manual', 'retry'])(
|
||||
it.each<ExecutionMode>(['cli', 'error', 'integrated', 'internal', 'manual', 'retry'])(
|
||||
'should do nothing on %s mode',
|
||||
async (mode: ExecutionMode) => {
|
||||
async (mode) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -215,9 +215,9 @@ describe('ConcurrencyControlService', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.each(['webhook', 'trigger', 'chat'])(
|
||||
it.each<ExecutionMode>(['webhook', 'trigger', 'chat'])(
|
||||
'should enqueue on %s mode',
|
||||
async (mode: ExecutionMode) => {
|
||||
async (mode) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -272,9 +272,9 @@ describe('ConcurrencyControlService', () => {
|
|||
});
|
||||
|
||||
describe('release', () => {
|
||||
it.each(['cli', 'error', 'integrated', 'internal', 'manual', 'retry'])(
|
||||
it.each<ExecutionMode>(['cli', 'error', 'integrated', 'internal', 'manual', 'retry'])(
|
||||
'should do nothing on %s mode',
|
||||
async (mode: ExecutionMode) => {
|
||||
async (mode) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -301,9 +301,9 @@ describe('ConcurrencyControlService', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.each(['webhook', 'trigger', 'chat'])(
|
||||
it.each<ExecutionMode>(['webhook', 'trigger', 'chat'])(
|
||||
'should dequeue on %s mode',
|
||||
(mode: ExecutionMode) => {
|
||||
(mode) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -358,9 +358,9 @@ describe('ConcurrencyControlService', () => {
|
|||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it.each(['cli', 'error', 'integrated', 'internal', 'manual', 'retry'])(
|
||||
it.each<ExecutionMode>(['cli', 'error', 'integrated', 'internal', 'manual', 'retry'])(
|
||||
'should do nothing on %s mode',
|
||||
async (mode: ExecutionMode) => {
|
||||
async (mode) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -387,9 +387,9 @@ describe('ConcurrencyControlService', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it.each(['webhook', 'trigger', 'chat'])(
|
||||
it.each<ExecutionMode>(['webhook', 'trigger', 'chat'])(
|
||||
'should remove an execution on %s mode',
|
||||
(mode: ExecutionMode) => {
|
||||
(mode) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
@ -444,9 +444,9 @@ describe('ConcurrencyControlService', () => {
|
|||
});
|
||||
|
||||
describe('removeAll', () => {
|
||||
it.each(['production', 'evaluation'])(
|
||||
it.each<ConcurrencyQueueType>(['production', 'evaluation'])(
|
||||
'should remove all executions from the %s queue',
|
||||
async (type: ConcurrencyQueueType) => {
|
||||
async (type) => {
|
||||
/**
|
||||
* Arrange
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { type BooleanLicenseFeature } from '@n8n/constants';
|
||||
import type { AuthenticatedRequest } from '@n8n/db';
|
||||
import { ControllerRegistryMetadata } from '@n8n/decorators';
|
||||
import type {
|
||||
AccessScope,
|
||||
|
|
@ -28,6 +27,7 @@ import { userHasScopes } from '@/permissions.ee/check-access';
|
|||
import { send } from '@/response-helper';
|
||||
import { CorsService } from './services/cors-service';
|
||||
import { inProduction } from '@n8n/backend-common';
|
||||
import { isAuthenticatedRequest } from '@n8n/db';
|
||||
|
||||
@Service()
|
||||
export class ControllerRegistry {
|
||||
|
|
@ -172,7 +172,7 @@ export class ControllerRegistry {
|
|||
allowSkipPreviewAuth: route.allowSkipPreviewAuth ?? false,
|
||||
allowUnauthenticated: route.allowUnauthenticated ?? false,
|
||||
}),
|
||||
this.lastActiveAtService.middleware.bind(this.lastActiveAtService) as RequestHandler,
|
||||
this.lastActiveAtService.middleware.bind(this.lastActiveAtService),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -219,11 +219,8 @@ export class ControllerRegistry {
|
|||
}
|
||||
|
||||
private createScopedMiddleware(accessScope: AccessScope): RequestHandler {
|
||||
return async (
|
||||
req: AuthenticatedRequest<{ credentialId?: string; workflowId?: string; projectId?: string }>,
|
||||
res,
|
||||
next,
|
||||
) => {
|
||||
return async (req, res, next) => {
|
||||
if (!isAuthenticatedRequest(req)) throw new UnauthenticatedError();
|
||||
if (!req.user) throw new UnauthenticatedError();
|
||||
|
||||
const { scope, globalOnly } = accessScope;
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ describe('AiController', () => {
|
|||
let abortSignalPassed: AbortSignal | undefined;
|
||||
|
||||
// Mock response.on to capture the close handler
|
||||
response.on.mockImplementation((event: string, handler: () => void) => {
|
||||
response.on.mockImplementation((event: string | symbol, handler: () => void) => {
|
||||
if (event === 'close') {
|
||||
abortHandler = handler;
|
||||
}
|
||||
|
|
@ -406,7 +406,7 @@ describe('AiController', () => {
|
|||
let abortHandler: (() => void) | undefined;
|
||||
let abortSignalPassed: AbortSignal | undefined;
|
||||
|
||||
response.on.mockImplementation((event: string, handler: () => void) => {
|
||||
response.on.mockImplementation((event: string | symbol, handler: () => void) => {
|
||||
if (event === 'close') {
|
||||
abortHandler = handler;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -460,10 +460,11 @@ export class CredentialsService {
|
|||
credentials: CredentialsEntity[],
|
||||
): Array<ICredentialsDecrypted<ICredentialDataDecryptedObject>> {
|
||||
return credentials.map(
|
||||
(
|
||||
c: CredentialsEntity & ScopesField,
|
||||
): ICredentialsDecrypted<ICredentialDataDecryptedObject> => {
|
||||
const data = c.scopes.includes('credential:update') ? this.decrypt(c) : undefined;
|
||||
(c: CredentialsEntity): ICredentialsDecrypted<ICredentialDataDecryptedObject> => {
|
||||
const credWithScopes = c as CredentialsEntity & ScopesField;
|
||||
const data = credWithScopes.scopes.includes('credential:update')
|
||||
? this.decrypt(c)
|
||||
: undefined;
|
||||
|
||||
// We never want to expose the oauthTokenData to the frontend, but it
|
||||
// expects it to check if the credential is already connected.
|
||||
|
|
|
|||
|
|
@ -73,9 +73,9 @@ describe('DeprecationService', () => {
|
|||
});
|
||||
|
||||
describe('when executions.mode is not queue', () => {
|
||||
test.each([['main'], ['worker'], ['webhook']])(
|
||||
test.each<[InstanceType]>([['main'], ['worker'], ['webhook']])(
|
||||
'should not warn for instanceType %s',
|
||||
(instanceType: InstanceType) => {
|
||||
(instanceType) => {
|
||||
process.env[envVar] = 'false';
|
||||
const service = new DeprecationService(
|
||||
logger,
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export class DeprecationService {
|
|||
envVar: 'EXECUTIONS_PROCESS',
|
||||
message:
|
||||
'n8n does not support `own` mode since May 2023. Please remove this environment variable to allow n8n to start. If you need the isolation and performance gains, please consider queue mode: https://docs.n8n.io/hosting/scaling/queue-mode/',
|
||||
checkValue: (value: string) => value === 'own',
|
||||
checkValue: (value: string | undefined): value is 'own' => value === 'own',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export type AiEventMap = {
|
|||
|
||||
'ai-documents-retrieved': AiEventPayload;
|
||||
|
||||
'ai-document-reranked': AiEventPayload;
|
||||
|
||||
'ai-document-embedded': AiEventPayload;
|
||||
|
||||
'ai-query-embedded': AiEventPayload;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class ModulesHooksRegistry {
|
|||
case 'workflowExecuteAfter':
|
||||
hooks.addHandler(eventName, async function (runData, newStaticData) {
|
||||
const context = {
|
||||
type: 'workflowExecuteAfter',
|
||||
type: 'workflowExecuteAfter' as const,
|
||||
workflow: this.workflowData,
|
||||
runData,
|
||||
newStaticData,
|
||||
|
|
@ -73,7 +73,7 @@ class ModulesHooksRegistry {
|
|||
case 'nodeExecuteBefore':
|
||||
hooks.addHandler(eventName, async function (nodeName, taskData) {
|
||||
const context = {
|
||||
type: 'nodeExecuteBefore',
|
||||
type: 'nodeExecuteBefore' as const,
|
||||
workflow: this.workflowData,
|
||||
nodeName,
|
||||
taskData,
|
||||
|
|
@ -87,7 +87,7 @@ class ModulesHooksRegistry {
|
|||
case 'nodeExecuteAfter':
|
||||
hooks.addHandler(eventName, async function (nodeName, taskData, executionData) {
|
||||
const context = {
|
||||
type: 'nodeExecuteAfter',
|
||||
type: 'nodeExecuteAfter' as const,
|
||||
workflow: this.workflowData,
|
||||
nodeName,
|
||||
taskData,
|
||||
|
|
@ -102,7 +102,7 @@ class ModulesHooksRegistry {
|
|||
case 'workflowExecuteBefore':
|
||||
hooks.addHandler(eventName, async function (workflowInstance, executionData) {
|
||||
const context = {
|
||||
type: 'workflowExecuteBefore',
|
||||
type: 'workflowExecuteBefore' as const,
|
||||
workflow: this.workflowData,
|
||||
workflowInstance,
|
||||
executionData,
|
||||
|
|
@ -116,7 +116,7 @@ class ModulesHooksRegistry {
|
|||
case 'workflowExecuteResume':
|
||||
hooks.addHandler(eventName, async function (workflowInstance, executionData) {
|
||||
const context = {
|
||||
type: 'workflowExecuteResume',
|
||||
type: 'workflowExecuteResume' as const,
|
||||
workflow: this.workflowData,
|
||||
workflowInstance,
|
||||
executionData,
|
||||
|
|
|
|||
|
|
@ -27,13 +27,15 @@ export const parseRangeQuery = (
|
|||
try {
|
||||
req.rangeQuery = {
|
||||
kind: 'range',
|
||||
range: { limit: limit ? Math.min(parseInt(limit, 10), 100) : 20 },
|
||||
range: {
|
||||
limit: limit && typeof limit === 'string' ? Math.min(parseInt(limit, 10), 100) : 20,
|
||||
},
|
||||
};
|
||||
|
||||
if (firstId) req.rangeQuery.range.firstId = firstId;
|
||||
if (lastId) req.rangeQuery.range.lastId = lastId;
|
||||
if (firstId && typeof firstId === 'string') req.rangeQuery.range.firstId = firstId;
|
||||
if (lastId && typeof lastId === 'string') req.rangeQuery.range.lastId = lastId;
|
||||
|
||||
if (req.query.filter) {
|
||||
if (typeof req.query.filter === 'string') {
|
||||
const jsonFilter = jsonParse<JsonObject>(req.query.filter, {
|
||||
errorMessage: 'Failed to parse query string',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ export class ExternalHooks {
|
|||
|
||||
for (const hookFunction of hookFunctions) {
|
||||
try {
|
||||
await hookFunction.apply(context, hookParameters);
|
||||
await hookFunction.apply(context, hookParameters!);
|
||||
} catch (cause) {
|
||||
this.logger.error(`There was a problem running hook "${hookName}"`);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { NextFunction, Response } from 'express';
|
||||
import type { RequestHandler } from 'express';
|
||||
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { appendListQueryOptions } from '@/requests';
|
||||
import * as ResponseHelper from '@/response-helper';
|
||||
import { toError } from '@/utils';
|
||||
|
||||
|
|
@ -8,14 +8,10 @@ import { CredentialsFilter } from './dtos/credentials.filter.dto';
|
|||
import { UserFilter } from './dtos/user.filter.dto';
|
||||
import { WorkflowFilter } from './dtos/workflow.filter.dto';
|
||||
|
||||
export const filterListQueryMiddleware = async (
|
||||
req: ListQuery.Request,
|
||||
res: Response,
|
||||
next: NextFunction,
|
||||
) => {
|
||||
export const filterListQueryMiddleware: RequestHandler = async (req, res, next) => {
|
||||
const { filter: rawFilter } = req.query;
|
||||
|
||||
if (!rawFilter) return next();
|
||||
if (!rawFilter || typeof rawFilter !== 'string') return next();
|
||||
|
||||
let Filter;
|
||||
|
||||
|
|
@ -34,7 +30,7 @@ export const filterListQueryMiddleware = async (
|
|||
|
||||
if (Object.keys(filter).length === 0) return next();
|
||||
|
||||
req.listQueryOptions = { ...req.listQueryOptions, filter };
|
||||
appendListQueryOptions(req, { filter });
|
||||
|
||||
next();
|
||||
} catch (maybeError) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { type NextFunction, type Response } from 'express';
|
||||
import type { RequestHandler, NextFunction, Response } from 'express';
|
||||
|
||||
import type { ListQuery } from '@/requests';
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export type ListQueryMiddleware = (
|
|||
/**
|
||||
* @deprecated Please create Zod validators in `@n8n/api-types` instead.
|
||||
*/
|
||||
export const listQueryMiddleware: ListQueryMiddleware[] = [
|
||||
export const listQueryMiddleware: RequestHandler[] = [
|
||||
filterListQueryMiddleware,
|
||||
selectListQueryMiddleware,
|
||||
paginationListQueryMiddleware,
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
import type { RequestHandler } from 'express';
|
||||
import { UnexpectedError } from 'n8n-workflow';
|
||||
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { appendListQueryOptions } from '@/requests';
|
||||
import * as ResponseHelper from '@/response-helper';
|
||||
import { toError } from '@/utils';
|
||||
|
||||
import { Pagination } from './dtos/pagination.dto';
|
||||
|
||||
export const paginationListQueryMiddleware: RequestHandler = (
|
||||
req: ListQuery.Request,
|
||||
res,
|
||||
next,
|
||||
) => {
|
||||
const { take: rawTake, skip: rawSkip = '0' } = req.query;
|
||||
export const paginationListQueryMiddleware: RequestHandler = (req, res, next) => {
|
||||
const { take: rawTake } = req.query;
|
||||
let { skip: rawSkip = '0' } = req.query;
|
||||
|
||||
try {
|
||||
if (!rawTake && req.query.skip) {
|
||||
throw new UnexpectedError('Please specify `take` when using `skip`');
|
||||
}
|
||||
|
||||
if (!rawTake) return next();
|
||||
if (!rawTake || typeof rawTake !== 'string') return next();
|
||||
|
||||
if (typeof rawSkip !== 'string') {
|
||||
rawSkip = '0';
|
||||
}
|
||||
|
||||
const { take, skip } = Pagination.fromString(rawTake, rawSkip);
|
||||
|
||||
req.listQueryOptions = { ...req.listQueryOptions, skip, take };
|
||||
appendListQueryOptions(req, { skip, take });
|
||||
|
||||
next();
|
||||
} catch (maybeError) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { RequestHandler } from 'express';
|
||||
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { appendListQueryOptions } from '@/requests';
|
||||
import * as ResponseHelper from '@/response-helper';
|
||||
import { toError } from '@/utils';
|
||||
|
||||
|
|
@ -8,10 +8,10 @@ import { CredentialsSelect } from './dtos/credentials.select.dto';
|
|||
import { UserSelect } from './dtos/user.select.dto';
|
||||
import { WorkflowSelect } from './dtos/workflow.select.dto';
|
||||
|
||||
export const selectListQueryMiddleware: RequestHandler = (req: ListQuery.Request, res, next) => {
|
||||
export const selectListQueryMiddleware: RequestHandler = (req, res, next) => {
|
||||
const { select: rawSelect } = req.query;
|
||||
|
||||
if (!rawSelect) return next();
|
||||
if (!rawSelect || typeof rawSelect !== 'string') return next();
|
||||
|
||||
let Select;
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ export const selectListQueryMiddleware: RequestHandler = (req: ListQuery.Request
|
|||
|
||||
if (Object.keys(select).length === 0) return next();
|
||||
|
||||
req.listQueryOptions = { ...req.listQueryOptions, select };
|
||||
appendListQueryOptions(req, { select });
|
||||
|
||||
next();
|
||||
} catch (maybeError) {
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@ import { validateSync } from 'class-validator';
|
|||
import type { RequestHandler } from 'express';
|
||||
import { UnexpectedError } from 'n8n-workflow';
|
||||
|
||||
import type { ListQuery } from '@/requests';
|
||||
import { appendListQueryOptions } from '@/requests';
|
||||
import * as ResponseHelper from '@/response-helper';
|
||||
import { toError } from '@/utils';
|
||||
|
||||
import { WorkflowSorting } from './dtos/workflow.sort-by.dto';
|
||||
|
||||
export const sortByQueryMiddleware: RequestHandler = (req: ListQuery.Request, res, next) => {
|
||||
export const sortByQueryMiddleware: RequestHandler = (req, res, next) => {
|
||||
const { sortBy } = req.query;
|
||||
|
||||
if (!sortBy) return next();
|
||||
if (!sortBy || typeof sortBy !== 'string') return next();
|
||||
|
||||
let SortBy;
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ export const sortByQueryMiddleware: RequestHandler = (req: ListQuery.Request, re
|
|||
throw new UnexpectedError(validationError.constraints?.workflowSortBy ?? '');
|
||||
}
|
||||
|
||||
req.listQueryOptions = { ...req.listQueryOptions, sortBy };
|
||||
appendListQueryOptions(req, { sortBy });
|
||||
|
||||
next();
|
||||
} catch (maybeError) {
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ export class ChatHubWorkflowService {
|
|||
const nodeNames = new Set(nodes.map((node) => node.name));
|
||||
const distinctTools = tools.map((tool, i) => {
|
||||
// Spread out the tool nodes so that they don't overlap on the canvas
|
||||
const position = [
|
||||
const position: [number, number] = [
|
||||
700 + Math.floor(i / 3) * 60 + (i % 3) * 120,
|
||||
300 + Math.floor(i / 3) * 120 - (i % 3) * 30,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
|
||||
import type { ChatHubMessage } from './chat-hub-message.entity';
|
||||
|
||||
type Write = ServerResponse['write'];
|
||||
type End = ServerResponse['end'];
|
||||
|
||||
export type ChunkTransformer = (chunk: string) => Promise<string>;
|
||||
|
|
@ -15,7 +14,7 @@ export function interceptResponseWrites<T extends Response>(
|
|||
res: T,
|
||||
transform: ChunkTransformer,
|
||||
): T {
|
||||
const originalWrite = res.write.bind(res) as Write;
|
||||
const originalWrite = res.write.bind(res);
|
||||
const originalEnd = res.end.bind(res) as End;
|
||||
const defaultEncoding = 'utf8';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { Logger } from '@n8n/backend-common';
|
||||
import { AuthenticatedRequest } from '@n8n/db';
|
||||
import { CredentialResolverError } from '@n8n/decorators';
|
||||
import { Service } from '@n8n/di';
|
||||
import { NextFunction, Response } from 'express';
|
||||
import type { NextFunction, Response } from 'express';
|
||||
import { Cipher } from 'n8n-core';
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
|
|
@ -28,6 +27,7 @@ import { CredentialResolutionError } from '../errors/credential-resolution.error
|
|||
import { CredentialResolverNotConfiguredError } from '../errors/credential-resolver-not-configured.error';
|
||||
import { CredentialResolverNotFoundError } from '../errors/credential-resolver-not-found.error';
|
||||
import { MissingExecutionContextError } from '../errors/missing-execution-context.error';
|
||||
import { AuthenticatedRequest } from '@n8n/db';
|
||||
|
||||
/**
|
||||
* Service for resolving credentials dynamically via configured resolvers.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import type { RequestHandler } from 'express';
|
||||
|
||||
import { Container } from '@n8n/di';
|
||||
import { DynamicCredentialService } from './services/dynamic-credential.service';
|
||||
|
||||
export const getDynamicCredentialMiddlewares = () => {
|
||||
export const getDynamicCredentialMiddlewares = (): RequestHandler[] => {
|
||||
return [Container.get(DynamicCredentialService).getDynamicCredentialsEndpointsMiddleware()];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ describe('ProviderLifecycle', () => {
|
|||
const originalConnect = provider.connect.bind(provider);
|
||||
jest.spyOn(provider, 'connect').mockImplementation(async function (this: DummyProvider) {
|
||||
stateBeforeConnect = this.state;
|
||||
return originalConnect();
|
||||
return await originalConnect();
|
||||
});
|
||||
|
||||
await lifecycle.connect(provider);
|
||||
|
|
|
|||
|
|
@ -36,13 +36,14 @@ export class InsightsByPeriod extends BaseEntity {
|
|||
private type_: number;
|
||||
|
||||
get type() {
|
||||
if (!isValidTypeNumber(this.type_)) {
|
||||
const typeValue = this.type_;
|
||||
if (!isValidTypeNumber(typeValue)) {
|
||||
throw new UnexpectedError(
|
||||
`Type '${this.type_}' is not a valid type for 'InsightsByPeriod.type'`,
|
||||
`Type '${typeValue}' is not a valid type for 'InsightsByPeriod.type'`,
|
||||
);
|
||||
}
|
||||
|
||||
return NumberToType[this.type_];
|
||||
return NumberToType[typeValue];
|
||||
}
|
||||
|
||||
set type(value: keyof typeof TypeToNumber) {
|
||||
|
|
@ -61,13 +62,14 @@ export class InsightsByPeriod extends BaseEntity {
|
|||
private periodUnit_: number;
|
||||
|
||||
get periodUnit() {
|
||||
if (!isValidPeriodNumber(this.periodUnit_)) {
|
||||
const periodUnitValue = this.periodUnit_;
|
||||
if (!isValidPeriodNumber(periodUnitValue)) {
|
||||
throw new UnexpectedError(
|
||||
`Period unit '${this.periodUnit_}' is not a valid unit for 'InsightsByPeriod.periodUnit'`,
|
||||
`Period unit '${periodUnitValue}' is not a valid unit for 'InsightsByPeriod.periodUnit'`,
|
||||
);
|
||||
}
|
||||
|
||||
return NumberToPeriodUnit[this.periodUnit_];
|
||||
return NumberToPeriodUnit[periodUnitValue];
|
||||
}
|
||||
|
||||
set periodUnit(value: PeriodUnit) {
|
||||
|
|
|
|||
|
|
@ -25,13 +25,14 @@ export class InsightsRaw extends BaseEntity {
|
|||
private type_: number;
|
||||
|
||||
get type() {
|
||||
if (!isValidTypeNumber(this.type_)) {
|
||||
const typeValue = this.type_;
|
||||
if (!isValidTypeNumber(typeValue)) {
|
||||
throw new UnexpectedError(
|
||||
`Type '${this.type_}' is not a valid type for 'InsightsByPeriod.type'`,
|
||||
`Type '${typeValue}' is not a valid type for 'InsightsByPeriod.type'`,
|
||||
);
|
||||
}
|
||||
|
||||
return NumberToType[this.type_];
|
||||
return NumberToType[typeValue];
|
||||
}
|
||||
|
||||
set type(value: keyof typeof TypeToNumber) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { invert } from '@/utils/inverter';
|
||||
|
||||
function isValid<T extends Record<number | string | symbol, unknown>>(
|
||||
value: number | string | symbol,
|
||||
constant: T,
|
||||
|
|
@ -5,6 +7,9 @@ function isValid<T extends Record<number | string | symbol, unknown>>(
|
|||
return Object.keys(constant).includes(value.toString());
|
||||
}
|
||||
|
||||
export type PeriodUnit = keyof typeof PeriodUnitToNumber;
|
||||
|
||||
export type PeriodUnitNumber = (typeof PeriodUnitToNumber)[PeriodUnit];
|
||||
// Periods
|
||||
export const PeriodUnitToNumber = {
|
||||
hour: 0,
|
||||
|
|
@ -12,16 +17,8 @@ export const PeriodUnitToNumber = {
|
|||
week: 2,
|
||||
} as const;
|
||||
|
||||
export type PeriodUnit = keyof typeof PeriodUnitToNumber;
|
||||
export const NumberToPeriodUnit = invert(PeriodUnitToNumber);
|
||||
|
||||
export type PeriodUnitNumber = (typeof PeriodUnitToNumber)[PeriodUnit];
|
||||
export const NumberToPeriodUnit = Object.entries(PeriodUnitToNumber).reduce(
|
||||
(acc, [key, value]: [PeriodUnit, PeriodUnitNumber]) => {
|
||||
acc[value] = key;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<PeriodUnitNumber, PeriodUnit>,
|
||||
);
|
||||
export function isValidPeriodNumber(value: number) {
|
||||
return isValid(value, NumberToPeriodUnit);
|
||||
}
|
||||
|
|
@ -37,13 +34,7 @@ export const TypeToNumber = {
|
|||
export type TypeUnit = keyof typeof TypeToNumber;
|
||||
|
||||
export type TypeUnitNumber = (typeof TypeToNumber)[TypeUnit];
|
||||
export const NumberToType = Object.entries(TypeToNumber).reduce(
|
||||
(acc, [key, value]: [TypeUnit, TypeUnitNumber]) => {
|
||||
acc[value] = key;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<TypeUnitNumber, TypeUnit>,
|
||||
);
|
||||
export const NumberToType = invert(TypeToNumber);
|
||||
|
||||
export function isValidTypeNumber(value: number) {
|
||||
return isValid(value, NumberToType);
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ describe('getDateRangesCommonTableExpressionQuery', () => {
|
|||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
describe.each([
|
||||
describe.each<[DatabaseConfig['type'], string]>([
|
||||
['sqlite', 'SQLite'],
|
||||
['postgresdb', 'PostgreSQL'],
|
||||
])('%s', (dbType: DatabaseConfig['type']) => {
|
||||
])('%s', (dbType) => {
|
||||
describe('hour periodicity (1 day - startDate == endDate)', () => {
|
||||
test('last 24 hours (endDate is today)', () => {
|
||||
const startDate = now.minus({ days: 1 }).startOf('day').toJSDate();
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class LRUCache<T> {
|
|||
|
||||
// Evict oldest if at capacity
|
||||
if (this.map.size >= this.maxEntries) {
|
||||
const oldest = this.map.keys().next().value as string | undefined;
|
||||
const oldest = this.map.keys().next().value;
|
||||
if (oldest !== undefined) {
|
||||
this.map.delete(oldest);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,7 +230,12 @@ export class MessageEventBusDestinationWebhook
|
|||
|
||||
const parametersToKeyValue = async (
|
||||
acc: Promise<{ [key: string]: any }>,
|
||||
cur: { name: string; value: string; parameterType?: string; inputDataFieldName?: string },
|
||||
cur: {
|
||||
name: string;
|
||||
value: string | number | boolean | null;
|
||||
parameterType?: string;
|
||||
inputDataFieldName?: string;
|
||||
},
|
||||
) => {
|
||||
const accumulator = await acc;
|
||||
accumulator[cur.name] = cur.value;
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ const quickConnectOptionSchema = z.union([
|
|||
export type QuickConnectOption = z.infer<typeof quickConnectOptionSchema>;
|
||||
|
||||
const quickConnectOptionsSchema = z.string().pipe(
|
||||
z.preprocess((input: string) => {
|
||||
z.preprocess((input: unknown) => {
|
||||
try {
|
||||
return JSON.parse(input);
|
||||
return JSON.parse(input as string);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,10 +69,11 @@ describe('OauthService', () => {
|
|||
axios.post = jest.fn();
|
||||
|
||||
// Setup cipher mock - encrypt returns the input as-is for testing, decrypt does the reverse
|
||||
cipher.encrypt.mockImplementation((data: string) => {
|
||||
cipher.encrypt.mockImplementation((data: string | object) => {
|
||||
// For testing, we'll use base64 encoding as a simple mock
|
||||
// In production, this would be actual encryption
|
||||
return Buffer.from(data).toString('base64');
|
||||
const str = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
return Buffer.from(str).toString('base64');
|
||||
});
|
||||
cipher.decrypt.mockImplementation((data: string) => {
|
||||
// For testing, decode the base64
|
||||
|
|
|
|||
|
|
@ -27,6 +27,15 @@ import type { IDependency, IJsonSchema } from '../../../types';
|
|||
|
||||
export class CredentialsIsNotUpdatableError extends BaseError {}
|
||||
|
||||
function isNodePropertyOptions(options: unknown): options is INodePropertyOptions[] {
|
||||
return (
|
||||
Array.isArray(options) &&
|
||||
options.every(
|
||||
(item) => typeof item === 'object' && item !== null && 'value' in item && 'name' in item,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared entry for credential list: project id/name plus sharing role and timestamps.
|
||||
* Derived from credential.shared (SharedCredentials + Project), limited to these fields.
|
||||
|
|
@ -294,7 +303,9 @@ export function toJsonSchema(properties: INodeProperties[]): IDataObject {
|
|||
.filter((property) => property.type === 'options')
|
||||
.forEach((property) => {
|
||||
Object.assign(optionsValues, {
|
||||
[property.name]: property.options?.map((option: INodePropertyOptions) => option.value),
|
||||
[property.name]: isNodePropertyOptions(property.options)
|
||||
? property.options.map((option) => option.value)
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -317,7 +328,9 @@ export function toJsonSchema(properties: INodeProperties[]): IDataObject {
|
|||
Object.assign(jsonSchema.properties, {
|
||||
[property.name]: {
|
||||
type: 'string',
|
||||
enum: property.options?.map((data: INodePropertyOptions) => data.value),
|
||||
enum: isNodePropertyOptions(property.options)
|
||||
? property.options.map((data) => data.value)
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type { AuthenticatedRequest } from '@n8n/db';
|
|||
import { Container } from '@n8n/di';
|
||||
import type { ApiKeyScope, Scope } from '@n8n/permissions';
|
||||
import type express from 'express';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
|
||||
import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
|
|
@ -85,16 +86,15 @@ export const validCursor = (
|
|||
return next();
|
||||
};
|
||||
|
||||
export type ScopeTaggedMiddleware = ((...args: unknown[]) => unknown) & {
|
||||
export type ScopeTaggedMiddleware = Middleware & {
|
||||
__apiKeyScope: ApiKeyScope;
|
||||
};
|
||||
|
||||
function tagMiddleware(
|
||||
middleware: (...args: unknown[]) => unknown,
|
||||
apiKeyScope: ApiKeyScope,
|
||||
): ScopeTaggedMiddleware {
|
||||
export type Middleware = (req: Request, res: Response, next: NextFunction) => unknown;
|
||||
|
||||
function tagMiddleware(middleware: Middleware, apiKeyScope: ApiKeyScope): ScopeTaggedMiddleware {
|
||||
const tagged: ScopeTaggedMiddleware = Object.assign(
|
||||
(req: unknown, res: unknown, next: unknown) => middleware(req, res, next),
|
||||
(req: Request, res: Response, next: NextFunction) => middleware(req, res, next),
|
||||
{ __apiKeyScope: apiKeyScope },
|
||||
);
|
||||
return tagged;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,15 @@ import { TypedEmitter } from '@/typed-emitter';
|
|||
import { validateOriginHeaders } from './origin-validator';
|
||||
import { PushConfig } from './push.config';
|
||||
import { SSEPush } from './sse.push';
|
||||
import type { OnPushMessage, PushResponse, SSEPushRequest, WebSocketPushRequest } from './types';
|
||||
import {
|
||||
type OnPushMessage,
|
||||
type PushResponse,
|
||||
type SSEPushRequest,
|
||||
type WebSocketPushRequest,
|
||||
} from './types';
|
||||
import { WebSocketPush } from './websocket.push';
|
||||
import { isPushResponse, isSSEPushRequest, isWebSocketPushRequest } from './push-helpers';
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
|
||||
type PushEvents = {
|
||||
editorUiConnected: string;
|
||||
|
|
@ -95,8 +102,15 @@ export class Push extends TypedEmitter<PushEvents> {
|
|||
`/${restEndpoint}/push`,
|
||||
|
||||
this.authService.createAuthMiddleware({ allowSkipMFA: false }),
|
||||
(req: SSEPushRequest | WebSocketPushRequest, res: PushResponse) =>
|
||||
this.handleRequest(req, res),
|
||||
(req, res) => {
|
||||
if (!isWebSocketPushRequest(req) && !isSSEPushRequest(req)) {
|
||||
throw new BadRequestError('Request is not a PushRequest');
|
||||
}
|
||||
if (!isPushResponse(res)) {
|
||||
throw new InternalServerError('Malformed response object');
|
||||
}
|
||||
return this.handleRequest(req, res);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
20
packages/cli/src/push/push-helpers.ts
Normal file
20
packages/cli/src/push/push-helpers.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { Request, Response } from 'express';
|
||||
|
||||
import type { PushRequest, PushResponse, SSEPushRequest, WebSocketPushRequest } from './types';
|
||||
|
||||
export function isPushRequest(req: Request): req is PushRequest {
|
||||
return 'pushRef' in req.query && typeof req.query.pushRef === 'string';
|
||||
}
|
||||
|
||||
export function isSSEPushRequest(req: Request): req is SSEPushRequest {
|
||||
const hasWs = 'ws' in req;
|
||||
return isPushRequest(req) && (!hasWs || req.ws === undefined);
|
||||
}
|
||||
|
||||
export function isWebSocketPushRequest(req: Request): req is WebSocketPushRequest {
|
||||
return isPushRequest(req) && 'ws' in req && req.ws !== undefined;
|
||||
}
|
||||
|
||||
export function isPushResponse(res: Response): res is PushResponse {
|
||||
return 'req' in res && isPushRequest(res.req);
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import type {
|
|||
ProjectRole,
|
||||
Scope,
|
||||
} from '@n8n/permissions';
|
||||
import type { Request } from 'express';
|
||||
import type {
|
||||
ICredentialDataDecryptedObject,
|
||||
INodeCredentialTestRequest,
|
||||
|
|
@ -50,6 +51,11 @@ export namespace ListQuery {
|
|||
};
|
||||
}
|
||||
|
||||
export function appendListQueryOptions(req: Request, options: ListQuery.Options) {
|
||||
const listReq = req as ListQuery.Request;
|
||||
listReq.listQueryOptions = { ...listReq.listQueryOptions, ...options };
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// list query
|
||||
// ----------------------------------
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ describe('WorkerServer', () => {
|
|||
|
||||
jest.spyOn(http, 'createServer').mockReturnValue(server);
|
||||
|
||||
server.on.mockImplementation((event: string, callback: (arg?: unknown) => void) => {
|
||||
server.on.mockImplementation((event: string, callback: (...args: unknown[]) => void) => {
|
||||
if (event === 'error') callback(addressInUseError());
|
||||
return server;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
import type { Tool } from '@langchain/core/tools';
|
||||
import type {
|
||||
ExecutionStatus,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecuteFunctions,
|
||||
IExecuteResponsePromiseData,
|
||||
|
|
@ -20,6 +21,7 @@ import type {
|
|||
IWorkflowExecutionDataProcess,
|
||||
StructuredChunk,
|
||||
CloseFunction,
|
||||
GenericValue,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
BINARY_ENCODING,
|
||||
|
|
@ -167,7 +169,9 @@ export class JobProcessor {
|
|||
|
||||
if (pushRef) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
additionalData.sendDataToUI = WorkflowExecuteAdditionalData.sendDataToUI.bind({ pushRef });
|
||||
additionalData.sendDataToUI = WorkflowExecuteAdditionalData.sendDataToUI.bind({
|
||||
pushRef,
|
||||
}) as (type: string, data: IDataObject | IDataObject[]) => void;
|
||||
}
|
||||
|
||||
lifecycleHooks.addHandler('sendResponse', async (response): Promise<void> => {
|
||||
|
|
@ -542,7 +546,10 @@ export class JobProcessor {
|
|||
|
||||
const result = await nodeType.execute.call(context as unknown as IExecuteFunctions);
|
||||
|
||||
const response = result?.[0]?.flatMap((item: INodeExecutionData) => item.json);
|
||||
let response: IDataObject | IDataObject[] | GenericValue | GenericValue[] = [];
|
||||
if (Array.isArray(result)) {
|
||||
response = result?.[0]?.flatMap((item: INodeExecutionData) => item.json);
|
||||
}
|
||||
|
||||
context.addOutputData(NodeConnectionTypes.AiTool, 0, [
|
||||
[{ json: { response } as INodeExecutionData['json'] }],
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ export class MultiMainSetup extends TypedEmitter<MultiMainEvents> {
|
|||
const instance = Container.get(eventHandlerClass);
|
||||
this.on(eventName, async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return instance[methodName].call(instance);
|
||||
return await instance[methodName].call(instance);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
PERSONAL_SPACE_SHARING_SETTING,
|
||||
EXTERNAL_SECRETS_SYSTEM_ROLES_ENABLED_SETTING,
|
||||
} from '@n8n/permissions';
|
||||
import type { EntityManager, Repository } from '@n8n/typeorm';
|
||||
import type { EntityManager, FindManyOptions, Repository } from '@n8n/typeorm';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
const SHARING_SCOPES = PERSONAL_SPACE_SHARING_SETTING.scopes;
|
||||
|
|
@ -446,8 +446,8 @@ describe('AuthRolesService', () => {
|
|||
scopeRepository.find.mockResolvedValue(allScopes);
|
||||
// syncScopes calls roleRepository.find({ relations: ['scopes'], ... }); syncRoles calls it with select/where.
|
||||
// Return [] for the obsolete-scopes lookup so syncScopes does not save; return correctRoles for syncRoles.
|
||||
roleRepository.find.mockImplementation(async (opts?: { relations?: string[] }) =>
|
||||
opts?.relations?.includes('scopes') ? [] : correctRoles,
|
||||
roleRepository.find.mockImplementation(async (opts?: FindManyOptions<Role>) =>
|
||||
(opts?.relations as string[] | undefined)?.includes('scopes') ? [] : correctRoles,
|
||||
);
|
||||
roleRepository.save.mockImplementation(async (entities) => entities as never);
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ type ListSearchMethod = (
|
|||
type LoadOptionsMethod = (this: ILoadOptionsFunctions) => Promise<INodePropertyOptions[]>;
|
||||
type ActionHandlerMethod = (
|
||||
this: ILoadOptionsFunctions,
|
||||
payload?: string,
|
||||
payload?: string | IDataObject,
|
||||
) => Promise<NodeParameterValueType>;
|
||||
type ResourceMappingMethod = (this: ILoadOptionsFunctions) => Promise<ResourceMapperFields>;
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ export class DynamicNodeParametersService {
|
|||
// Need to use untyped call since `this` usage is widespread and we don't have `strictBindCallApply`
|
||||
// enabled in `tsconfig.json`
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return method.call(thisArgs);
|
||||
return await method.call(thisArgs);
|
||||
}
|
||||
|
||||
/** Returns the available options via a loadOptions param */
|
||||
|
|
@ -238,7 +238,7 @@ export class DynamicNodeParametersService {
|
|||
const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials);
|
||||
const thisArgs = this.getThisArg(path, additionalData, workflow);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return method.call(thisArgs, filter, paginationToken);
|
||||
return await method.call(thisArgs, filter, paginationToken);
|
||||
}
|
||||
|
||||
/** Returns the available mapping fields for the ResourceMapper component */
|
||||
|
|
@ -254,9 +254,7 @@ export class DynamicNodeParametersService {
|
|||
const method = this.getMethod('resourceMapping', methodName, nodeType);
|
||||
const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials);
|
||||
const thisArgs = this.getThisArg(path, additionalData, workflow);
|
||||
return this.removeDuplicateResourceMappingFields(
|
||||
(await method.call(thisArgs)) as ResourceMapperFields,
|
||||
);
|
||||
return this.removeDuplicateResourceMappingFields(await method.call(thisArgs));
|
||||
}
|
||||
|
||||
/** Returns the available workflow input mapping fields for the ResourceMapper component */
|
||||
|
|
@ -269,9 +267,7 @@ export class DynamicNodeParametersService {
|
|||
const nodeType = this.getNodeType(nodeTypeAndVersion);
|
||||
const method = this.getMethod('localResourceMapping', methodName, nodeType);
|
||||
const thisArgs = this.getLocalLoadOptionsContext(path, additionalData);
|
||||
return this.removeDuplicateResourceMappingFields(
|
||||
(await method.call(thisArgs)) as ResourceMapperFields,
|
||||
);
|
||||
return this.removeDuplicateResourceMappingFields(await method.call(thisArgs));
|
||||
}
|
||||
|
||||
/** Returns the result of the action handler */
|
||||
|
|
@ -289,7 +285,7 @@ export class DynamicNodeParametersService {
|
|||
const workflow = this.getWorkflow(nodeTypeAndVersion, currentNodeParameters, credentials);
|
||||
const thisArgs = this.getThisArg(path, additionalData, workflow);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return method.call(thisArgs, payload);
|
||||
return await method.call(thisArgs, payload);
|
||||
}
|
||||
|
||||
private getMethod(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Logger } from '@n8n/backend-common';
|
||||
import type { AuthenticatedRequest } from '@n8n/db';
|
||||
import { UserRepository } from '@n8n/db';
|
||||
import { AuthenticatedRequest, UserRepository } from '@n8n/db';
|
||||
import { Service } from '@n8n/di';
|
||||
import type { NextFunction, Response } from 'express';
|
||||
import { DateTime } from 'luxon';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Time } from '@n8n/constants';
|
||||
import type { AuthenticatedRequest } from '@n8n/db';
|
||||
import type { RateLimiterLimits, UserKeyedRateLimiterConfig } from '@n8n/decorators';
|
||||
import { BodyKeyedRateLimiterConfig } from '@n8n/decorators';
|
||||
import { Service } from '@n8n/di';
|
||||
|
|
@ -8,6 +7,7 @@ import { rateLimit as expressRateLimit } from 'express-rate-limit';
|
|||
import assert from 'node:assert';
|
||||
import type { ZodTypeAny } from 'zod';
|
||||
import type { ZodClass } from '@n8n/api-types';
|
||||
import { AuthenticatedRequest } from '@n8n/db';
|
||||
|
||||
const defaultLimits: Required<RateLimiterLimits> = {
|
||||
limit: 5,
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ export abstract class TaskRequester {
|
|||
}
|
||||
}
|
||||
|
||||
const data = (await func.call(funcs, ...params)) as unknown;
|
||||
const data = await func.call(funcs, ...params);
|
||||
|
||||
this.sendMessage({
|
||||
type: 'requester:rpcresponse',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Logger } from '@n8n/backend-common';
|
|||
import { TaskRunnersConfig } from '@n8n/config';
|
||||
import { OnShutdown } from '@n8n/decorators';
|
||||
import { Container, Service } from '@n8n/di';
|
||||
import type { ServiceIdentifier } from '@n8n/di';
|
||||
import { ErrorReporter } from 'n8n-core';
|
||||
import { sleep } from 'n8n-workflow';
|
||||
import * as a from 'node:assert/strict';
|
||||
|
|
@ -135,7 +136,10 @@ export class TaskRunnerModule {
|
|||
|
||||
const failureReason = await PyTaskRunnerProcess.checkRequirements();
|
||||
if (failureReason) {
|
||||
Container.get(TaskRequester).setRunnerUnavailable('python', failureReason);
|
||||
Container.get(TaskRequester as ServiceIdentifier<TaskRequester>).setRunnerUnavailable(
|
||||
'python',
|
||||
failureReason,
|
||||
);
|
||||
const error = new MissingRequirementsError(failureReason);
|
||||
this.logger.warn(error.message);
|
||||
return; // allow bootup, will fail at execution time
|
||||
|
|
|
|||
7
packages/cli/src/utils/inverter.ts
Normal file
7
packages/cli/src/utils/inverter.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export function invert<T extends Record<PropertyKey, PropertyKey>>(obj: T) {
|
||||
const result = {} as Record<T[keyof T], keyof T>;
|
||||
for (const key in obj) {
|
||||
result[obj[key]] = key;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -21,7 +21,10 @@ jest.mock('n8n-core', () => ({
|
|||
|
||||
describe('WebhookRequestHandler', () => {
|
||||
const webhookManager = mock<Required<IWebhookManager>>();
|
||||
const handler = createWebhookHandlerFor(webhookManager);
|
||||
const handler = createWebhookHandlerFor(webhookManager) as (
|
||||
req: WebhookRequest | WebhookOptionsRequest,
|
||||
res: Response,
|
||||
) => Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
|
|
@ -29,6 +32,7 @@ describe('WebhookRequestHandler', () => {
|
|||
|
||||
it('should throw for unsupported methods', async () => {
|
||||
const req = mock<WebhookRequest | WebhookOptionsRequest>({
|
||||
path: '/',
|
||||
method: 'CONNECT' as IHttpRequestMethods,
|
||||
});
|
||||
const res = mock<Response>();
|
||||
|
|
@ -46,6 +50,7 @@ describe('WebhookRequestHandler', () => {
|
|||
describe('preflight requests', () => {
|
||||
it('should handle missing header for requested method', async () => {
|
||||
const req = mock<WebhookRequest | WebhookOptionsRequest>({
|
||||
path: '/',
|
||||
method: 'OPTIONS',
|
||||
headers: {
|
||||
origin: 'https://example.com',
|
||||
|
|
@ -69,6 +74,7 @@ describe('WebhookRequestHandler', () => {
|
|||
|
||||
it('should handle default origin and max-age', async () => {
|
||||
const req = mock<WebhookRequest | WebhookOptionsRequest>({
|
||||
path: '/',
|
||||
method: 'OPTIONS',
|
||||
headers: {
|
||||
origin: 'https://example.com',
|
||||
|
|
@ -95,6 +101,7 @@ describe('WebhookRequestHandler', () => {
|
|||
it('should handle wildcard origin', async () => {
|
||||
const randomOrigin = randomString(10);
|
||||
const req = mock<WebhookRequest | WebhookOptionsRequest>({
|
||||
path: '/',
|
||||
method: 'OPTIONS',
|
||||
headers: {
|
||||
origin: randomOrigin,
|
||||
|
|
@ -122,6 +129,7 @@ describe('WebhookRequestHandler', () => {
|
|||
|
||||
it('should handle custom origin', async () => {
|
||||
const req = mock<WebhookRequest | WebhookOptionsRequest>({
|
||||
path: '/',
|
||||
method: 'OPTIONS',
|
||||
headers: {
|
||||
origin: 'https://example.com',
|
||||
|
|
@ -151,6 +159,7 @@ describe('WebhookRequestHandler', () => {
|
|||
describe('webhook requests', () => {
|
||||
it('should delegate the request to the webhook manager and send the response', async () => {
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
params: { path: 'test' },
|
||||
});
|
||||
|
|
@ -177,6 +186,7 @@ describe('WebhookRequestHandler', () => {
|
|||
it('should send an error response if webhook execution throws', async () => {
|
||||
class TestError extends ResponseError {}
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
params: { path: 'test' },
|
||||
});
|
||||
|
|
@ -201,6 +211,7 @@ describe('WebhookRequestHandler', () => {
|
|||
|
||||
it('should not throw when legacy response headers contain invalid names', async () => {
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
params: { path: 'test' },
|
||||
});
|
||||
|
|
@ -226,6 +237,7 @@ describe('WebhookRequestHandler', () => {
|
|||
|
||||
it('should not allow user to override CSP via response headers', async () => {
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
params: { path: 'test' },
|
||||
});
|
||||
|
|
@ -251,6 +263,7 @@ describe('WebhookRequestHandler', () => {
|
|||
"should handle '%s' method",
|
||||
async (method) => {
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
method,
|
||||
params: { path: 'test' },
|
||||
});
|
||||
|
|
@ -274,6 +287,7 @@ describe('WebhookRequestHandler', () => {
|
|||
describe('CSP sandbox header', () => {
|
||||
it('should set CSP sandbox header on all webhook responses', async () => {
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
params: { path: 'test' },
|
||||
});
|
||||
|
|
@ -296,6 +310,7 @@ describe('WebhookRequestHandler', () => {
|
|||
jest.mocked(isWebhookHtmlSandboxingDisabled).mockReturnValueOnce(true);
|
||||
|
||||
const req = mock<WebhookRequest>({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
params: { path: 'test' },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { TestWebhookRegistrationsService } from '@/webhooks/test-webhook-registr
|
|||
import * as WebhookHelpers from '@/webhooks/webhook-helpers';
|
||||
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
|
||||
import type { WorkflowRequest } from '@/workflows/workflow.request';
|
||||
import { WebhookResponse } from './webhook-response';
|
||||
|
||||
import { authAllowlistedNodes } from './constants';
|
||||
import { matchesExpectedNodeType } from './node-type-matcher';
|
||||
|
|
@ -70,7 +71,7 @@ export class TestWebhooks implements IWebhookManager {
|
|||
request: WebhookRequest,
|
||||
response: express.Response,
|
||||
expectedNodeType?: ExpectedWebhookNodeType,
|
||||
): Promise<IWebhookResponseCallbackData> {
|
||||
): Promise<IWebhookResponseCallbackData | WebhookResponse> {
|
||||
const httpMethod = request.method;
|
||||
|
||||
let path = removeTrailingSlash(request.params.path);
|
||||
|
|
@ -158,7 +159,7 @@ export class TestWebhooks implements IWebhookManager {
|
|||
undefined, // executionId
|
||||
request,
|
||||
response,
|
||||
(error: Error | null, data: IWebhookResponseCallbackData) => {
|
||||
(error: Error | null, data: IWebhookResponseCallbackData | WebhookResponse) => {
|
||||
if (error !== null) reject(error);
|
||||
else resolve(data);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import type {
|
|||
IDataObject,
|
||||
IDeferredPromise,
|
||||
IExecuteData,
|
||||
IExecuteResponsePromiseData,
|
||||
IN8nHttpFullResponse,
|
||||
INode,
|
||||
IPinData,
|
||||
|
|
@ -754,7 +755,7 @@ export async function executeWebhook(
|
|||
true,
|
||||
!didSendResponse && !shouldDeferOnReceivedResponse,
|
||||
executionId,
|
||||
responsePromise,
|
||||
responsePromise as IDeferredPromise<IExecuteResponsePromiseData> | undefined,
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -238,14 +238,16 @@ class WebhookRequestHandler {
|
|||
export function createWebhookHandlerFor(
|
||||
webhookManager: IWebhookManager,
|
||||
expectedNodeType?: ExpectedWebhookNodeType,
|
||||
) {
|
||||
): express.RequestHandler {
|
||||
const handler = new WebhookRequestHandler(webhookManager, expectedNodeType);
|
||||
|
||||
return async (req: WebhookRequest | WebhookOptionsRequest, res: express.Response) => {
|
||||
const { params } = req;
|
||||
return async (req, res) => {
|
||||
const webhookRequest = req as WebhookRequest | WebhookOptionsRequest;
|
||||
|
||||
const { params } = webhookRequest;
|
||||
if (Array.isArray(params.path)) {
|
||||
params.path = params.path.join('/');
|
||||
}
|
||||
await handler.handleRequest(req, res);
|
||||
await handler.handleRequest(webhookRequest, res);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ export class WebhookService {
|
|||
webhookData,
|
||||
);
|
||||
|
||||
return (await webhookFn.call(context)) as boolean;
|
||||
return await webhookFn.call(context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -463,7 +463,7 @@ export class WebhookService {
|
|||
try {
|
||||
return nodeType instanceof Node
|
||||
? await nodeType.webhook(context)
|
||||
: ((await nodeType.webhook.call(context)) as IWebhookResponseData);
|
||||
: await nodeType.webhook.call(context);
|
||||
} finally {
|
||||
const settledResults = await Promise.allSettled(closeFunctions.map(async (fn) => await fn()));
|
||||
for (const result of settledResults) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { Request, Response } from 'express';
|
|||
import type { IDataObject, IHttpRequestMethods } from 'n8n-workflow';
|
||||
|
||||
import type { ExpectedWebhookNodeType } from './node-type-matcher';
|
||||
import type { WebhookResponse } from './webhook-response';
|
||||
|
||||
export type WebhookOptionsRequest = Request & { method: 'OPTIONS' };
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ export interface IWebhookManager {
|
|||
req: WebhookRequest,
|
||||
res: Response,
|
||||
expectedNodeType?: ExpectedWebhookNodeType,
|
||||
): Promise<IWebhookResponseCallbackData>;
|
||||
): Promise<IWebhookResponseCallbackData | WebhookResponse>;
|
||||
}
|
||||
|
||||
export interface IWebhookResponseCallbackData {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ import { Logger, ModuleRegistry } from '@n8n/backend-common';
|
|||
import { GlobalConfig, SsrfProtectionConfig } from '@n8n/config';
|
||||
import { ExecutionRepository, WorkflowRepository } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { ServiceIdentifier } from '@n8n/di';
|
||||
import { ExternalSecretsProxy, WorkflowExecute } from 'n8n-core';
|
||||
import { UnexpectedError, Workflow, createRunExecutionData } from 'n8n-workflow';
|
||||
import type {
|
||||
AiEvent,
|
||||
IDataObject,
|
||||
IExecuteData,
|
||||
IExecuteWorkflowInfo,
|
||||
|
|
@ -35,7 +37,7 @@ import type {
|
|||
import { ActiveExecutions } from '@/active-executions';
|
||||
import { CredentialsHelper } from '@/credentials-helper';
|
||||
import { EventService } from '@/events/event.service';
|
||||
import type { AiEventMap, AiEventPayload } from '@/events/maps/ai.event-map';
|
||||
import type { AiEventPayload } from '@/events/maps/ai.event-map';
|
||||
import { getLifecycleHooksForSubExecutions } from '@/execution-lifecycle/execution-lifecycle-hooks';
|
||||
import { FailedRunFactory } from '@/executions/failed-run-factory';
|
||||
import { isManualOrChatExecution } from '@/executions/execution.utils';
|
||||
|
|
@ -415,7 +417,7 @@ async function startExecution(
|
|||
);
|
||||
}
|
||||
|
||||
export function setExecutionStatus(status: ExecutionStatus) {
|
||||
export function setExecutionStatus(this: { executionId?: string }, status: ExecutionStatus) {
|
||||
const logger = Container.get(Logger);
|
||||
if (this.executionId === undefined) {
|
||||
logger.debug(`Setting execution status "${status}" failed because executionId is undefined`);
|
||||
|
|
@ -425,7 +427,11 @@ export function setExecutionStatus(status: ExecutionStatus) {
|
|||
Container.get(ActiveExecutions).setStatus(this.executionId, status);
|
||||
}
|
||||
|
||||
export function sendDataToUI(type: PushType, data: IDataObject | IDataObject[]) {
|
||||
export function sendDataToUI(
|
||||
this: { pushRef?: string },
|
||||
type: PushType,
|
||||
data: IDataObject | IDataObject[],
|
||||
) {
|
||||
const { pushRef } = this;
|
||||
if (pushRef === undefined) {
|
||||
return;
|
||||
|
|
@ -538,9 +544,11 @@ export async function getBase({
|
|||
executeData,
|
||||
);
|
||||
},
|
||||
logAiEvent: (eventName: keyof AiEventMap, payload: AiEventPayload) =>
|
||||
eventService.emit(eventName, payload),
|
||||
getRunnerStatus: (taskType: string) => Container.get(TaskRequester).getRunnerStatus(taskType),
|
||||
logAiEvent: (eventName: AiEvent, payload: AiEventPayload) => {
|
||||
eventService.emit(eventName, payload);
|
||||
},
|
||||
getRunnerStatus: (taskType: string) =>
|
||||
Container.get(TaskRequester as ServiceIdentifier<TaskRequester>).getRunnerStatus(taskType),
|
||||
};
|
||||
|
||||
const ssrfConfig = Container.get(SsrfProtectionConfig);
|
||||
|
|
|
|||
|
|
@ -251,6 +251,12 @@ export class WorkflowService {
|
|||
);
|
||||
}
|
||||
|
||||
private isWorkflowWithSharing(
|
||||
workflow: ListQueryDb.Workflow.Plain,
|
||||
): workflow is ListQueryDb.Workflow.WithSharing {
|
||||
return 'shared' in workflow;
|
||||
}
|
||||
|
||||
private cleanupSharedField(
|
||||
workflows: ListQueryDb.Workflow.Plain[] | ListQueryDb.Workflow.WithSharing[],
|
||||
): void {
|
||||
|
|
@ -260,7 +266,9 @@ export class WorkflowService {
|
|||
though. So to avoid leaking the information we just delete it.
|
||||
*/
|
||||
workflows.forEach((workflow) => {
|
||||
if (this.isWorkflowWithSharing(workflow)) {
|
||||
delete workflow.shared;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,9 +144,9 @@ describe('init()', () => {
|
|||
|
||||
describe('add()', () => {
|
||||
describe('in single-main mode', () => {
|
||||
test.each(['activate', 'update'])(
|
||||
test.each<WorkflowActivateMode>(['activate', 'update'])(
|
||||
"should add webhooks, triggers and pollers for workflow in '%s' activation mode",
|
||||
async (mode: WorkflowActivateMode) => {
|
||||
async (mode) => {
|
||||
await activeWorkflowManager.init();
|
||||
|
||||
const dbWorkflow = await createActiveWorkflow();
|
||||
|
|
|
|||
|
|
@ -534,8 +534,9 @@ describe('SourceControlService', () => {
|
|||
return [];
|
||||
});
|
||||
|
||||
fsReadFile.mockImplementation(async (path: string) => {
|
||||
const pathWithoutCwd = isAbsolute(path) ? basename(path) : path;
|
||||
fsReadFile.mockImplementation(async (path) => {
|
||||
const pathStr = String(path);
|
||||
const pathWithoutCwd = isAbsolute(pathStr) ? basename(pathStr) : pathStr;
|
||||
return JSON.stringify(gitFiles[pathWithoutCwd]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,9 +6,11 @@ import { gzipSync, deflateSync } from 'zlib';
|
|||
import { rawBodyReader, bodyParser } from '@/middlewares/body-parser';
|
||||
|
||||
describe('bodyParser', () => {
|
||||
const server = createServer((req: Request, res: Response) => {
|
||||
void rawBodyReader(req, res, async () => {
|
||||
void bodyParser(req, res, () => res.end(JSON.stringify(req.body)));
|
||||
const server = createServer((req, res) => {
|
||||
const expressReq = req as unknown as Request;
|
||||
const expressRes = res as unknown as Response;
|
||||
void rawBodyReader(expressReq, expressRes, async () => {
|
||||
void bodyParser(expressReq, expressRes, () => res.end(JSON.stringify(expressReq.body)));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ export class LicenseMocker {
|
|||
|
||||
mock(license: License) {
|
||||
license.isLicensed = this.isFeatureEnabled.bind(this);
|
||||
license.getValue = this.getFeatureValue.bind(this);
|
||||
license.getValue = this.getFeatureValue.bind(this) as typeof license.getValue;
|
||||
}
|
||||
|
||||
mockLicenseState(licenseState: LicenseState) {
|
||||
const licenseProvider: LicenseProvider = {
|
||||
isLicensed: this.isFeatureEnabled.bind(this),
|
||||
getValue: this.getFeatureValue.bind(this),
|
||||
getValue: this.getFeatureValue.bind(this) as LicenseProvider['getValue'],
|
||||
};
|
||||
|
||||
licenseState.setLicenseProvider(licenseProvider);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,10 @@
|
|||
"@test-integration/*": ["./test/integration/shared/*"]
|
||||
},
|
||||
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo",
|
||||
"strict": true,
|
||||
"strictFunctionTypes": false,
|
||||
"strictPropertyInitialization": false,
|
||||
// TODO: remove all options below this line
|
||||
"strict": false,
|
||||
"useUnknownInCatchVariables": false
|
||||
},
|
||||
"include": ["src/**/*.ts", "test/**/*.ts", "src/sso.ee/saml/saml-schema-metadata-2.0.xsd"],
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export type ExecutionLifecycleHookHandlers = {
|
|||
(
|
||||
this: ExecutionLifecycleHooks,
|
||||
workflow: Workflow,
|
||||
data?: IRunExecutionData,
|
||||
data: IRunExecutionData,
|
||||
) => Promise<void> | void
|
||||
>;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ interface MemoryMessage {
|
|||
lc: number;
|
||||
type: string;
|
||||
id: string[];
|
||||
kwargs: {
|
||||
kwargs?: {
|
||||
content: unknown;
|
||||
additional_kwargs: Record<string, unknown>;
|
||||
};
|
||||
|
|
@ -26,6 +26,27 @@ const fallbackParser = (execData: IDataObject) => ({
|
|||
parsed: false,
|
||||
});
|
||||
|
||||
function isMemoryMessage(message: unknown): message is MemoryMessage {
|
||||
if (typeof message !== 'object' || message === null || Array.isArray(message)) {
|
||||
return false;
|
||||
}
|
||||
if (!('lc' in message) || typeof message.lc !== 'number') {
|
||||
return false;
|
||||
}
|
||||
if (!('type' in message) || typeof message.type !== 'string') {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!('id' in message) ||
|
||||
!Array.isArray(message.id) ||
|
||||
!message.id.every((item): item is string => typeof item === 'string')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const outputTypeParsers: {
|
||||
[key in AllowedEndpointType]: (execData: IDataObject) => {
|
||||
type: 'json' | 'text' | 'markdown';
|
||||
|
|
@ -93,12 +114,9 @@ const outputTypeParsers: {
|
|||
(execData?.response as IDataObject)?.chat_history;
|
||||
if (Array.isArray(chatHistory)) {
|
||||
const responseText = chatHistory
|
||||
.filter(isMemoryMessage)
|
||||
.map((content: MemoryMessage) => {
|
||||
if (
|
||||
content.type === 'constructor' &&
|
||||
content.id?.includes('messages') &&
|
||||
content.kwargs
|
||||
) {
|
||||
if (content.type === 'constructor' && content.id.includes('messages') && content.kwargs) {
|
||||
interface MessageContent {
|
||||
type: string;
|
||||
text?: string;
|
||||
|
|
@ -126,7 +144,10 @@ const outputTypeParsers: {
|
|||
})
|
||||
.join('\n');
|
||||
}
|
||||
if (Object.keys(content.kwargs.additional_kwargs).length) {
|
||||
if (
|
||||
content.kwargs.additional_kwargs &&
|
||||
Object.keys(content.kwargs.additional_kwargs).length
|
||||
) {
|
||||
message += ` (${JSON.stringify(content.kwargs.additional_kwargs)})`;
|
||||
}
|
||||
if (content.id.includes('HumanMessage')) {
|
||||
|
|
|
|||
|
|
@ -1240,7 +1240,7 @@ export interface IPollFunctions
|
|||
__emit(
|
||||
data: INodeExecutionData[][],
|
||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||
donePromise?: IDeferredPromise<IRun>,
|
||||
donePromise?: IDeferredPromise<IRun | undefined>,
|
||||
): void;
|
||||
__emitError(error: Error, responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>): void;
|
||||
getNodeParameter(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user