mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-28 22:19:41 -08:00
dff8456382
Ensure all errors in `core` and `workflow` inherit from `ApplicationError` so that we start normalizing all the errors we report to Sentry Follow-up to: https://github.com/n8n-io/n8n/pull/7757#discussion_r1404338844 ### `core` package `ApplicationError` - `FileSystemError` (abstract) - `FileNotFoundError` - `DisallowedFilepathError` - `BinaryDataError` (abstract) - `InvalidModeError` - `InvalidManagerError` - `InvalidExecutionMetadataError` ### `workflow` package `ApplicationError` - `ExecutionBaseError` (abstract) - `WorkflowActivationError` - `WorkflowDeactivationError` - `WebhookTakenError` - `WorkflowOperationError` - `SubworkflowOperationError` - `CliWorkflowOperationError` - `ExpressionError` - `ExpressionExtensionError` - `NodeError` (abstract) - `NodeOperationError` - `NodeApiError` - `NodeSSLError` Up next: - Reorganize errors in `cli` - Flatten the hierarchy in `workflow` (do we really need `ExecutionBaseError`?) - Remove `ExecutionError` type - Stop throwing plain `Error`s - Replace `severity` with `level` - Add node and credential types as `tags` - Add workflow IDs and execution IDs as `extras`
323 lines
8.2 KiB
TypeScript
323 lines
8.2 KiB
TypeScript
import { ExpressionExtensionError } from '../errors/expression-extension.error';
|
|
|
|
import { DateTime } from 'luxon';
|
|
import type {
|
|
DateTimeUnit,
|
|
DurationLike,
|
|
DurationObjectUnits,
|
|
LocaleOptions,
|
|
WeekdayNumbers,
|
|
} from 'luxon';
|
|
import type { ExtensionMap } from './Extensions';
|
|
import { convertToDateTime } from './utils';
|
|
|
|
type DurationUnit =
|
|
| 'milliseconds'
|
|
| 'seconds'
|
|
| 'minutes'
|
|
| 'hours'
|
|
| 'days'
|
|
| 'weeks'
|
|
| 'months'
|
|
| 'quarter'
|
|
| 'years';
|
|
type DatePart =
|
|
| 'day'
|
|
| 'week'
|
|
| 'month'
|
|
| 'year'
|
|
| 'hour'
|
|
| 'minute'
|
|
| 'second'
|
|
| 'millisecond'
|
|
| 'weekNumber'
|
|
| 'yearDayNumber'
|
|
| 'weekday';
|
|
|
|
const DURATION_MAP: Record<string, DurationUnit> = {
|
|
day: 'days',
|
|
month: 'months',
|
|
year: 'years',
|
|
week: 'weeks',
|
|
hour: 'hours',
|
|
minute: 'minutes',
|
|
second: 'seconds',
|
|
millisecond: 'milliseconds',
|
|
ms: 'milliseconds',
|
|
sec: 'seconds',
|
|
secs: 'seconds',
|
|
hr: 'hours',
|
|
hrs: 'hours',
|
|
min: 'minutes',
|
|
mins: 'minutes',
|
|
};
|
|
|
|
const DATETIMEUNIT_MAP: Record<string, DateTimeUnit> = {
|
|
days: 'day',
|
|
months: 'month',
|
|
years: 'year',
|
|
hours: 'hour',
|
|
minutes: 'minute',
|
|
seconds: 'second',
|
|
milliseconds: 'millisecond',
|
|
hrs: 'hour',
|
|
hr: 'hour',
|
|
mins: 'minute',
|
|
min: 'minute',
|
|
secs: 'second',
|
|
sec: 'second',
|
|
ms: 'millisecond',
|
|
};
|
|
|
|
function isDateTime(date: unknown): date is DateTime {
|
|
return date ? DateTime.isDateTime(date) : false;
|
|
}
|
|
|
|
function generateDurationObject(durationValue: number, unit: DurationUnit): DurationObjectUnits {
|
|
const convertedUnit = DURATION_MAP[unit] || unit;
|
|
return { [`${convertedUnit}`]: durationValue };
|
|
}
|
|
|
|
function beginningOf(date: Date | DateTime, extraArgs: DurationUnit[]): Date | DateTime {
|
|
const [rawUnit = 'week'] = extraArgs;
|
|
|
|
const unit = DATETIMEUNIT_MAP[rawUnit] || rawUnit;
|
|
|
|
if (isDateTime(date)) return date.startOf(unit);
|
|
|
|
return DateTime.fromJSDate(date).startOf(unit).toJSDate();
|
|
}
|
|
|
|
function endOfMonth(date: Date | DateTime): Date | DateTime {
|
|
if (isDateTime(date)) return date.endOf('month');
|
|
|
|
return DateTime.fromJSDate(date).endOf('month').toJSDate();
|
|
}
|
|
|
|
function extract(date: Date | DateTime, args: DatePart[]): number {
|
|
let [part = 'week'] = args;
|
|
|
|
if (part === 'yearDayNumber') {
|
|
date = isDateTime(date) ? date.toJSDate() : date;
|
|
|
|
const firstDayOfTheYear = new Date(date.getFullYear(), 0, 0);
|
|
|
|
const diff =
|
|
date.getTime() -
|
|
firstDayOfTheYear.getTime() +
|
|
(firstDayOfTheYear.getTimezoneOffset() - date.getTimezoneOffset()) * 60 * 1000;
|
|
|
|
return Math.floor(diff / (1000 * 60 * 60 * 24));
|
|
}
|
|
|
|
if (part === 'week') part = 'weekNumber';
|
|
|
|
const unit = (DATETIMEUNIT_MAP[part] as keyof DateTime) || part;
|
|
|
|
if (isDateTime(date)) return date.get(unit);
|
|
|
|
return DateTime.fromJSDate(date).get(unit);
|
|
}
|
|
|
|
function format(date: Date | DateTime, extraArgs: unknown[]): string {
|
|
const [dateFormat, localeOpts = {}] = extraArgs as [string, LocaleOptions];
|
|
if (isDateTime(date)) {
|
|
return date.toFormat(dateFormat, { ...localeOpts });
|
|
}
|
|
return DateTime.fromJSDate(date).toFormat(dateFormat, { ...localeOpts });
|
|
}
|
|
|
|
function isBetween(
|
|
date: Date | DateTime,
|
|
extraArgs: Array<string | Date | DateTime>,
|
|
): boolean | undefined {
|
|
if (extraArgs.length !== 2) {
|
|
throw new ExpressionExtensionError('isBetween(): expected exactly two args');
|
|
}
|
|
|
|
const [first, second] = extraArgs;
|
|
|
|
const firstDate = convertToDateTime(first);
|
|
const secondDate = convertToDateTime(second);
|
|
|
|
if (!firstDate || !secondDate) {
|
|
return;
|
|
}
|
|
|
|
if (firstDate > secondDate) {
|
|
return secondDate < date && date < firstDate;
|
|
}
|
|
return secondDate > date && date > firstDate;
|
|
}
|
|
|
|
function isDst(date: Date | DateTime): boolean {
|
|
if (isDateTime(date)) {
|
|
return date.isInDST;
|
|
}
|
|
return DateTime.fromJSDate(date).isInDST;
|
|
}
|
|
|
|
function isInLast(date: Date | DateTime, extraArgs: unknown[]): boolean {
|
|
const [durationValue = 0, unit = 'minutes'] = extraArgs as [number, DurationUnit];
|
|
|
|
const dateInThePast = DateTime.now().minus(generateDurationObject(durationValue, unit));
|
|
let thisDate = date;
|
|
if (!isDateTime(thisDate)) {
|
|
thisDate = DateTime.fromJSDate(thisDate);
|
|
}
|
|
return dateInThePast <= thisDate && thisDate <= DateTime.now();
|
|
}
|
|
|
|
const WEEKEND_DAYS: WeekdayNumbers[] = [6, 7];
|
|
function isWeekend(date: Date | DateTime): boolean {
|
|
const { weekday } = isDateTime(date) ? date : DateTime.fromJSDate(date);
|
|
return WEEKEND_DAYS.includes(weekday);
|
|
}
|
|
|
|
function minus(
|
|
date: Date | DateTime,
|
|
args: [DurationLike] | [number, DurationUnit],
|
|
): Date | DateTime {
|
|
if (args.length === 1) {
|
|
const [arg] = args;
|
|
|
|
if (isDateTime(date)) return date.minus(arg);
|
|
|
|
return DateTime.fromJSDate(date).minus(arg).toJSDate();
|
|
}
|
|
|
|
const [durationValue = 0, unit = 'minutes'] = args;
|
|
|
|
const duration = generateDurationObject(durationValue, unit);
|
|
|
|
if (isDateTime(date)) return date.minus(duration);
|
|
|
|
return DateTime.fromJSDate(date).minus(duration).toJSDate();
|
|
}
|
|
|
|
function plus(
|
|
date: Date | DateTime,
|
|
args: [DurationLike] | [number, DurationUnit],
|
|
): Date | DateTime {
|
|
if (args.length === 1) {
|
|
const [arg] = args;
|
|
|
|
if (isDateTime(date)) return date.plus(arg);
|
|
|
|
return DateTime.fromJSDate(date).plus(arg).toJSDate();
|
|
}
|
|
|
|
const [durationValue = 0, unit = 'minutes'] = args;
|
|
|
|
const duration = generateDurationObject(durationValue, unit);
|
|
|
|
if (isDateTime(date)) return date.plus(duration);
|
|
|
|
return DateTime.fromJSDate(date).plus(duration).toJSDate();
|
|
}
|
|
|
|
endOfMonth.doc = {
|
|
name: 'endOfMonth',
|
|
returnType: 'Date',
|
|
description: 'Transforms a date to the last possible moment that lies within the month.',
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-endOfMonth',
|
|
};
|
|
|
|
isDst.doc = {
|
|
name: 'isDst',
|
|
returnType: 'boolean',
|
|
description: 'Checks if a Date is within Daylight Savings Time.',
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isDst',
|
|
};
|
|
|
|
isWeekend.doc = {
|
|
name: 'isWeekend',
|
|
returnType: 'boolean',
|
|
description: 'Checks if the Date falls on a Saturday or Sunday.',
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isWeekend',
|
|
};
|
|
|
|
beginningOf.doc = {
|
|
name: 'beginningOf',
|
|
description: 'Transform a Date to the start of the given time period. Default unit is `week`.',
|
|
returnType: 'Date',
|
|
args: [{ name: 'unit?', type: 'DurationUnit' }],
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-beginningOf',
|
|
};
|
|
|
|
extract.doc = {
|
|
name: 'extract',
|
|
description: 'Extracts the part defined in `datePart` from a Date. Default unit is `week`.',
|
|
returnType: 'number',
|
|
args: [{ name: 'datePart?', type: 'DurationUnit' }],
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-extract',
|
|
};
|
|
|
|
format.doc = {
|
|
name: 'format',
|
|
description: 'Formats a Date in the given structure.',
|
|
returnType: 'string',
|
|
args: [{ name: 'fmt', type: 'TimeFormat' }],
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-format',
|
|
};
|
|
|
|
isBetween.doc = {
|
|
name: 'isBetween',
|
|
description: 'Checks if a Date is between two given dates.',
|
|
returnType: 'boolean',
|
|
args: [
|
|
{ name: 'date1', type: 'Date|string' },
|
|
{ name: 'date2', type: 'Date|string' },
|
|
],
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isBetween',
|
|
};
|
|
|
|
isInLast.doc = {
|
|
name: 'isInLast',
|
|
description: 'Checks if a Date is within a given time period. Default unit is `minute`.',
|
|
returnType: 'boolean',
|
|
args: [
|
|
{ name: 'n', type: 'number' },
|
|
{ name: 'unit?', type: 'DurationUnit' },
|
|
],
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-isInLast',
|
|
};
|
|
|
|
minus.doc = {
|
|
name: 'minus',
|
|
description: 'Subtracts a given time period from a Date. Default unit is `minute`.',
|
|
returnType: 'Date',
|
|
args: [
|
|
{ name: 'n', type: 'number' },
|
|
{ name: 'unit?', type: 'DurationUnit' },
|
|
],
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-minus',
|
|
};
|
|
|
|
plus.doc = {
|
|
name: 'plus',
|
|
description: 'Adds a given time period to a Date. Default unit is `minute`.',
|
|
returnType: 'Date',
|
|
args: [
|
|
{ name: 'n', type: 'number' },
|
|
{ name: 'unit?', type: 'DurationUnit' },
|
|
],
|
|
docURL: 'https://docs.n8n.io/code/builtin/data-transformation-functions/dates/#date-plus',
|
|
};
|
|
|
|
export const dateExtensions: ExtensionMap = {
|
|
typeName: 'Date',
|
|
functions: {
|
|
beginningOf,
|
|
endOfMonth,
|
|
extract,
|
|
isBetween,
|
|
isDst,
|
|
isInLast,
|
|
isWeekend,
|
|
minus,
|
|
plus,
|
|
format,
|
|
},
|
|
};
|