fix: 修复n8n项目多个模块错误和配置问题

- 添加WooCommerce节点描述文件
- 实现错误处理基础类结构
- 修复data-store仓库TypeScript类型问题
- 更新Claude本地配置
- 修复start.bat脚本
This commit is contained in:
2025-09-09 14:45:53 +08:00
parent 83dc9270c8
commit a6025569b7
14 changed files with 790 additions and 479 deletions

View File

@@ -0,0 +1,52 @@
import { ApplicationError, type ReportingOptions } from '@n8n/errors';
import type { Functionality, IDataObject, JsonObject } from '../../interfaces';
interface ExecutionBaseErrorOptions extends ReportingOptions {
cause?: Error;
errorResponse?: JsonObject;
}
export abstract class ExecutionBaseError extends ApplicationError {
description: string | null | undefined;
override cause?: Error;
errorResponse?: JsonObject;
timestamp: number;
context: IDataObject = {};
lineNumber: number | undefined;
functionality: Functionality = 'regular';
constructor(message: string, options: ExecutionBaseErrorOptions = {}) {
super(message, options);
this.name = this.constructor.name;
this.timestamp = Date.now();
const { cause, errorResponse } = options;
if (cause instanceof ExecutionBaseError) {
this.context = cause.context;
} else if (cause && !(cause instanceof Error)) {
this.cause = cause;
}
if (errorResponse) this.errorResponse = errorResponse;
}
toJSON?() {
return {
message: this.message,
lineNumber: this.lineNumber,
timestamp: this.timestamp,
name: this.name,
description: this.description,
context: this.context,
cause: this.cause,
};
}
}

View File

@@ -0,0 +1,187 @@
import { ExecutionBaseError } from './execution-base.error';
import type { IDataObject, INode, JsonObject } from '../../interfaces';
import { isTraversableObject, jsonParse } from '../../utils';
/**
* Descriptive messages for common errors.
*/
const COMMON_ERRORS: IDataObject = {
// nodeJS errors
ECONNREFUSED: 'The service refused the connection - perhaps it is offline',
ECONNRESET:
'The connection to the server was closed unexpectedly, perhaps it is offline. You can retry the request immediately or wait and retry later.',
ENOTFOUND:
'The connection cannot be established, this usually occurs due to an incorrect host (domain) value',
ETIMEDOUT:
"The connection timed out, consider setting the 'Retry on Fail' option in the node settings",
ERRADDRINUSE:
'The port is already occupied by some other application, if possible change the port or kill the application that is using it',
EADDRNOTAVAIL: 'The address is not available, ensure that you have the right IP address',
ECONNABORTED: 'The connection was aborted, perhaps the server is offline',
EHOSTUNREACH: 'The host is unreachable, perhaps the server is offline',
EAI_AGAIN: 'The DNS server returned an error, perhaps the server is offline',
ENOENT: 'The file or directory does not exist',
EISDIR: 'The file path was expected but the given path is a directory',
ENOTDIR: 'The directory path was expected but the given path is a file',
EACCES: 'Forbidden by access permissions, make sure you have the right permissions',
EEXIST: 'The file or directory already exists',
EPERM: 'Operation not permitted, make sure you have the right permissions',
// other errors
GETADDRINFO: 'The server closed the connection unexpectedly',
};
/**
* Base class for specific NodeError-types, with functionality for finding
* a value recursively inside an error object.
*/
export abstract class NodeError extends ExecutionBaseError {
messages: string[] = [];
constructor(
readonly node: INode,
error: Error | JsonObject,
) {
const isError = error instanceof Error;
const message = isError ? error.message : '';
const options = isError ? { cause: error } : { errorResponse: error };
super(message, options);
if (error instanceof NodeError) {
this.tags.reWrapped = true;
}
}
/**
* Finds property through exploration based on potential keys and traversal keys.
* Depth-first approach.
*
* This method iterates over `potentialKeys` and, if the value at the key is a
* truthy value, the type of the value is checked:
* (1) if a string or number, the value is returned as a string; or
* (2) if an array,
* its string or number elements are collected as a long string,
* its object elements are traversed recursively (restart this function
* with each object as a starting point), or
* (3) if it is an object, it traverses the object and nested ones recursively
* based on the `potentialKeys` and returns a string if found.
*
* If nothing found via `potentialKeys` this method iterates over `traversalKeys` and
* if the value at the key is a traversable object, it restarts with the object as the
* new starting point (recursion).
* If nothing found for any of the `traversalKeys`, exploration continues with remaining
* `traversalKeys`.
*
* Otherwise, if all the paths have been exhausted and no value is eligible, `null` is
* returned.
*
*/
protected findProperty(
jsonError: JsonObject,
potentialKeys: string[],
traversalKeys: string[] = [],
): string | null {
for (const key of potentialKeys) {
let value = jsonError[key];
if (value) {
if (typeof value === 'string') {
try {
value = jsonParse(value);
} catch (error) {
return value as string;
}
if (typeof value === 'string') return value;
}
if (typeof value === 'number') return value.toString();
if (Array.isArray(value)) {
const resolvedErrors: string[] = value
.map((jsonError) => {
if (typeof jsonError === 'string') return jsonError;
if (typeof jsonError === 'number') return jsonError.toString();
if (isTraversableObject(jsonError)) {
return this.findProperty(jsonError, potentialKeys);
}
return null;
})
.filter((errorValue): errorValue is string => errorValue !== null);
if (resolvedErrors.length === 0) {
return null;
}
return resolvedErrors.join(' | ');
}
if (isTraversableObject(value)) {
const property = this.findProperty(value, potentialKeys);
if (property) {
return property;
}
}
}
}
for (const key of traversalKeys) {
const value = jsonError[key];
if (isTraversableObject(value)) {
const property = this.findProperty(value, potentialKeys, traversalKeys);
if (property) {
return property;
}
}
}
return null;
}
/**
* Preserve the original error message before setting the new one
*/
protected addToMessages(message: string): void {
if (message && !this.messages.includes(message)) {
this.messages.push(message);
}
}
/**
* Set descriptive error message if code is provided or if message contains any of the common errors,
* update description to include original message plus the description
*/
protected setDescriptiveErrorMessage(
message: string,
messages: string[],
code?: string | null,
messageMapping?: { [key: string]: string },
): [string, string[]] {
let newMessage = message;
if (messageMapping) {
for (const [mapKey, mapMessage] of Object.entries(messageMapping)) {
if ((message || '').toUpperCase().includes(mapKey.toUpperCase())) {
newMessage = mapMessage;
messages.push(message);
break;
}
}
if (newMessage !== message) {
return [newMessage, messages];
}
}
// if code is provided and it is in the list of common errors set the message and return early
if (code && typeof code === 'string' && COMMON_ERRORS[code.toUpperCase()]) {
newMessage = COMMON_ERRORS[code] as string;
messages.push(message);
return [newMessage, messages];
}
// check if message contains any of the common errors and set the message and description
for (const [errorCode, errorDescriptiveMessage] of Object.entries(COMMON_ERRORS)) {
if ((message || '').toUpperCase().includes(errorCode.toUpperCase())) {
newMessage = errorDescriptiveMessage as string;
messages.push(message);
break;
}
}
return [newMessage, messages];
}
}

