Skip to content

Commit 772655e

Browse files
committed
Merge branch 'develop' into manual-develop-sync
2 parents c0fb319 + cb5265c commit 772655e

File tree

8 files changed

+414
-116
lines changed

8 files changed

+414
-116
lines changed

dev-packages/e2e-tests/test-applications/nextjs-14/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@types/node": "^18.19.1",
1818
"@types/react": "18.0.26",
1919
"@types/react-dom": "18.0.9",
20-
"next": "14.1.3",
20+
"next": "14.2.30",
2121
"react": "18.2.0",
2222
"react-dom": "18.2.0",
2323
"typescript": "~5.0.0"

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export { parseSampleRate } from './utils/parseSampleRate';
8888
export { applySdkMetadata } from './utils/sdkMetadata';
8989
export { getTraceData } from './utils/traceData';
9090
export { getTraceMetaTags } from './utils/meta';
91+
export { debounce } from './utils/debounce';
9192
export {
9293
winterCGHeadersToDict,
9394
winterCGRequestToRequestData,

packages/core/src/utils/debounce.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
type DebouncedCallback = {
2+
(): void | unknown;
3+
flush: () => void | unknown;
4+
cancel: () => void;
5+
};
6+
type CallbackFunction = () => unknown;
7+
type DebounceOptions = {
8+
/** The max. time in ms to wait for the callback to be invoked. */
9+
maxWait?: number;
10+
/** This can be overwritten to use a different setTimeout implementation, e.g. to avoid triggering change detection in Angular */
11+
setTimeoutImpl?: typeof setTimeout;
12+
};
13+
14+
/**
15+
* Heavily simplified debounce function based on lodash.debounce.
16+
*
17+
* This function takes a callback function (@param fun) and delays its invocation
18+
* by @param wait milliseconds. Optionally, a maxWait can be specified in @param options,
19+
* which ensures that the callback is invoked at least once after the specified max. wait time.
20+
*
21+
* @param func the function whose invocation is to be debounced
22+
* @param wait the minimum time until the function is invoked after it was called once
23+
* @param options the options object, which can contain the `maxWait` property
24+
*
25+
* @returns the debounced version of the function, which needs to be called at least once to start the
26+
* debouncing process. Subsequent calls will reset the debouncing timer and, in case @paramfunc
27+
* was already invoked in the meantime, return @param func's return value.
28+
* The debounced function has two additional properties:
29+
* - `flush`: Invokes the debounced function immediately and returns its return value
30+
* - `cancel`: Cancels the debouncing process and resets the debouncing timer
31+
*/
32+
export function debounce(func: CallbackFunction, wait: number, options?: DebounceOptions): DebouncedCallback {
33+
let callbackReturnValue: unknown;
34+
35+
let timerId: ReturnType<typeof setTimeout> | undefined;
36+
let maxTimerId: ReturnType<typeof setTimeout> | undefined;
37+
38+
const maxWait = options?.maxWait ? Math.max(options.maxWait, wait) : 0;
39+
const setTimeoutImpl = options?.setTimeoutImpl || setTimeout;
40+
41+
function invokeFunc(): unknown {
42+
cancelTimers();
43+
callbackReturnValue = func();
44+
return callbackReturnValue;
45+
}
46+
47+
function cancelTimers(): void {
48+
timerId !== undefined && clearTimeout(timerId);
49+
maxTimerId !== undefined && clearTimeout(maxTimerId);
50+
timerId = maxTimerId = undefined;
51+
}
52+
53+
function flush(): unknown {
54+
if (timerId !== undefined || maxTimerId !== undefined) {
55+
return invokeFunc();
56+
}
57+
return callbackReturnValue;
58+
}
59+
60+
function debounced(): unknown {
61+
if (timerId) {
62+
clearTimeout(timerId);
63+
}
64+
timerId = setTimeoutImpl(invokeFunc, wait);
65+
66+
if (maxWait && maxTimerId === undefined) {
67+
maxTimerId = setTimeoutImpl(invokeFunc, maxWait);
68+
}
69+
70+
return callbackReturnValue;
71+
}
72+
73+
debounced.cancel = cancelTimers;
74+
debounced.flush = flush;
75+
return debounced;
76+
}
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import { beforeAll, describe, expect, it, vi } from 'vitest';
2+
import { debounce } from '../../../src/utils/debounce';
3+
4+
describe('Unit | util | debounce', () => {
5+
beforeAll(() => {
6+
vi.useFakeTimers();
7+
});
8+
9+
it('delay the execution of the passed callback function by the passed minDelay', () => {
10+
const callback = vi.fn();
11+
const debouncedCallback = debounce(callback, 100);
12+
debouncedCallback();
13+
expect(callback).not.toHaveBeenCalled();
14+
15+
vi.advanceTimersByTime(99);
16+
expect(callback).not.toHaveBeenCalled();
17+
18+
vi.advanceTimersByTime(1);
19+
expect(callback).toHaveBeenCalled();
20+
});
21+
22+
it('should invoke the callback at latest by maxWait, if the option is specified', () => {
23+
const callback = vi.fn();
24+
const debouncedCallback = debounce(callback, 100, { maxWait: 150 });
25+
debouncedCallback();
26+
expect(callback).not.toHaveBeenCalled();
27+
28+
vi.advanceTimersByTime(98);
29+
expect(callback).not.toHaveBeenCalled();
30+
31+
debouncedCallback();
32+
33+
vi.advanceTimersByTime(1);
34+
expect(callback).not.toHaveBeenCalled();
35+
36+
vi.advanceTimersByTime(49);
37+
// at this time, the callback shouldn't be invoked and with a new call, it should be debounced further.
38+
debouncedCallback();
39+
expect(callback).not.toHaveBeenCalled();
40+
41+
// But because the maxWait is reached, the callback should nevertheless be invoked.
42+
vi.advanceTimersByTime(10);
43+
expect(callback).toHaveBeenCalled();
44+
});
45+
46+
it('should not invoke the callback as long as it is debounced and no maxWait option is specified', () => {
47+
const callback = vi.fn();
48+
const debouncedCallback = debounce(callback, 100);
49+
debouncedCallback();
50+
expect(callback).not.toHaveBeenCalled();
51+
52+
vi.advanceTimersByTime(99);
53+
expect(callback).not.toHaveBeenCalled();
54+
55+
debouncedCallback();
56+
57+
vi.advanceTimersByTime(1);
58+
expect(callback).not.toHaveBeenCalled();
59+
60+
vi.advanceTimersByTime(98);
61+
debouncedCallback();
62+
expect(callback).not.toHaveBeenCalled();
63+
64+
vi.advanceTimersByTime(99);
65+
expect(callback).not.toHaveBeenCalled();
66+
debouncedCallback();
67+
68+
vi.advanceTimersByTime(100);
69+
expect(callback).toHaveBeenCalled();
70+
});
71+
72+
it('should invoke the callback as soon as callback.flush() is called', () => {
73+
const callback = vi.fn();
74+
const debouncedCallback = debounce(callback, 100, { maxWait: 200 });
75+
debouncedCallback();
76+
expect(callback).not.toHaveBeenCalled();
77+
78+
vi.advanceTimersByTime(10);
79+
expect(callback).not.toHaveBeenCalled();
80+
81+
debouncedCallback.flush();
82+
expect(callback).toHaveBeenCalled();
83+
});
84+
85+
it('should not invoke the callback, if callback.cancel() is called', () => {
86+
const callback = vi.fn();
87+
const debouncedCallback = debounce(callback, 100, { maxWait: 200 });
88+
debouncedCallback();
89+
expect(callback).not.toHaveBeenCalled();
90+
91+
vi.advanceTimersByTime(99);
92+
expect(callback).not.toHaveBeenCalled();
93+
94+
// If the callback is canceled, it should not be invoked after the minwait
95+
debouncedCallback.cancel();
96+
vi.advanceTimersByTime(1);
97+
expect(callback).not.toHaveBeenCalled();
98+
99+
// And it should also not be invoked after the maxWait
100+
vi.advanceTimersByTime(500);
101+
expect(callback).not.toHaveBeenCalled();
102+
});
103+
104+
it("should return the callback's return value when calling callback.flush()", () => {
105+
const callback = vi.fn().mockReturnValue('foo');
106+
const debouncedCallback = debounce(callback, 100);
107+
108+
debouncedCallback();
109+
110+
const returnValue = debouncedCallback.flush();
111+
expect(returnValue).toBe('foo');
112+
});
113+
114+
it('should return the callbacks return value on subsequent calls of the debounced function', () => {
115+
const callback = vi.fn().mockReturnValue('foo');
116+
const debouncedCallback = debounce(callback, 100);
117+
118+
const returnValue1 = debouncedCallback();
119+
expect(returnValue1).toBe(undefined);
120+
expect(callback).not.toHaveBeenCalled();
121+
122+
// now we expect the callback to have been invoked
123+
vi.advanceTimersByTime(200);
124+
expect(callback).toHaveBeenCalledTimes(1);
125+
126+
// calling the debounced function now should return the return value of the callback execution
127+
const returnValue2 = debouncedCallback();
128+
expect(returnValue2).toBe('foo');
129+
expect(callback).toHaveBeenCalledTimes(1);
130+
131+
// and the callback should also be invoked again
132+
vi.advanceTimersByTime(200);
133+
expect(callback).toHaveBeenCalledTimes(2);
134+
});
135+
136+
it('should handle return values of consecutive invocations without maxWait', () => {
137+
let i = 0;
138+
const callback = vi.fn().mockImplementation(() => {
139+
return `foo-${++i}`;
140+
});
141+
const debouncedCallback = debounce(callback, 100);
142+
143+
const returnValue0 = debouncedCallback();
144+
expect(returnValue0).toBe(undefined);
145+
expect(callback).not.toHaveBeenCalled();
146+
147+
// now we expect the callback to have been invoked
148+
vi.advanceTimersByTime(200);
149+
expect(callback).toHaveBeenCalledTimes(1);
150+
151+
// calling the debounced function now should return the return value of the callback execution
152+
const returnValue1 = debouncedCallback();
153+
expect(returnValue1).toBe('foo-1');
154+
expect(callback).toHaveBeenCalledTimes(1);
155+
156+
vi.advanceTimersByTime(1);
157+
const returnValue2 = debouncedCallback();
158+
expect(returnValue2).toBe('foo-1');
159+
expect(callback).toHaveBeenCalledTimes(1);
160+
161+
// and the callback should also be invoked again
162+
vi.advanceTimersByTime(200);
163+
const returnValue3 = debouncedCallback();
164+
expect(returnValue3).toBe('foo-2');
165+
expect(callback).toHaveBeenCalledTimes(2);
166+
});
167+
168+
it('should handle return values of consecutive invocations with maxWait', () => {
169+
let i = 0;
170+
const callback = vi.fn().mockImplementation(() => {
171+
return `foo-${++i}`;
172+
});
173+
const debouncedCallback = debounce(callback, 150, { maxWait: 200 });
174+
175+
const returnValue0 = debouncedCallback();
176+
expect(returnValue0).toBe(undefined);
177+
expect(callback).not.toHaveBeenCalled();
178+
179+
// now we expect the callback to have been invoked
180+
vi.advanceTimersByTime(149);
181+
const returnValue1 = debouncedCallback();
182+
expect(returnValue1).toBe(undefined);
183+
expect(callback).not.toHaveBeenCalled();
184+
185+
// calling the debounced function now should return the return value of the callback execution
186+
// as it was executed because of maxWait
187+
vi.advanceTimersByTime(51);
188+
const returnValue2 = debouncedCallback();
189+
expect(returnValue2).toBe('foo-1');
190+
expect(callback).toHaveBeenCalledTimes(1);
191+
192+
// at this point (100ms after the last debounce call), nothing should have happened
193+
vi.advanceTimersByTime(100);
194+
const returnValue3 = debouncedCallback();
195+
expect(returnValue3).toBe('foo-1');
196+
expect(callback).toHaveBeenCalledTimes(1);
197+
198+
// and the callback should now have been invoked again
199+
vi.advanceTimersByTime(150);
200+
const returnValue4 = debouncedCallback();
201+
expect(returnValue4).toBe('foo-2');
202+
expect(callback).toHaveBeenCalledTimes(2);
203+
});
204+
205+
it('should handle return values of consecutive invocations after a cancellation', () => {
206+
let i = 0;
207+
const callback = vi.fn().mockImplementation(() => {
208+
return `foo-${++i}`;
209+
});
210+
const debouncedCallback = debounce(callback, 150, { maxWait: 200 });
211+
212+
const returnValue0 = debouncedCallback();
213+
expect(returnValue0).toBe(undefined);
214+
expect(callback).not.toHaveBeenCalled();
215+
216+
// now we expect the callback to have been invoked
217+
vi.advanceTimersByTime(149);
218+
const returnValue1 = debouncedCallback();
219+
expect(returnValue1).toBe(undefined);
220+
expect(callback).not.toHaveBeenCalled();
221+
222+
debouncedCallback.cancel();
223+
224+
// calling the debounced function now still return undefined because we cancelled the invocation
225+
vi.advanceTimersByTime(51);
226+
const returnValue2 = debouncedCallback();
227+
expect(returnValue2).toBe(undefined);
228+
expect(callback).not.toHaveBeenCalled();
229+
230+
// and the callback should also be invoked again
231+
vi.advanceTimersByTime(150);
232+
const returnValue3 = debouncedCallback();
233+
expect(returnValue3).toBe('foo-1');
234+
expect(callback).toHaveBeenCalledTimes(1);
235+
});
236+
237+
it('should handle the return value of calling flush after cancelling', () => {
238+
const callback = vi.fn().mockReturnValue('foo');
239+
const debouncedCallback = debounce(callback, 100);
240+
241+
debouncedCallback();
242+
debouncedCallback.cancel();
243+
244+
const returnValue = debouncedCallback.flush();
245+
expect(returnValue).toBe(undefined);
246+
});
247+
248+
it('should handle equal wait and maxWait values and only invoke func once', () => {
249+
const callback = vi.fn().mockReturnValue('foo');
250+
const debouncedCallback = debounce(callback, 100, { maxWait: 100 });
251+
252+
debouncedCallback();
253+
vi.advanceTimersByTime(25);
254+
debouncedCallback();
255+
vi.advanceTimersByTime(25);
256+
debouncedCallback();
257+
vi.advanceTimersByTime(25);
258+
debouncedCallback();
259+
vi.advanceTimersByTime(25);
260+
261+
expect(callback).toHaveBeenCalledTimes(1);
262+
263+
const retval = debouncedCallback();
264+
expect(retval).toBe('foo');
265+
266+
vi.advanceTimersByTime(25);
267+
debouncedCallback();
268+
vi.advanceTimersByTime(25);
269+
debouncedCallback();
270+
vi.advanceTimersByTime(25);
271+
debouncedCallback();
272+
vi.advanceTimersByTime(25);
273+
274+
expect(callback).toHaveBeenCalledTimes(2);
275+
});
276+
});

packages/node/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,9 @@
9494
"@opentelemetry/resources": "^1.30.1",
9595
"@opentelemetry/sdk-trace-base": "^1.30.1",
9696
"@opentelemetry/semantic-conventions": "^1.34.0",
97-
"@prisma/instrumentation": "6.8.2",
9897
"@sentry/core": "9.30.0",
9998
"@sentry/opentelemetry": "9.30.0",
99+
"@prisma/instrumentation": "6.9.0",
100100
"import-in-the-middle": "^1.13.1",
101101
"minimatch": "^9.0.0"
102102
},

0 commit comments

Comments
 (0)