svelte-ag 1.0.56 → 1.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/api/query/query.svelte.d.ts.map +1 -1
- package/dist/lib/api/query/query.svelte.js +2 -6
- package/dist/lib/api/query/rate.svelte.d.ts +5 -0
- package/dist/lib/api/query/rate.svelte.d.ts.map +1 -0
- package/dist/lib/api/query/rate.svelte.js +16 -0
- package/dist/lib/api/query/rate.unit.test.d.ts +2 -0
- package/dist/lib/api/query/rate.unit.test.d.ts.map +1 -0
- package/dist/lib/api/query/rate.unit.test.js +109 -0
- package/package.json +2 -2
- package/src/lib/api/query/query.svelte.ts +3 -7
- package/src/lib/api/query/rate.svelte.ts +20 -0
- package/src/lib/api/query/rate.unit.test.ts +149 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/query/query.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"query.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/query/query.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAGnH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,qBAAa,KAAK,CAChB,GAAG,SAAS,YAAY,EACxB,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EACxB,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC;;gBAyBzC,EACV,IAAI,EACJ,MAAM,EACN,KAAK,EACL,SAAS,EACT,KAAK,EACN,EAAE;QACD,IAAI,EAAE,IAAI,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACnC,SAAS,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,KAAK,EAAE,KAAK,CAAC;QACb,IAAI,CAAC,EAAE;YACL,KAAK,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC1C,CAAC;KACH;IAgBK,OAAO,IAAI,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IA+BxD,IAAI,QAAQ,WAEX;IACD,IAAI,QAAQ,YAEX;IACD,UAAU;IAMV,IAAI,MAAM,gBAET;IACD,IAAI,IAAI,IAAI,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAEnD;IACD,IAAI,SAAS,IAAI,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,IAAI,CAEtD;CACF;AAED,qBAAa,SAAS,CACpB,GAAG,SAAS,YAAY,EACxB,IAAI,SAAS,GAAG,CAAC,MAAM,CAAC,EACxB,MAAM,SAAS,OAAO,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,QAAQ,CAAC;;gBA6BnD,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,kBAAkB,CAAC,GAAG,CAAC,EAChC,MAAM,EAAE,KAAK,EACb,YAAY,CAAC,EAAE,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC;YAelC,KAAK;IAOnB;;;OAGG;YACW,eAAe;IAqBvB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;CAkB3F"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { stringify } from 'devalue';
|
|
2
|
-
import PQueue from 'p-queue';
|
|
3
2
|
import { cacheKey } from './utils.svelte.js';
|
|
3
|
+
import { RateLimiter } from './rate.svelte';
|
|
4
4
|
export class Query {
|
|
5
5
|
// -------- Constants --------
|
|
6
6
|
#TIMEOUT = 1000 * 60 * 5; // 5 minutes
|
|
@@ -97,11 +97,7 @@ export class Requestor {
|
|
|
97
97
|
this.#path = path;
|
|
98
98
|
this.#method = method;
|
|
99
99
|
this.#request = request;
|
|
100
|
-
this.#limiter = new
|
|
101
|
-
concurrency: 5,
|
|
102
|
-
interval: 100,
|
|
103
|
-
intervalCap: 1
|
|
104
|
-
});
|
|
100
|
+
this.#limiter = new RateLimiter();
|
|
105
101
|
// this.#cache = cache;
|
|
106
102
|
// TODO
|
|
107
103
|
this.#canBatch = batchDetails ? batchDetails.canBatch : () => false;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate.svelte.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/query/rate.svelte.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAW;;IAItB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAezC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class RateLimiter {
|
|
2
|
+
#nextStart = 0;
|
|
3
|
+
#interval = 100;
|
|
4
|
+
add(fn) {
|
|
5
|
+
const now = Date.now();
|
|
6
|
+
const startAt = Math.max(now, this.#nextStart);
|
|
7
|
+
this.#nextStart = startAt + this.#interval;
|
|
8
|
+
return (async () => {
|
|
9
|
+
const wait = Math.max(0, startAt - Date.now());
|
|
10
|
+
if (wait > 0) {
|
|
11
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
12
|
+
}
|
|
13
|
+
return await fn();
|
|
14
|
+
})();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate.unit.test.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/query/rate.unit.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { sleep } from 'radash';
|
|
3
|
+
import { RateLimiter } from './rate.svelte.js';
|
|
4
|
+
describe('RateLimiter', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
vi.useFakeTimers();
|
|
7
|
+
vi.setSystemTime(0);
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.useRealTimers();
|
|
11
|
+
vi.restoreAllMocks();
|
|
12
|
+
});
|
|
13
|
+
it('starts the first request immediately', async () => {
|
|
14
|
+
const limiter = new RateLimiter();
|
|
15
|
+
const fn = vi.fn().mockResolvedValue('ok');
|
|
16
|
+
const promise = limiter.add(fn);
|
|
17
|
+
await Promise.resolve();
|
|
18
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
19
|
+
await expect(promise).resolves.toBe('ok');
|
|
20
|
+
});
|
|
21
|
+
it('spaces simultaneous requests by at least 100ms', async () => {
|
|
22
|
+
const limiter = new RateLimiter();
|
|
23
|
+
const starts = [];
|
|
24
|
+
const makeTask = (label) => vi.fn().mockImplementation(async () => {
|
|
25
|
+
starts.push(Date.now());
|
|
26
|
+
await sleep(105);
|
|
27
|
+
return label;
|
|
28
|
+
});
|
|
29
|
+
const p1 = limiter.add(makeTask('a'));
|
|
30
|
+
const p2 = limiter.add(makeTask('b'));
|
|
31
|
+
const p3 = limiter.add(makeTask('c'));
|
|
32
|
+
const p4 = limiter.add(makeTask('d'));
|
|
33
|
+
const p5 = limiter.add(makeTask('e'));
|
|
34
|
+
await Promise.resolve();
|
|
35
|
+
expect(starts).toEqual([0]);
|
|
36
|
+
await vi.advanceTimersByTimeAsync(99);
|
|
37
|
+
expect(starts).toEqual([0]);
|
|
38
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
39
|
+
expect(starts).toEqual([0, 100]);
|
|
40
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
41
|
+
expect(starts).toEqual([0, 100, 200]);
|
|
42
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
43
|
+
expect(starts).toEqual([0, 100, 200, 300]);
|
|
44
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
45
|
+
expect(starts).toEqual([0, 100, 200, 300, 400]);
|
|
46
|
+
await vi.runAllTimersAsync();
|
|
47
|
+
await expect(Promise.all([p1, p2, p3, p4, p5])).resolves.toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
48
|
+
});
|
|
49
|
+
it('does not bunch requests together after waiting', async () => {
|
|
50
|
+
const limiter = new RateLimiter();
|
|
51
|
+
const starts = [];
|
|
52
|
+
const task = () => limiter.add(async () => {
|
|
53
|
+
starts.push(Date.now());
|
|
54
|
+
await sleep(105);
|
|
55
|
+
});
|
|
56
|
+
const p1 = task();
|
|
57
|
+
const p2 = task();
|
|
58
|
+
const p3 = task();
|
|
59
|
+
await Promise.resolve();
|
|
60
|
+
expect(starts).toEqual([0]);
|
|
61
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
62
|
+
expect(starts).toEqual([0, 100]);
|
|
63
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
64
|
+
expect(starts).toEqual([0, 100, 200]);
|
|
65
|
+
await vi.runAllTimersAsync();
|
|
66
|
+
await Promise.all([p1, p2, p3]);
|
|
67
|
+
});
|
|
68
|
+
it('keeps working after a request fails', async () => {
|
|
69
|
+
const limiter = new RateLimiter();
|
|
70
|
+
const starts = [];
|
|
71
|
+
const p1 = limiter.add(async () => {
|
|
72
|
+
starts.push(Date.now());
|
|
73
|
+
await sleep(200);
|
|
74
|
+
throw new Error('boom');
|
|
75
|
+
});
|
|
76
|
+
const p1Expectation = expect(p1).rejects.toThrow('boom');
|
|
77
|
+
const p2 = limiter.add(async () => {
|
|
78
|
+
starts.push(Date.now());
|
|
79
|
+
return 'ok';
|
|
80
|
+
});
|
|
81
|
+
await Promise.resolve();
|
|
82
|
+
expect(starts).toEqual([0]);
|
|
83
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
84
|
+
expect(starts).toEqual([0, 100]);
|
|
85
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
86
|
+
await p1Expectation;
|
|
87
|
+
await expect(p2).resolves.toBe('ok');
|
|
88
|
+
});
|
|
89
|
+
it('only limits start time, not completion time', async () => {
|
|
90
|
+
const limiter = new RateLimiter();
|
|
91
|
+
const starts = [];
|
|
92
|
+
const p1 = limiter.add(async () => {
|
|
93
|
+
starts.push(Date.now());
|
|
94
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
95
|
+
return 'slow';
|
|
96
|
+
});
|
|
97
|
+
const p2 = limiter.add(async () => {
|
|
98
|
+
starts.push(Date.now());
|
|
99
|
+
return 'fast';
|
|
100
|
+
});
|
|
101
|
+
await Promise.resolve();
|
|
102
|
+
expect(starts).toEqual([0]);
|
|
103
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
104
|
+
expect(starts).toEqual([0, 100]);
|
|
105
|
+
await vi.advanceTimersByTimeAsync(900);
|
|
106
|
+
await expect(p2).resolves.toBe('fast');
|
|
107
|
+
await expect(p1).resolves.toBe('slow');
|
|
108
|
+
});
|
|
109
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-ag",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.57",
|
|
4
4
|
"description": "Useful svelte components",
|
|
5
5
|
"bugs": "https://github.com/ageorgeh/svelte-ag/issues",
|
|
6
6
|
"repository": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"lint:fix": "eslint --fix",
|
|
40
40
|
"prepare": "husky",
|
|
41
41
|
"svelte:sync": "svelte-kit sync",
|
|
42
|
+
"test": "vitest --run",
|
|
42
43
|
"test:e2e": "playwright test",
|
|
43
44
|
"test:e2e:ui": "playwright test --ui",
|
|
44
45
|
"test:e2e:update": "playwright test --update-snapshots",
|
|
@@ -53,7 +54,6 @@
|
|
|
53
54
|
"devalue": "^5.6.4",
|
|
54
55
|
"embla-carousel-svelte": "8.6.0",
|
|
55
56
|
"formsnap": "2.0.1",
|
|
56
|
-
"p-queue": "^9.1.0",
|
|
57
57
|
"radash": "12.1.1",
|
|
58
58
|
"runed": "0.37.1",
|
|
59
59
|
"svelte-toolbelt": "^0.10.6",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ApiEndpoints, ApiInput, ApiRequestFunction, ApiSuccessBody, ApiErrorBody, ApiResponse } from 'ts-ag';
|
|
2
2
|
|
|
3
3
|
import { stringify } from 'devalue';
|
|
4
|
-
import PQueue from 'p-queue';
|
|
5
4
|
import type { Cache } from './cache.svelte';
|
|
6
5
|
|
|
7
6
|
import { cacheKey } from './utils.svelte.js';
|
|
8
7
|
import type { BatchDetails } from './entrypoint.svelte';
|
|
8
|
+
import { RateLimiter } from './rate.svelte';
|
|
9
9
|
|
|
10
10
|
export type QueryStatus = 'idle' | 'loading' | 'success' | 'error';
|
|
11
11
|
|
|
@@ -139,7 +139,7 @@ export class Requestor<
|
|
|
139
139
|
#batchInput: BatchDetails<API, Path, Method>['batchInput'];
|
|
140
140
|
#unBatchOutput: BatchDetails<API, Path, Method>['unBatchOutput'];
|
|
141
141
|
|
|
142
|
-
#limiter:
|
|
142
|
+
#limiter: RateLimiter;
|
|
143
143
|
// #cache: Cache;
|
|
144
144
|
|
|
145
145
|
// -------- State --------
|
|
@@ -163,11 +163,7 @@ export class Requestor<
|
|
|
163
163
|
this.#path = path;
|
|
164
164
|
this.#method = method;
|
|
165
165
|
this.#request = request;
|
|
166
|
-
this.#limiter = new
|
|
167
|
-
concurrency: 5,
|
|
168
|
-
interval: 100,
|
|
169
|
-
intervalCap: 1
|
|
170
|
-
});
|
|
166
|
+
this.#limiter = new RateLimiter();
|
|
171
167
|
// this.#cache = cache;
|
|
172
168
|
|
|
173
169
|
// TODO
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class RateLimiter {
|
|
2
|
+
#nextStart = 0;
|
|
3
|
+
#interval = 100;
|
|
4
|
+
|
|
5
|
+
add<T>(fn: () => Promise<T>): Promise<T> {
|
|
6
|
+
const now = Date.now();
|
|
7
|
+
const startAt = Math.max(now, this.#nextStart);
|
|
8
|
+
this.#nextStart = startAt + this.#interval;
|
|
9
|
+
|
|
10
|
+
return (async () => {
|
|
11
|
+
const wait = Math.max(0, startAt - Date.now());
|
|
12
|
+
|
|
13
|
+
if (wait > 0) {
|
|
14
|
+
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return await fn();
|
|
18
|
+
})();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { sleep } from 'radash';
|
|
3
|
+
import { RateLimiter } from './rate.svelte.js';
|
|
4
|
+
|
|
5
|
+
describe('RateLimiter', () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.useFakeTimers();
|
|
8
|
+
vi.setSystemTime(0);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.useRealTimers();
|
|
13
|
+
vi.restoreAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('starts the first request immediately', async () => {
|
|
17
|
+
const limiter = new RateLimiter();
|
|
18
|
+
const fn = vi.fn().mockResolvedValue('ok');
|
|
19
|
+
|
|
20
|
+
const promise = limiter.add(fn);
|
|
21
|
+
|
|
22
|
+
await Promise.resolve();
|
|
23
|
+
|
|
24
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
25
|
+
|
|
26
|
+
await expect(promise).resolves.toBe('ok');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('spaces simultaneous requests by at least 100ms', async () => {
|
|
30
|
+
const limiter = new RateLimiter();
|
|
31
|
+
const starts: number[] = [];
|
|
32
|
+
|
|
33
|
+
const makeTask = (label: string) =>
|
|
34
|
+
vi.fn().mockImplementation(async () => {
|
|
35
|
+
starts.push(Date.now());
|
|
36
|
+
await sleep(105);
|
|
37
|
+
return label;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const p1 = limiter.add(makeTask('a'));
|
|
41
|
+
const p2 = limiter.add(makeTask('b'));
|
|
42
|
+
const p3 = limiter.add(makeTask('c'));
|
|
43
|
+
const p4 = limiter.add(makeTask('d'));
|
|
44
|
+
const p5 = limiter.add(makeTask('e'));
|
|
45
|
+
|
|
46
|
+
await Promise.resolve();
|
|
47
|
+
expect(starts).toEqual([0]);
|
|
48
|
+
|
|
49
|
+
await vi.advanceTimersByTimeAsync(99);
|
|
50
|
+
expect(starts).toEqual([0]);
|
|
51
|
+
|
|
52
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
53
|
+
expect(starts).toEqual([0, 100]);
|
|
54
|
+
|
|
55
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
56
|
+
expect(starts).toEqual([0, 100, 200]);
|
|
57
|
+
|
|
58
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
59
|
+
expect(starts).toEqual([0, 100, 200, 300]);
|
|
60
|
+
|
|
61
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
62
|
+
expect(starts).toEqual([0, 100, 200, 300, 400]);
|
|
63
|
+
|
|
64
|
+
await vi.runAllTimersAsync();
|
|
65
|
+
await expect(Promise.all([p1, p2, p3, p4, p5])).resolves.toEqual(['a', 'b', 'c', 'd', 'e']);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('does not bunch requests together after waiting', async () => {
|
|
69
|
+
const limiter = new RateLimiter();
|
|
70
|
+
const starts: number[] = [];
|
|
71
|
+
|
|
72
|
+
const task = () =>
|
|
73
|
+
limiter.add(async () => {
|
|
74
|
+
starts.push(Date.now());
|
|
75
|
+
await sleep(105);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const p1 = task();
|
|
79
|
+
const p2 = task();
|
|
80
|
+
const p3 = task();
|
|
81
|
+
|
|
82
|
+
await Promise.resolve();
|
|
83
|
+
expect(starts).toEqual([0]);
|
|
84
|
+
|
|
85
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
86
|
+
expect(starts).toEqual([0, 100]);
|
|
87
|
+
|
|
88
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
89
|
+
expect(starts).toEqual([0, 100, 200]);
|
|
90
|
+
|
|
91
|
+
await vi.runAllTimersAsync();
|
|
92
|
+
await Promise.all([p1, p2, p3]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('keeps working after a request fails', async () => {
|
|
96
|
+
const limiter = new RateLimiter();
|
|
97
|
+
const starts: number[] = [];
|
|
98
|
+
|
|
99
|
+
const p1 = limiter.add(async () => {
|
|
100
|
+
starts.push(Date.now());
|
|
101
|
+
await sleep(200);
|
|
102
|
+
throw new Error('boom');
|
|
103
|
+
});
|
|
104
|
+
const p1Expectation = expect(p1).rejects.toThrow('boom');
|
|
105
|
+
|
|
106
|
+
const p2 = limiter.add(async () => {
|
|
107
|
+
starts.push(Date.now());
|
|
108
|
+
return 'ok';
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await Promise.resolve();
|
|
112
|
+
expect(starts).toEqual([0]);
|
|
113
|
+
|
|
114
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
115
|
+
expect(starts).toEqual([0, 100]);
|
|
116
|
+
|
|
117
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
118
|
+
|
|
119
|
+
await p1Expectation;
|
|
120
|
+
await expect(p2).resolves.toBe('ok');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('only limits start time, not completion time', async () => {
|
|
124
|
+
const limiter = new RateLimiter();
|
|
125
|
+
const starts: number[] = [];
|
|
126
|
+
|
|
127
|
+
const p1 = limiter.add(async () => {
|
|
128
|
+
starts.push(Date.now());
|
|
129
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
130
|
+
return 'slow';
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const p2 = limiter.add(async () => {
|
|
134
|
+
starts.push(Date.now());
|
|
135
|
+
return 'fast';
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await Promise.resolve();
|
|
139
|
+
expect(starts).toEqual([0]);
|
|
140
|
+
|
|
141
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
142
|
+
expect(starts).toEqual([0, 100]);
|
|
143
|
+
|
|
144
|
+
await vi.advanceTimersByTimeAsync(900);
|
|
145
|
+
|
|
146
|
+
await expect(p2).resolves.toBe('fast');
|
|
147
|
+
await expect(p1).resolves.toBe('slow');
|
|
148
|
+
});
|
|
149
|
+
});
|