View File

@@ -0,0 +1,58 @@
import type { Event } from '@sentry/node';
import callsites from 'callsites';
import type { ErrorTags, ErrorLevel, ReportingOptions } from '@n8n/errors';
export type BaseErrorOptions = { description?: string | undefined | null } & ErrorOptions &
ReportingOptions;
/**
* Base class for all errors
*/
export abstract class BaseError extends Error {
/**
* Error level. Defines which level the error should be logged/reported
* @default 'error'
*/
level: ErrorLevel;
/**
* Whether the error should be reported to Sentry.
* @default true
*/
readonly shouldReport: boolean;
readonly description: string | null | undefined;
readonly tags: ErrorTags;
readonly extra?: Event['extra'];
readonly packageName?: string;
constructor(
message: string,
{
level = 'error',
description,
shouldReport,
tags = {},
extra,
...rest
}: BaseErrorOptions = {},
) {
super(message, rest);
this.level = level;
this.shouldReport = shouldReport ?? (level === 'error' || level === 'fatal');
this.description = description;
this.tags = tags;
this.extra = extra;
try {
const filePath = callsites()[2].getFileName() ?? '';
const match = /packages\/([^\/]+)\//.exec(filePath)?.[1];
if (match) this.tags.packageName = match;
} catch {}
}
}

View File

@@ -0,0 +1,21 @@
import type { BaseErrorOptions } from './base.error';
import { BaseError } from './base.error';
export type OperationalErrorOptions = Omit<BaseErrorOptions, 'level'> & {
level?: 'info' | 'warning' | 'error';
};
/**
* Error that indicates a transient issue, like a network request failing,
* a database query timing out, etc. These are expected to happen, are
* transient by nature and should be handled gracefully.
*
* Default level: warning
*/
export class OperationalError extends BaseError {
constructor(message: string, opts: OperationalErrorOptions = {}) {
opts.level = opts.level ?? 'warning';
super(message, opts);
}
}

View File

@@ -0,0 +1,21 @@
import type { BaseErrorOptions } from './base.error';
import { BaseError } from './base.error';
export type UnexpectedErrorOptions = Omit<BaseErrorOptions, 'level'> & {
level?: 'error' | 'fatal';
};
/**
* Error that indicates something is wrong in the code: logic mistakes,
* unhandled cases, assertions that fail. These are not recoverable and
* should be brought to developers' attention.
*
* Default level: error
*/
export class UnexpectedError extends BaseError {
constructor(message: string, opts: UnexpectedErrorOptions = {}) {
opts.level = opts.level ?? 'error';
super(message, opts);
}
}

View File

@@ -0,0 +1,24 @@
import type { BaseErrorOptions } from './base.error';
import { BaseError } from './base.error';
export type UserErrorOptions = Omit<BaseErrorOptions, 'level'> & {
level?: 'info' | 'warning';
description?: string | null | undefined;
};
/**
* Error that indicates the user performed an action that caused an error.
* E.g. provided invalid input, tried to access a resource theyre not
* authorized to, or violates a business rule.
*
* Default level: info
*/
export class UserError extends BaseError {
declare readonly description: string | null | undefined;
constructor(message: string, opts: UserErrorOptions = {}) {
opts.level = opts.level ?? 'info';
super(message, opts);
}
}