From c3d8815e40e6e2660bc8145711b40d51450abb2f Mon Sep 17 00:00:00 2001 From: ybzky Date: Wed, 30 Oct 2024 17:02:42 +0800 Subject: [PATCH 1/2] feat: add async interceptor --- packages/core/src/common/interceptor.ts | 95 ++++++++++++++++++++++++- packages/core/src/index.ts | 2 +- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/packages/core/src/common/interceptor.ts b/packages/core/src/common/interceptor.ts index 9c726723673..4f146f26386 100644 --- a/packages/core/src/common/interceptor.ts +++ b/packages/core/src/common/interceptor.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { remove } from './array'; import type { Nullable } from '../shared/types'; +import { remove } from './array'; export type InterceptorHandler = ( value: Nullable, @@ -118,3 +118,96 @@ export class InterceptorManager

> this._interceptorsByName.clear(); } } + +export function createAsyncInterceptorKey(key: string): IAsyncInterceptor { + const symbol = `sheet_interceptor_${key}`; + return symbol as unknown as IAsyncInterceptor; // FIXME: priority and handler is completely missing? +}; + +export type AsyncInterceptorHandler = ( + value: Nullable, + context: C, + next?: (value: Nullable) => Promise> +) => Promise>; + +export interface IAsyncInterceptor { + priority?: number; + handler: AsyncInterceptorHandler; +} + +export type IComposeAsyncInterceptors = ( + interceptors: Array> +) => (initValue: Nullable, initContext: C) => Promise>; + +export const composeAsyncInterceptors = ( + interceptors: Array> +): ((initialValue: Nullable, context: C) => Promise>) => { + return async function (initialValue: Nullable, context: C) { + let index = -1; + let value: Nullable = initialValue; + + for (let i = 0; i <= interceptors.length; i++) { + if (i <= index) { + throw new Error('[SheetInterceptorService]: next() called multiple times!'); + } + + index = i; + + if (i === interceptors.length) { + return value; + } + + const interceptor = interceptors[i]; + let nextCalled = false; + + value = await interceptor.handler!(value, context, async (nextValue) => { + nextCalled = true; + return nextValue; + }); + + if (!nextCalled) { + break; + } + } + + return value; + }; +}; + +export class AsyncInterceptorManager

>> { + private _asyncInterceptorsByName: Map>> = new Map(); + private _asyncInterceptorPoints: P; + + constructor(asyncInterceptorPoints: P) { + this._asyncInterceptorPoints = asyncInterceptorPoints; + } + + public fetchThroughAsyncInterceptors(name: IAsyncInterceptor) { + const key = name as unknown as string; + const interceptors = this._asyncInterceptorsByName.get(key) as unknown as Array; + return composeAsyncInterceptors(interceptors || []); + } + + public async interceptAsync>(name: T, interceptor: T) { + const key = name as unknown as string; + if (!this._asyncInterceptorsByName.has(key)) { + this._asyncInterceptorsByName.set(key, []); + } + const interceptors = this._asyncInterceptorsByName.get(key)!; + interceptors.push(interceptor); + + this._asyncInterceptorsByName.set( + key, + interceptors.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)) // from large to small + ); + return () => remove(this._asyncInterceptorsByName.get(key)!, interceptor); + } + + public getInterceptPoints() { + return this._asyncInterceptorPoints; + } + + public dispose() { + this._asyncInterceptorsByName.clear(); + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8135d99ee24..79f6ace1907 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -36,7 +36,7 @@ export { shallowEqual } from './common/equal'; export { CustomCommandExecutionError } from './common/error'; export { throttle } from './common/function'; export type { ICellInterceptor, IComposeInterceptors, IInterceptor, InterceptorHandler } from './common/interceptor'; -export { composeInterceptors, createInterceptorKey, InterceptorEffectEnum, InterceptorManager } from './common/interceptor'; +export { AsyncInterceptorManager, composeInterceptors, createAsyncInterceptorKey, createInterceptorKey, InterceptorEffectEnum, InterceptorManager } from './common/interceptor'; export type { Serializable } from './common/json'; export { MemoryCursor } from './common/memory-cursor'; export { mixinClass } from './common/mixin'; From 77f3e43c06e619ff98a35410701a653881bc4e31 Mon Sep 17 00:00:00 2001 From: ybzky Date: Wed, 30 Oct 2024 17:43:40 +0800 Subject: [PATCH 2/2] test: add test --- .../src/common/__tests__/interceptor.spec.ts | 64 +++++++++++++++++++ packages/core/src/common/interceptor.ts | 6 +- 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 packages/core/src/common/__tests__/interceptor.spec.ts diff --git a/packages/core/src/common/__tests__/interceptor.spec.ts b/packages/core/src/common/__tests__/interceptor.spec.ts new file mode 100644 index 00000000000..b2cd1eca7e5 --- /dev/null +++ b/packages/core/src/common/__tests__/interceptor.spec.ts @@ -0,0 +1,64 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; +import { AsyncInterceptorManager, createAsyncInterceptorKey } from '../interceptor'; + +describe('Test interceptor', () => { + it('should return "true" when each interceptor returns true ', async () => { + const INTERCEPTOR_KEY = createAsyncInterceptorKey('test'); + const interceptor = new AsyncInterceptorManager({ INTERCEPTOR_KEY }); + + interceptor.interceptAsync(interceptor.getInterceptPoints().INTERCEPTOR_KEY, { + priority: 0, + handler: async (value, context, next) => { + return await next(value); + }, + }); + + interceptor.interceptAsync(interceptor.getInterceptPoints().INTERCEPTOR_KEY, { + priority: 10, + handler: async (value, context, next) => { + return await next(true); + }, + }); + + const interceptorRes = await interceptor.fetchThroughAsyncInterceptors(INTERCEPTOR_KEY)(false, true); + expect(interceptorRes).toBe(true); + }); + + it('should return "false" when one of the interceptor returns false', async () => { + const INTERCEPTOR_KEY = createAsyncInterceptorKey('test'); + const interceptor = new AsyncInterceptorManager({ INTERCEPTOR_KEY }); + + interceptor.interceptAsync(interceptor.getInterceptPoints().INTERCEPTOR_KEY, { + priority: 0, + handler: async (value, context, next) => { + return await next(value); + }, + }); + + interceptor.interceptAsync(interceptor.getInterceptPoints().INTERCEPTOR_KEY, { + priority: 10, + handler: async (value, context, next) => { + return await next(false); + }, + }); + + const interceptorRes = await interceptor.fetchThroughAsyncInterceptors(INTERCEPTOR_KEY)(false, true); + expect(interceptorRes).toBe(false); + }); +}); diff --git a/packages/core/src/common/interceptor.ts b/packages/core/src/common/interceptor.ts index 4f146f26386..d3c4120ed9b 100644 --- a/packages/core/src/common/interceptor.ts +++ b/packages/core/src/common/interceptor.ts @@ -120,14 +120,14 @@ export class InterceptorManager

> } export function createAsyncInterceptorKey(key: string): IAsyncInterceptor { - const symbol = `sheet_interceptor_${key}`; - return symbol as unknown as IAsyncInterceptor; // FIXME: priority and handler is completely missing? + const symbol = `sheet_async_interceptor_${key}`; + return symbol as unknown as IAsyncInterceptor; }; export type AsyncInterceptorHandler = ( value: Nullable, context: C, - next?: (value: Nullable) => Promise> + next: (value: Nullable) => Promise> ) => Promise>; export interface IAsyncInterceptor {