diff --git a/api/src/api/context.ts b/api/src/api/context.ts
index 6631dea8f1b..42d06dc9b4e 100644
--- a/api/src/api/context.ts
+++ b/api/src/api/context.ts
@@ -32,8 +32,11 @@ const NOOP_CONTEXT_MANAGER = new NoopContextManager();
export class ContextAPI {
private static _instance?: ContextAPI;
- /** Empty private constructor prevents end users from constructing a new instance of the API */
- private constructor() {}
+ /**
+ * Empty protected constructor prevents end users from constructing a new instance
+ * of the API while allowing experimental API to extend it.
+ */
+ protected constructor() {}
/** Get the singleton instance of the Context API */
public static getInstance(): ContextAPI {
@@ -87,7 +90,7 @@ export class ContextAPI {
return this._getContextManager().bind(context, target);
}
- private _getContextManager(): ContextManager {
+ protected _getContextManager(): ContextManager {
return getGlobal(API_NAME) || NOOP_CONTEXT_MANAGER;
}
diff --git a/api/src/experimental/api/context.ts b/api/src/experimental/api/context.ts
new file mode 100644
index 00000000000..21ecafe5806
--- /dev/null
+++ b/api/src/experimental/api/context.ts
@@ -0,0 +1,106 @@
+import { ContextAPI } from "../../api/context";
+import { Context } from "../../context/types";
+import { ExperimentalContextManager } from "../context/types";
+import { ExperimentalNoopContextManager } from "../context/ExperimentalNoopContextManager";
+
+const NOOP_CONTEXT_MANAGER = new ExperimentalNoopContextManager();
+
+export class ExperimentalContextAPI extends ContextAPI {
+ /**
+ * @experimental this operation should be considered experimental and may make use of experimental APIs.
+ * {@link with} should be preferred over `attach`/{@link detach} unless there are strong reasons to use this method.
+ *
+ * Make a context active in the current execution. Returns a unique restore
+ * key which must be used with detach to restore the previous context.
+ *
+ * The context will remain the active context for the entire asynchronous
+ * execution unless another context is made active by calling `attach`,
+ * {@link with}, or {@link detach}, or if a {@link with} callback ends.
+ *
+ * If `attach` is used within a {@link with} callback, the context which was active
+ * before {@link with} was called will be made the active context when the callback
+ * ends.
+ *
+ * Note that every call to this operation should result in a corresponding call to {@link detach} in the reverse order.
+ *
+ * @example
Example of using context.attach to make context active in a sibling execution
+ *
+ * ```typescript
+ * function func1() {
+ * api.context.attach(ctx1)
+ * }
+ *
+ * function func2() {
+ * api.context.active() // returns ctx1
+ * }
+ *
+ * func1() // ctx1 is made active within this execution
+ * func2() // ctx1 is still active
+ * ```
+ *
+ * @example Example of using context.with to override the context set by context.attach
+ *
+ * ```typescript
+ * function func1() {
+ * api.context.attach(ctx1)
+ * }
+ *
+ * function func2() {
+ * api.context.active() // returns ctx2
+ * }
+ *
+ * func1()
+ * api.context.active() // returns ctx1
+ * api.context.with(ctx2, func2) // run func2 with ctx2 active
+ * api.context.active() // returns ctx1
+ * ```
+ *
+ * @example Example of incorrect use of context.attach inside a context.with callback. This is incorrect because attach is called within a with callback, but there is no corresponding detach within the same callback.
+ *
+ * ```typescript
+ * function foo() {
+ * api.context.active() // returns ctx1
+ * api.context.attach(ctx2) // make ctx2 active
+ * api.context.active() // returns ctx2
+ * }
+ *
+ * api.context.active() // returns root context
+ * api.context.with(ctx1, foo)
+ * api.context.active() // returns root context
+ * ```
+ *
+ * @param context context to make active in the current execution
+ * @returns a restore key
+ */
+ public attach(context: Context): symbol {
+ return this._getContextManager().attach(context);
+ }
+
+ /**
+ * @experimental this operation should be considered experimental and may make use of experimental APIs.
+ * {@link with} should be preferred over {@link attach}/`detach` unless there are strong reasons to use this method.
+ *
+ * Restore the context which was active when attach was called using the restore
+ * token returned by attach.
+ *
+ * @param token the restore token returned by attach
+ */
+ public detach(token: symbol): void {
+ return this._getContextManager().detach(token);
+ }
+
+ /**
+ * @experimental this operation should be considered experimental and may make use of experimental APIs.
+ * Stable context API should be preferred over experimental unless there are strong reasons to use experimental APIs.
+ *
+ * Get experimental context manager. If the currently registered context manager does not have the required
+ * experimental functions, a noop context manager will be returned.
+ */
+ protected override _getContextManager(): ExperimentalContextManager {
+ const manager = super._getContextManager() as any;
+ if (typeof manager["attach"] === "function" && typeof manager["detach"] === "function") {
+ return manager;
+ }
+ return NOOP_CONTEXT_MANAGER;
+ }
+}
\ No newline at end of file
diff --git a/api/src/experimental/context/ExperimentalNoopContextManager.ts b/api/src/experimental/context/ExperimentalNoopContextManager.ts
new file mode 100644
index 00000000000..4e580a90a00
--- /dev/null
+++ b/api/src/experimental/context/ExperimentalNoopContextManager.ts
@@ -0,0 +1,10 @@
+import { NoopContextManager } from "../../context/NoopContextManager";
+import { ExperimentalContextManager } from "./types";
+
+export class ExperimentalNoopContextManager extends NoopContextManager implements ExperimentalContextManager {
+ attach() {
+ return Symbol("NOOP Context Symbol");
+ }
+
+ detach(_token: symbol) {/* nothing */}
+}
\ No newline at end of file
diff --git a/api/src/experimental/context/types.ts b/api/src/experimental/context/types.ts
new file mode 100644
index 00000000000..e234b64c094
--- /dev/null
+++ b/api/src/experimental/context/types.ts
@@ -0,0 +1,25 @@
+import { Context, ContextManager } from "../../context/types"
+
+export interface ExperimentalContextManager extends ContextManager {
+ /**
+ * @experimental this operation should be considered experimental and may make use of experimental APIs.
+ *
+ * Make a context active in the current execution.
+ * For more info, see {@Link ContextAPI.attach}.
+ *
+ * @param context context to make active in the current execution
+ * @returns a restore key
+ */
+ attach(context: Context): symbol;
+
+ /**
+ * @experimental this operation should be considered experimental and may make use of experimental APIs.
+ *
+ * Restore the context which was active when attach was called using the restore
+ * token returned by attach.
+ * For more info, see {@Link ContextAPI.detach}.
+ *
+ * @param token the restore token returned by attach
+ */
+ detach(token: symbol): void;
+}
\ No newline at end of file