chore: 清理macOS同步产生的重复文件

详细说明:
- 删除了352个带数字后缀的重复文件
- 更新.gitignore防止未来产生此类文件
- 这些文件是由iCloud或其他同步服务冲突产生的
- 不影响项目功能,仅清理冗余文件
This commit is contained in:
Yep_Q
2025-09-08 12:06:01 +08:00
parent 1564396449
commit d6f48d6d14
365 changed files with 2039 additions and 68301 deletions

View File

@@ -1,9 +0,0 @@
/**
* Asserts given condition
*/
export function assert(condition: unknown, message?: string): asserts condition {
if (!condition) {
// eslint-disable-next-line n8n-local-rules/no-plain-errors
throw new Error(message ?? 'Assertion failed');
}
}

View File

@@ -1,84 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type CallbackFn = (...args: any[]) => any;
type Payloads<ListenerMap> = {
[E in keyof ListenerMap]: unknown;
};
type Listener<Payload> = (payload: Payload) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface EventBus<ListenerMap extends Payloads<ListenerMap> = Record<string, any>> {
on<EventName extends keyof ListenerMap & string>(
eventName: EventName,
fn: Listener<ListenerMap[EventName]>,
): void;
once<EventName extends keyof ListenerMap & string>(
eventName: EventName,
fn: Listener<ListenerMap[EventName]>,
): void;
off<EventName extends keyof ListenerMap & string>(
eventName: EventName,
fn: Listener<ListenerMap[EventName]>,
): void;
emit<EventName extends keyof ListenerMap & string>(
eventName: EventName,
event?: ListenerMap[EventName],
): void;
}
/**
* Creates an event bus with the given listener map.
*
* @example
* ```ts
* const eventBus = createEventBus<{
* 'user-logged-in': { username: string };
* 'user-logged-out': never;
* }>();
*/
export function createEventBus<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ListenerMap extends Payloads<ListenerMap> = Record<string, any>,
>(): EventBus<ListenerMap> {
const handlers = new Map<string, CallbackFn[]>();
return {
on(eventName, fn) {
let eventFns = handlers.get(eventName);
if (!eventFns) {
eventFns = [fn];
} else {
eventFns.push(fn);
}
handlers.set(eventName, eventFns);
},
once(eventName, fn) {
const handler: typeof fn = (payload) => {
this.off(eventName, handler);
fn(payload);
};
this.on(eventName, handler);
},
off(eventName, fn) {
const eventFns = handlers.get(eventName);
if (eventFns) {
eventFns.splice(eventFns.indexOf(fn) >>> 0, 1);
}
},
emit(eventName, event) {
const eventFns = handlers.get(eventName);
if (eventFns) {
eventFns.slice().forEach((handler) => {
handler(event);
});
}
},
};
}

View File

@@ -1,74 +0,0 @@
import { createEventBus } from './event-bus';
describe('createEventBus()', () => {
const eventBus = createEventBus();
describe('on()', () => {
it('should register event handler', () => {
const handler = vi.fn();
const eventName = 'test';
eventBus.on(eventName, handler);
eventBus.emit(eventName, {});
expect(handler).toHaveBeenCalled();
});
});
describe('once()', () => {
it('should register event handler', () => {
const handler = vi.fn();
const eventName = 'test';
eventBus.once(eventName, handler);
eventBus.emit(eventName, {});
expect(handler).toHaveBeenCalled();
});
it('should unregister event handler after first call', () => {
const handler = vi.fn();
const eventName = 'test';
eventBus.once(eventName, handler);
eventBus.emit(eventName, {});
eventBus.emit(eventName, {});
expect(handler).toHaveBeenCalledTimes(1);
});
});
describe('off()', () => {
it('should register event handler', () => {
const handler = vi.fn();
const eventName = 'test';
eventBus.on(eventName, handler);
eventBus.off(eventName, handler);
eventBus.emit(eventName, {});
expect(handler).not.toHaveBeenCalled();
});
});
describe('emit()', () => {
it('should call handlers with given event', () => {
const handlerA = vi.fn();
const handlerB = vi.fn();
const eventName = 'test';
const event = new Event(eventName);
eventBus.on(eventName, handlerA);
eventBus.on(eventName, handlerB);
eventBus.emit(eventName, event);
expect(handlerA).toHaveBeenCalledWith(event);
expect(handlerB).toHaveBeenCalledWith(event);
});
});
});

View File

