Skip to content

Commit e6596af

Browse files
authored
ref(core): Restructure hub exports (#10639)
To make it a bit easier to replace/refactor them in coming steps.
1 parent ef37a80 commit e6596af

36 files changed

+289
-283
lines changed

packages/core/src/asyncContext.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import type { Hub, Integration } from '@sentry/types';
2+
import { GLOBAL_OBJ } from '@sentry/utils';
3+
4+
export interface RunWithAsyncContextOptions {
5+
/** Whether to reuse an existing async context if one exists. Defaults to false. */
6+
reuseExisting?: boolean;
7+
}
8+
9+
/**
10+
* @private Private API with no semver guarantees!
11+
*
12+
* Strategy used to track async context.
13+
*/
14+
export interface AsyncContextStrategy {
15+
/**
16+
* Gets the current async context. Returns undefined if there is no current async context.
17+
*/
18+
getCurrentHub: () => Hub | undefined;
19+
20+
/**
21+
* Runs the supplied callback in its own async context.
22+
*/
23+
runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions): T;
24+
}
25+
26+
/**
27+
* An object that contains a hub and maintains a scope stack.
28+
* @hidden
29+
*/
30+
export interface Carrier {
31+
__SENTRY__?: SentryCarrier;
32+
}
33+
34+
interface SentryCarrier {
35+
hub?: Hub;
36+
acs?: AsyncContextStrategy;
37+
/**
38+
* Extra Hub properties injected by various SDKs
39+
*/
40+
integrations?: Integration[];
41+
extensions?: {
42+
/** Extension methods for the hub, which are bound to the current Hub instance */
43+
// eslint-disable-next-line @typescript-eslint/ban-types
44+
[key: string]: Function;
45+
};
46+
}
47+
48+
/**
49+
* Returns the global shim registry.
50+
*
51+
* FIXME: This function is problematic, because despite always returning a valid Carrier,
52+
* it has an optional `__SENTRY__` property, which then in turn requires us to always perform an unnecessary check
53+
* at the call-site. We always access the carrier through this function, so we can guarantee that `__SENTRY__` is there.
54+
**/
55+
export function getMainCarrier(): Carrier {
56+
// This ensures a Sentry carrier exists
57+
getSentryCarrier(GLOBAL_OBJ);
58+
return GLOBAL_OBJ;
59+
}
60+
61+
/**
62+
* @private Private API with no semver guarantees!
63+
*
64+
* Sets the global async context strategy
65+
*/
66+
export function setAsyncContextStrategy(strategy: AsyncContextStrategy | undefined): void {
67+
// Get main carrier (global for every environment)
68+
const registry = getMainCarrier();
69+
const sentry = getSentryCarrier(registry);
70+
sentry.acs = strategy;
71+
}
72+
73+
/**
74+
* Runs the supplied callback in its own async context. Async Context strategies are defined per SDK.
75+
*
76+
* @param callback The callback to run in its own async context
77+
* @param options Options to pass to the async context strategy
78+
* @returns The result of the callback
79+
*/
80+
export function runWithAsyncContext<T>(callback: () => T, options: RunWithAsyncContextOptions = {}): T {
81+
const registry = getMainCarrier();
82+
const sentry = getSentryCarrier(registry);
83+
84+
if (sentry.acs) {
85+
return sentry.acs.runWithAsyncContext(callback, options);
86+
}
87+
88+
// if there was no strategy, fallback to just calling the callback
89+
return callback();
90+
}
91+
92+
/** Will either get the existing sentry carrier, or create a new one. */
93+
export function getSentryCarrier(carrier: Carrier): SentryCarrier {
94+
if (!carrier.__SENTRY__) {
95+
carrier.__SENTRY__ = {
96+
extensions: {},
97+
hub: undefined,
98+
};
99+
}
100+
return carrier.__SENTRY__;
101+
}

packages/core/src/baseclient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ import {
4747
} from '@sentry/utils';
4848

4949
import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
50+
import { getIsolationScope } from './currentScopes';
5051
import { DEBUG_BUILD } from './debug-build';
5152
import { createEventEnvelope, createSessionEnvelope } from './envelope';
52-
import { getIsolationScope } from './hub';
5353
import type { IntegrationIndex } from './integration';
5454
import { afterSetupIntegrations } from './integration';
5555
import { setupIntegration, setupIntegrations } from './integration';

packages/core/src/breadcrumbs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Breadcrumb, BreadcrumbHint } from '@sentry/types';
22
import { consoleSandbox, dateTimestampInSeconds } from '@sentry/utils';
3+
import { getIsolationScope } from './currentScopes';
34
import { getClient } from './exports';
4-
import { getIsolationScope } from './hub';
55

