feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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, vi } from 'vitest';
|
||||
|
||||
import { Deferred, sleep } from '../src/async';
|
||||
|
||||
it('sleep', async () => {
|
||||
vi.useFakeTimers();
|
||||
let count = 0;
|
||||
sleep(1000).then(() => (count = 1));
|
||||
vi.runAllTimers();
|
||||
expect(count).toBe(0);
|
||||
await Promise.resolve();
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
describe('test deferred', () => {
|
||||
it('works', async () => {
|
||||
const deferred = new Deferred<number>();
|
||||
deferred.resolve(1);
|
||||
expect(await deferred.promise).toBe(1);
|
||||
});
|
||||
|
||||
it('reject', async () => {
|
||||
const deferred = new Deferred();
|
||||
deferred.reject(123);
|
||||
try {
|
||||
await deferred.promise;
|
||||
} catch (err) {
|
||||
expect(err).toBe(123);
|
||||
}
|
||||
});
|
||||
|
||||
it('perform like promise', async () => {
|
||||
const deferred = new Deferred<string>();
|
||||
deferred.resolve('1');
|
||||
expect(await deferred).toBe('1');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 {
|
||||
exhaustiveCheckSimple,
|
||||
exhaustiveCheckForRecord,
|
||||
} from '../src/exhaustive-check';
|
||||
|
||||
it('works', () => {
|
||||
const obj = { a: 1 };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars -- .
|
||||
const { a, ...rest } = obj;
|
||||
exhaustiveCheckForRecord(rest);
|
||||
type N = 1;
|
||||
const n: N = 1;
|
||||
switch (n) {
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
exhaustiveCheckSimple(n);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 { expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
sortInt64CompareFn,
|
||||
getIsDiffWithinRange,
|
||||
getMinMax,
|
||||
compareInt64,
|
||||
getInt64AbsDifference,
|
||||
} from '../src/int64';
|
||||
|
||||
it('正确排序', () => {
|
||||
expect(['123', '3', '02', '01234'].sort(sortInt64CompareFn)).toMatchObject([
|
||||
'02',
|
||||
'3',
|
||||
'123',
|
||||
'01234',
|
||||
]);
|
||||
});
|
||||
|
||||
it('计算两个数字差值小于范围', () => {
|
||||
expect(getIsDiffWithinRange('1234567', '12345678923456745678', 50)).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
getIsDiffWithinRange('12345678923456745679', '12345678923456745678', 50),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('get min max', () => {
|
||||
expect(getMinMax('1', '3', '2', '5')).toMatchObject({ min: '1', max: '5' });
|
||||
expect(getMinMax('3', '2', '5')).toMatchObject({ min: '2', max: '5' });
|
||||
expect(getMinMax('3', '2', '1')).toMatchObject({ min: '1', max: '3' });
|
||||
expect(getMinMax('3')).toMatchObject({ min: '3', max: '3' });
|
||||
expect(getMinMax()).toBeNull();
|
||||
});
|
||||
|
||||
it('compare right', () => {
|
||||
expect(compareInt64('1').greaterThan('0')).toBe(true);
|
||||
expect(compareInt64('1').lesserThan('10')).toBe(true);
|
||||
expect(compareInt64('1').eq('1')).toBe(true);
|
||||
});
|
||||
|
||||
it('get diff right', () => {
|
||||
expect(getInt64AbsDifference('10', '200')).toBe(190);
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
typeSafeJsonParse,
|
||||
typeSafeJsonParseEnhanced,
|
||||
} from '../src/json-parse';
|
||||
|
||||
it('simple, parse json', () => {
|
||||
const items = [1, true, [], { a: 1 }];
|
||||
const empty = () => undefined;
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[0]), empty)).toBe(1);
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[1]), empty)).toBe(true);
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[2]), empty)).toMatchObject(
|
||||
items[2],
|
||||
);
|
||||
expect(typeSafeJsonParse(JSON.stringify(items[3]), empty)).toMatchObject(
|
||||
items[3],
|
||||
);
|
||||
});
|
||||
|
||||
it('simple, trigger error', () => {
|
||||
const onErr = vi.fn();
|
||||
expect(typeSafeJsonParse('a', onErr)).toBeNull();
|
||||
expect(onErr.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('enhanced, parse json', () => {
|
||||
const items = [1, true, [], { a: 1 }];
|
||||
const getEmpty = () => ({
|
||||
onVerifyError: () => undefined,
|
||||
onParseError: () => undefined,
|
||||
verifyStruct: (sth: unknown): sth is unknown => true,
|
||||
});
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[0]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toBe(1);
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[1]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[2]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toMatchObject(items[2]);
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: JSON.stringify(items[3]),
|
||||
...getEmpty(),
|
||||
}),
|
||||
).toMatchObject(items[3]);
|
||||
});
|
||||
|
||||
it('enhanced, trigger parse error', () => {
|
||||
const onParseError = vi.fn();
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: 'ax',
|
||||
onParseError,
|
||||
onVerifyError: () => undefined,
|
||||
verifyStruct: (sth): sth is unknown => true,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(onParseError.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('enhanced, catch verify not pass error', () => {
|
||||
const onVerifyError = vi.fn();
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: 'ax',
|
||||
onParseError: () => undefined,
|
||||
onVerifyError,
|
||||
verifyStruct: (sth): sth is unknown => false,
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(onVerifyError.mock.calls.length).toBe(1);
|
||||
expect(onVerifyError.mock.calls[0][0]).toMatchObject({
|
||||
message: 'verify struct no pass',
|
||||
});
|
||||
});
|
||||
|
||||
it('enhanced, catch verify broken', () => {
|
||||
const onVerifyError = vi.fn();
|
||||
expect(
|
||||
typeSafeJsonParseEnhanced({
|
||||
str: 'ax',
|
||||
onParseError: () => undefined,
|
||||
onVerifyError,
|
||||
verifyStruct: (sth): sth is unknown => {
|
||||
const obj = Object(null);
|
||||
return 'x' in obj.x;
|
||||
},
|
||||
}),
|
||||
).toBeNull();
|
||||
expect(onVerifyError.mock.calls.length).toBe(1);
|
||||
expect(onVerifyError.mock.calls[0][0]).toBeInstanceOf(TypeError);
|
||||
expect(onVerifyError.mock.calls[0][0].message).toEqual(
|
||||
expect.stringContaining("Cannot use 'in' operator"),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 { expect, it } from 'vitest';
|
||||
|
||||
import { performSimpleObjectTypeCheck } from '../src/perform-simple-type-check';
|
||||
|
||||
it('check simple obj', () => {
|
||||
expect(
|
||||
performSimpleObjectTypeCheck(
|
||||
{
|
||||
a: 1,
|
||||
b: '2',
|
||||
},
|
||||
[
|
||||
['a', 'is-number'],
|
||||
['b', 'is-string'],
|
||||
],
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('not block', () => {
|
||||
expect(performSimpleObjectTypeCheck([], [])).toBe(true);
|
||||
expect(
|
||||
performSimpleObjectTypeCheck(
|
||||
{
|
||||
a: 1,
|
||||
},
|
||||
[],
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
performSimpleObjectTypeCheck(
|
||||
{
|
||||
a: 1,
|
||||
b: '2',
|
||||
},
|
||||
[['a', 'is-string']],
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('only check object', () => {
|
||||
expect(performSimpleObjectTypeCheck(1, [])).toBe(false);
|
||||
expect(performSimpleObjectTypeCheck('1', [])).toBe(false);
|
||||
expect(performSimpleObjectTypeCheck(null, [])).toBe(false);
|
||||
expect(performSimpleObjectTypeCheck(undefined, [])).toBe(false);
|
||||
});
|
||||
|
||||
it('check key exists', () => {
|
||||
expect(performSimpleObjectTypeCheck({}, [['a', 'is-string']])).toBe(false);
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import { RateLimit } from '../src/rate-limit';
|
||||
|
||||
it('limit rate', async () => {
|
||||
vi.useFakeTimers();
|
||||
const request = vi.fn();
|
||||
const limiter = new RateLimit(request, {
|
||||
limit: 3,
|
||||
onLimitDelay: 1000,
|
||||
timeWindow: 5000,
|
||||
});
|
||||
for (const i of [1, 2, 3, 4, 5]) {
|
||||
limiter.invoke(i);
|
||||
}
|
||||
expect(request.mock.calls.length).toBe(3);
|
||||
// 1000
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(request.mock.calls.length).toBe(4);
|
||||
// 2000
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(request.mock.calls.length).toBe(5);
|
||||
// 3000
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
limiter.invoke();
|
||||
limiter.invoke();
|
||||
// 3010
|
||||
await vi.advanceTimersByTimeAsync(10);
|
||||
expect(request.mock.calls.length).toBe(6);
|
||||
// 4010
|
||||
await vi.advanceTimersByTimeAsync(1000);
|
||||
expect(request.mock.calls.length).toBe(7);
|
||||
|
||||
// 离开窗口
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
limiter.invoke();
|
||||
limiter.invoke();
|
||||
limiter.invoke();
|
||||
expect(request.mock.calls.length).toBe(10);
|
||||
// 进入限流
|
||||
limiter.invoke();
|
||||
await vi.advanceTimersByTimeAsync(100);
|
||||
expect(request.mock.calls.length).toBe(10);
|
||||
expect((limiter as unknown as { records: number[] }).records.length).toBe(4);
|
||||
vi.useRealTimers();
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import { safeAsyncThrow } from '../src/safe-async-throw';
|
||||
|
||||
it('throw in IS_DEV_MODE', () => {
|
||||
vi.stubGlobal('IS_PROD', true);
|
||||
vi.stubGlobal('IS_DEV_MODE', true);
|
||||
expect(() => safeAsyncThrow('1')).toThrow();
|
||||
});
|
||||
|
||||
it('do not throw in BUILD env', () => {
|
||||
vi.stubGlobal('IS_DEV_MODE', false);
|
||||
vi.stubGlobal('IS_BOE', false);
|
||||
vi.stubGlobal('IS_PROD', true);
|
||||
vi.stubGlobal('window', {
|
||||
gfdatav1: {
|
||||
canary: 0,
|
||||
},
|
||||
});
|
||||
vi.useFakeTimers();
|
||||
safeAsyncThrow('1');
|
||||
try {
|
||||
vi.runAllTimers();
|
||||
} catch (e) {
|
||||
expect((e as Error).message).toBe('[chat-area] 1');
|
||||
}
|
||||
vi.useRealTimers();
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 { expect, it, vi } from 'vitest';
|
||||
|
||||
import { updateOnlyDefined } from '../src/update-only-defined';
|
||||
|
||||
it('update only defined', () => {
|
||||
const updater = vi.fn();
|
||||
updateOnlyDefined(updater, {
|
||||
a: undefined,
|
||||
b: 1,
|
||||
});
|
||||
expect(updater.mock.calls[0][0]).toMatchObject({
|
||||
b: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('do not run updater if item value is only undefined', () => {
|
||||
const updater = vi.fn();
|
||||
updateOnlyDefined(updater, {
|
||||
a: undefined,
|
||||
b: undefined,
|
||||
});
|
||||
expect(updater.mock.calls.length).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user