@@ -1,50 +0,0 @@
/**
* Create an event queue that processes events sequentially.
*
* @param processEvent - Async function that processes a single event.
* @returns A function that enqueues events for processing.
*/
export function createEventQueue<T>(processEvent: (event: T) => Promise<void>) {
// The internal queue holding events.
const queue: T[] = [];
// Flag to indicate whether an event is currently being processed.
let processing = false;
/**
* Process the next event in the queue (if not already processing).
*/
async function processNext(): Promise<void> {
if (processing || queue.length === 0) {
return;
}
processing = true;
const currentEvent = queue.shift();
if (currentEvent !== undefined) {
try {
await processEvent(currentEvent);
} catch (error) {
console.error('Error processing event:', error);
}
}
processing = false;
// Recursively process the next event.
await processNext();
}
/**
* Enqueue an event and trigger processing.
*
* @param event - The event to enqueue.
*/
function enqueue(event: T): void {
queue.push(event);
void processNext();
}
return { enqueue };
}

View File

@@ -1,100 +0,0 @@
import { createEventQueue } from './event-queue';
describe('createEventQueue', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should process events in order', async () => {
const processedEvents: string[] = [];
// Create an async handler that pushes events into the processedEvents array.
const processEvent = vi.fn(async (event: string) => {
processedEvents.push(event);
// Simulate asynchronous delay of 10ms.
await new Promise((resolve) => setTimeout(resolve, 10));
});
// Create the event queue.
const { enqueue } = createEventQueue<string>(processEvent);
// Enqueue events in a specific order.
enqueue('Event 1');
enqueue('Event 2');
enqueue('Event 3');
// Advance the timers enough to process all events.
// runAllTimersAsync() will run all pending timers and wait for any pending promise resolution.
await vi.runAllTimersAsync();
expect(processEvent).toHaveBeenCalledTimes(3);
expect(processedEvents).toEqual(['Event 1', 'Event 2', 'Event 3']);
});
it('should handle errors and continue processing', async () => {
const processedEvents: string[] = [];
const processEvent = vi.fn(async (event: string) => {
if (event === 'fail') {
throw new Error('Processing error'); // eslint-disable-line n8n-local-rules/no-plain-errors
}
processedEvents.push(event);
await new Promise((resolve) => setTimeout(resolve, 10));
});
const { enqueue } = createEventQueue<string>(processEvent);
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
enqueue('Event A');
enqueue('fail');
enqueue('Event B');
await vi.runAllTimersAsync();
expect(processEvent).toHaveBeenCalledTimes(3);
// 'fail' should cause an error but processing continues.
expect(processedEvents).toEqual(['Event A', 'Event B']);
expect(consoleSpy).toHaveBeenCalledWith('Error processing event:', expect.any(Error));
consoleSpy.mockRestore();
});
it('should not process any events if none are enqueued', async () => {
const processEvent = vi.fn(async (_event: string) => {
await new Promise((resolve) => setTimeout(resolve, 10));
});
createEventQueue<string>(processEvent);
await vi.runAllTimersAsync();
// Did not enqueue any event.
expect(processEvent).not.toHaveBeenCalled();
});
it('should ensure no concurrent processing of events', async () => {
let processingCounter = 0;
let maxConcurrent = 0;
const processEvent = vi.fn(async (_event: string) => {
processingCounter++;
maxConcurrent = Math.max(maxConcurrent, processingCounter);
// Simulate asynchronous delay.
await new Promise((resolve) => setTimeout(resolve, 20));
processingCounter--;
});
const { enqueue } = createEventQueue<string>(processEvent);
enqueue('A');
enqueue('B');
enqueue('C');
await vi.runAllTimersAsync();
// Throughout processing, maxConcurrent should remain 1.
expect(maxConcurrent).toEqual(1);
});
});

View File