66
/**
77
* Default maximum number of breadcrumbs added to an event. Can be overwritten

packages/core/src/currentScopes.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { Scope } from '@sentry/types';
2+
import { getCurrentHub } from './hub';
3+
import { Scope as ScopeClass } from './scope';
4+
5+
/**
6+
* The global scope is kept in this module.
7+
* When accessing it, we'll make sure to set one if none is currently present.
8+
*/
9+
let globalScope: Scope | undefined;
10+
11+
/**
12+
* Get the currently active scope.
13+
*/
14+
export function getCurrentScope(): Scope {
15+
// eslint-disable-next-line deprecation/deprecation
16+
return getCurrentHub().getScope();
17+
}
18+
19+
/**
20+
* Get the currently active isolation scope.
21+
* The isolation scope is active for the current exection context.
22+
*/
23+
export function getIsolationScope(): Scope {
24+
// eslint-disable-next-line deprecation/deprecation
25+
return getCurrentHub().getIsolationScope();
26+
}
27+
28+
/**
29+
* Get the global scope.
30+
* This scope is applied to _all_ events.
31+
*/
32+
export function getGlobalScope(): Scope {
33+
if (!globalScope) {
34+
globalScope = new ScopeClass();
35+
}
36+
37+
return globalScope;
38+
}
39+
40+
/**
41+
* This is mainly needed for tests.
42+
* DO NOT USE this, as this is an internal API and subject to change.
43+
* @hidden
44+
*/
45+
export function setGlobalScope(scope: Scope | undefined): void {
46+
globalScope = scope;
47+
}

packages/core/src/exports.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ import type {
2020
User,
2121
} from '@sentry/types';
2222
import { GLOBAL_OBJ, isThenable, logger, timestampInSeconds, uuid4 } from '@sentry/utils';
23+
import { runWithAsyncContext } from './asyncContext';
2324

2425
import { DEFAULT_ENVIRONMENT } from './constants';
26+
import { getCurrentScope, getIsolationScope } from './currentScopes';
2527
import { DEBUG_BUILD } from './debug-build';
2628
import type { Hub } from './hub';
27-
import { runWithAsyncContext } from './hub';
28-
import { getCurrentHub, getIsolationScope } from './hub';
29+
import { getCurrentHub } from './hub';
2930
import type { Scope } from './scope';
3031
import { closeSession, makeSession, updateSession } from './session';
3132
import type { ExclusiveEventHintOrCaptureContext } from './utils/prepareEvent';
@@ -148,19 +149,21 @@ export function setUser(user: User | null): ReturnType<Hub['setUser']> {
148149
* callback();
149150
* popScope();
150151
*/
151-
export function withScope<T>(callback: (scope: Scope) => T): T;
152+
export function withScope<T>(callback: (scope: ScopeInterface) => T): T;
152153
/**
153154
* Set the given scope as the active scope in the callback.
154155
*/
155-
export function withScope<T>(scope: ScopeInterface | undefined, callback: (scope: Scope) => T): T;
156+
export function withScope<T>(scope: ScopeInterface | undefined, callback: (scope: ScopeInterface) => T): T;
156157
/**
157158
* Either creates a new active scope, or sets the given scope as active scope in the given callback.
158159
*/
159160
export function withScope<T>(
160-
...rest: [callback: (scope: Scope) => T] | [scope: ScopeInterface | undefined, callback: (scope: Scope) => T]
161+
...rest:
162+
| [callback: (scope: ScopeInterface) => T]
163+
| [scope: ScopeInterface | undefined, callback: (scope: ScopeInterface) => T]
161164
): T {
162165
// eslint-disable-next-line deprecation/deprecation
163-
const hub = getCurrentHub();
166+
const hub = getCurrentHub() as Hub;
164167

165168
// If a scope is defined, we want to make this the active scope instead of the default one
166169
if (rest.length === 2) {
@@ -196,7 +199,7 @@ export function withScope<T>(
196199
* context strategy, the currently active isolation scope may change within execution of the callback.)
197200
* @returns The same value that `callback` returns.
198201
*/
199-
export function withIsolationScope<T>(callback: (isolationScope: Scope) => T): T {
202+
export function withIsolationScope<T>(callback: (isolationScope: ScopeInterface) => T): T {
200203
return runWithAsyncContext(() => {
201204
return callback(getIsolationScope());
202205
});
@@ -209,7 +212,7 @@ export function withIsolationScope<T>(callback: (isolationScope: Scope) => T): T
209212
* @param callback Execution context in which the provided span will be active. Is passed the newly forked scope.
210213
* @returns the value returned from the provided callback function.
211214
*/
212-
export function withActiveSpan<T>(span: Span, callback: (scope: Scope) => T): T {
215+
export function withActiveSpan<T>(span: Span, callback: (scope: ScopeInterface) => T): T {
213216
return withScope(scope => {
214217
// eslint-disable-next-line deprecation/deprecation
215218
scope.setSpan(span);
@@ -359,14 +362,6 @@ export function isInitialized(): boolean {
359362
return !!getClient();
360363
}
361364

362-
/**
363-
* Get the currently active scope.
364-
*/
365-
export function getCurrentScope(): Scope {
366-
// eslint-disable-next-line deprecation/deprecation
367-
return getCurrentHub().getScope();
368-
}
369-
370365
/**
371366
* Add an event processor.
372367
* This will be added to the current isolation scope, ensuring any event that is processed in the current execution

0 commit comments

Comments
 (0)