@@ -1,51 +0,0 @@
type RetryFn = () => boolean | Promise<boolean>;
/**
* A utility that retries a function every `interval` milliseconds
* until the function returns true or the maximum number of retries is reached.
*
* @param fn - A function that returns a boolean or a Promise resolving to a boolean.
* @param interval - The time interval (in milliseconds) between each retry. Defaults to 1000.
* @param maxRetries - The maximum number of retry attempts. Defaults to 3.
* @param backoff - The backoff strategy to use: 'linear', 'exponential', or null.
* @returns {Promise<boolean>} - A promise that resolves to:
* - true: If the function returns true before reaching maxRetries.
* - false: If the function never returns true or if an error occurs.
*/
export async function retry(
fn: RetryFn,
interval: number = 1000,
maxRetries: number = 3,
backoff: 'exponential' | 'linear' | null = 'linear',
): Promise<boolean> {
let attempt = 0;
while (attempt < maxRetries) {
attempt++;
try {
const result = await fn();
if (result) {
return true;
}
} catch (error) {
console.error('Error during retry:', error);
throw error;
}
// Wait for the specified interval before the next attempt, if any attempts remain.
if (attempt < maxRetries) {
let computedInterval = interval;
if (backoff === 'linear') {
computedInterval = interval * attempt;
} else if (backoff === 'exponential') {
computedInterval = Math.pow(2, attempt - 1) * interval;
computedInterval = Math.min(computedInterval, 30000); // Cap the maximum interval to 30 seconds
}
await new Promise<void>((resolve) => setTimeout(resolve, computedInterval));
}
}
return false;
}

View File

@@ -1,122 +0,0 @@
import { retry } from './retry';
describe('retry', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
vi.clearAllTimers();
});
it('should resolve true when the function eventually returns true', async () => {
let callCount = 0;
const fn = vi.fn(async () => {
callCount++;
// Return true on the second attempt.
return callCount === 2;
});
const promise = retry(fn, 1000, 2, null);
// The first call happens immediately.
expect(fn).toHaveBeenCalledTimes(1);
// Advance timers by 1000ms asynchronously to allow the waiting period to complete.
await vi.advanceTimersByTimeAsync(1000);
// After advancing, the second attempt should have occurred.
expect(fn).toHaveBeenCalledTimes(2);
// The promise should now resolve with true.
const result = await promise;
expect(result).toBe(true);
});
it('should resolve false if maximum retries are reached with no success', async () => {
let callCount = 0;
const fn = vi.fn(async () => {
callCount++;
return false;
});
const promise = retry(fn, 1000, 3, null);
// The first attempt fires immediately.
expect(fn).toHaveBeenCalledTimes(1);
// Advance timers for the delay after the first attempt.
await vi.advanceTimersByTimeAsync(1000);
expect(fn).toHaveBeenCalledTimes(2);
// Advance timers for the delay after the second attempt.
await vi.advanceTimersByTimeAsync(1000);
expect(fn).toHaveBeenCalledTimes(3);
// With maxRetries reached (3 calls), promise should resolve to false.
const result = await promise;
expect(result).toBe(false);
});
it('should reject if the function throws an error', async () => {
const fn = vi.fn(async () => {
throw new Error('Test error'); // eslint-disable-line n8n-local-rules/no-plain-errors
});
// Since the error is thrown on the first call, no timer advancement is needed.
await expect(retry(fn, 1000, 3, null)).rejects.toThrow('Test error');
expect(fn).toHaveBeenCalledTimes(1);
});
it('should use linear backoff strategy', async () => {
let callCount = 0;
const fn = vi.fn(async () => {
callCount++;
return callCount === 4; // Return true on the fourth attempt.
});
const promise = retry(fn, 1000, 4, 'linear');
expect(fn).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(1000); // First backoff
expect(fn).toHaveBeenCalledTimes(2);
await vi.advanceTimersByTimeAsync(2000); // Second backoff
expect(fn).toHaveBeenCalledTimes(3);
await vi.advanceTimersByTimeAsync(3000); // Third backoff
expect(fn).toHaveBeenCalledTimes(4);
const result = await promise;
expect(result).toBe(true);
});
it('should use exponential backoff strategy', async () => {
let callCount = 0;
const fn = vi.fn(async () => {
callCount++;
return callCount === 5; // Return true on the fifth attempt.
});
const promise = retry(fn, 1000, 5, 'exponential');
expect(fn).toHaveBeenCalledTimes(1);
await vi.advanceTimersByTimeAsync(1000); // First backoff
expect(fn).toHaveBeenCalledTimes(2);
await vi.advanceTimersByTimeAsync(2000); // Second backoff
expect(fn).toHaveBeenCalledTimes(3);
await vi.advanceTimersByTimeAsync(4000); // Third backoff
expect(fn).toHaveBeenCalledTimes(4);
await vi.advanceTimersByTimeAsync(8000); // Fourth backoff
expect(fn).toHaveBeenCalledTimes(5);
const result = await promise;
expect(result).toBe(true);
});
});

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />