diff --git a/package.json b/package.json
index 8c50e83c69b0..6ae7e4f1d2cf 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"postpublish": "lerna run --stream --concurrency 1 postpublish",
"test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test",
"test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test:unit",
- "test-ci-browser": "lerna run test --ignore \"@sentry/{bun,node,node-experimental,opentelemetry-node,serverless,nextjs,remix,gatsby,sveltekit}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"",
+ "test-ci-browser": "lerna run test --ignore \"@sentry/{bun,node,node-experimental,opentelemetry-node,serverless,nextjs,remix,gatsby,sveltekit,vercel-edge}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"",
"test-ci-node": "ts-node ./scripts/node-unit-tests.ts",
"test-ci-bun": "lerna run test --scope @sentry/bun",
"test:update-snapshots": "lerna run test:update-snapshots",
@@ -70,6 +70,7 @@
"packages/types",
"packages/typescript",
"packages/utils",
+ "packages/vercel-edge",
"packages/vue",
"packages/wasm"
],
diff --git a/packages/e2e-tests/verdaccio-config/config.yaml b/packages/e2e-tests/verdaccio-config/config.yaml
index 05895a1adbed..80a5afc70008 100644
--- a/packages/e2e-tests/verdaccio-config/config.yaml
+++ b/packages/e2e-tests/verdaccio-config/config.yaml
@@ -164,6 +164,12 @@ packages:
unpublish: $all
# proxy: npmjs # Don't proxy for E2E tests!
+ '@sentry/vercel-edge':
+ access: $all
+ publish: $all
+ unpublish: $all
+ # proxy: npmjs # Don't proxy for E2E tests!
+
'@sentry/vue':
access: $all
publish: $all
diff --git a/packages/vercel-edge/.eslintrc.js b/packages/vercel-edge/.eslintrc.js
new file mode 100644
index 000000000000..bec6469d0e28
--- /dev/null
+++ b/packages/vercel-edge/.eslintrc.js
@@ -0,0 +1,12 @@
+module.exports = {
+ env: {
+ node: true,
+ },
+ extends: ['../../.eslintrc.js'],
+ rules: {
+ '@sentry-internal/sdk/no-optional-chaining': 'off',
+ '@sentry-internal/sdk/no-nullish-coalescing': 'off',
+ '@sentry-internal/sdk/no-unsupported-es6-methods': 'off',
+ '@sentry-internal/sdk/no-class-field-initializers': 'off',
+ },
+};
diff --git a/packages/vercel-edge/LICENSE b/packages/vercel-edge/LICENSE
new file mode 100644
index 000000000000..d11896ba1181
--- /dev/null
+++ b/packages/vercel-edge/LICENSE
@@ -0,0 +1,14 @@
+Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/vercel-edge/README.md b/packages/vercel-edge/README.md
new file mode 100644
index 000000000000..d3e7849ffdab
--- /dev/null
+++ b/packages/vercel-edge/README.md
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+# Official Sentry SDK for Vercel Edge Runtime [ALPHA]
+
+[![npm version](https://img.shields.io/npm/v/@sentry/vercel-edge.svg)](https://www.npmjs.com/package/@sentry/vercel-edge)
+[![npm dm](https://img.shields.io/npm/dm/@sentry/vercel-edge.svg)](https://www.npmjs.com/package/@sentry/vercel-edge)
+[![npm dt](https://img.shields.io/npm/dt/@sentry/vercel-edge.svg)](https://www.npmjs.com/package/@sentry/vercel-edge)
+
+## Links
+
+- [Official SDK Docs](https://docs.sentry.io/quickstart/)
+- [TypeDoc](http://getsentry.github.io/sentry-javascript/)
+
+**Note: This SDK is still in an alpha state. Breaking changes can occur at any time.**
+
+## Usage
+
+To use this SDK, call `init(options)` as early as possible in the main entry module. This will initialize the SDK and
+hook into the environment. Note that you can turn off almost all side effects using the respective options.
+
+```javascript
+// ES5 Syntax
+const Sentry = require('@sentry/vercel-edge');
+// ES6 Syntax
+import * as Sentry from '@sentry/vercel-edge';
+
+Sentry.init({
+ dsn: '__DSN__',
+ // ...
+});
+```
+
+To set context information or send manual events, use the exported functions of `@sentry/vercel-edge`. Note that these
+functions will not perform any action before you have called `init()`:
+
+```javascript
+// Set user information, as well as tags and further extras
+Sentry.configureScope(scope => {
+ scope.setExtra('battery', 0.7);
+ scope.setTag('user_mode', 'admin');
+ scope.setUser({ id: '4711' });
+ // scope.clear();
+});
+
+// Add a breadcrumb for future events
+Sentry.addBreadcrumb({
+ message: 'My Breadcrumb',
+ // ...
+});
+
+// Capture exceptions, messages or manual events
+Sentry.captureMessage('Hello, world!');
+Sentry.captureException(new Error('Good bye'));
+Sentry.captureEvent({
+ message: 'Manual',
+ stacktrace: [
+ // ...
+ ],
+});
+```
diff --git a/packages/vercel-edge/jest.config.js b/packages/vercel-edge/jest.config.js
new file mode 100644
index 000000000000..dfc8f746b929
--- /dev/null
+++ b/packages/vercel-edge/jest.config.js
@@ -0,0 +1,7 @@
+const baseConfig = require('../../jest/jest.config.js');
+
+module.exports = {
+ ...baseConfig,
+ // TODO: Fix tests to work with the Edge environment
+ // testEnvironment: '@edge-runtime/jest-environment',
+};
diff --git a/packages/vercel-edge/package.json b/packages/vercel-edge/package.json
new file mode 100644
index 000000000000..73559218598a
--- /dev/null
+++ b/packages/vercel-edge/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "@sentry/vercel-edge",
+ "version": "7.69.0",
+ "description": "Offical Sentry SDK for the Vercel Edge Runtime",
+ "repository": "git://github.com/getsentry/sentry-javascript.git",
+ "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/vercel-edge",
+ "author": "Sentry",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "main": "build/cjs/index.js",
+ "module": "build/esm/index.js",
+ "types": "build/types/index.d.ts",
+ "typesVersions": {
+ "<4.9": {
+ "build/types/index.d.ts": [
+ "build/types-ts3.8/index.d.ts"
+ ]
+ }
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "dependencies": {
+ "@sentry/core": "7.69.0",
+ "@sentry/types": "7.69.0",
+ "@sentry/utils": "7.69.0",
+ "tslib": "^2.4.1 || ^1.9.3"
+ },
+ "devDependencies": {
+ "@edge-runtime/jest-environment": "2.2.3",
+ "@edge-runtime/types": "2.2.3"
+ },
+ "scripts": {
+ "build": "run-p build:transpile build:types",
+ "build:dev": "yarn build",
+ "build:transpile": "rollup -c rollup.npm.config.js",
+ "build:types": "run-s build:types:core build:types:downlevel",
+ "build:types:core": "tsc -p tsconfig.types.json",
+ "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8",
+ "build:watch": "run-p build:transpile:watch build:types:watch",
+ "build:dev:watch": "yarn build:watch",
+ "build:transpile:watch": "rollup -c rollup.npm.config.js --watch",
+ "build:types:watch": "tsc -p tsconfig.types.json --watch",
+ "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build",
+ "circularDepCheck": "madge --circular src/index.ts",
+ "clean": "rimraf build coverage sentry-core-*.tgz",
+ "fix": "run-s fix:eslint fix:prettier",
+ "fix:eslint": "eslint . --format stylish --fix",
+ "fix:prettier": "prettier --write \"{src,test,scripts}/**/**.ts\"",
+ "lint": "run-s lint:prettier lint:eslint",
+ "lint:eslint": "eslint . --format stylish",
+ "lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"",
+ "test": "jest",
+ "test:watch": "jest --watch",
+ "version": "node ../../scripts/versionbump.js src/version.ts",
+ "yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push"
+ },
+ "volta": {
+ "extends": "../../package.json"
+ },
+ "sideEffects": false,
+ "madge":{
+ "detectiveOptions": {
+ "ts": {
+ "skipTypeImports": true
+ }
+ }
+ }
+}
diff --git a/packages/vercel-edge/rollup.npm.config.js b/packages/vercel-edge/rollup.npm.config.js
new file mode 100644
index 000000000000..5a62b528ef44
--- /dev/null
+++ b/packages/vercel-edge/rollup.npm.config.js
@@ -0,0 +1,3 @@
+import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js';
+
+export default makeNPMConfigVariants(makeBaseNPMConfig());
diff --git a/packages/vercel-edge/src/async.ts b/packages/vercel-edge/src/async.ts
new file mode 100644
index 000000000000..36c6317248b4
--- /dev/null
+++ b/packages/vercel-edge/src/async.ts
@@ -0,0 +1,59 @@
+import type { Carrier, Hub, RunWithAsyncContextOptions } from '@sentry/core';
+import { ensureHubOnCarrier, getHubFromCarrier, setAsyncContextStrategy } from '@sentry/core';
+import { GLOBAL_OBJ, logger } from '@sentry/utils';
+
+interface AsyncLocalStorage {
+ getStore(): T | undefined;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ run(store: T, callback: (...args: TArgs) => R, ...args: TArgs): R;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
+const MaybeGlobalAsyncLocalStorage = (GLOBAL_OBJ as any).AsyncLocalStorage;
+
+let asyncStorage: AsyncLocalStorage;
+
+/**
+ * Sets the async context strategy to use AsyncLocalStorage which should be available in the edge runtime.
+ */
+export function setAsyncLocalStorageAsyncContextStrategy(): void {
+ if (!MaybeGlobalAsyncLocalStorage) {
+ __DEBUG_BUILD__ &&
+ logger.warn(
+ "Tried to register AsyncLocalStorage async context strategy in a runtime that doesn't support AsyncLocalStorage.",
+ );
+ return;
+ }
+
+ if (!asyncStorage) {
+ asyncStorage = new MaybeGlobalAsyncLocalStorage();
+ }
+
+ function getCurrentHub(): Hub | undefined {
+ return asyncStorage.getStore();
+ }
+
+ function createNewHub(parent: Hub | undefined): Hub {
+ const carrier: Carrier = {};
+ ensureHubOnCarrier(carrier, parent);
+ return getHubFromCarrier(carrier);
+ }
+
+ function runWithAsyncContext(callback: () => T, options: RunWithAsyncContextOptions): T {
+ const existingHub = getCurrentHub();
+
+ if (existingHub && options?.reuseExisting) {
+ // We're already in an async context, so we don't need to create a new one
+ // just call the callback with the current hub
+ return callback();
+ }
+
+ const newHub = createNewHub(existingHub);
+
+ return asyncStorage.run(newHub, () => {
+ return callback();
+ });
+ }
+
+ setAsyncContextStrategy({ getCurrentHub, runWithAsyncContext });
+}
diff --git a/packages/vercel-edge/src/client.ts b/packages/vercel-edge/src/client.ts
new file mode 100644
index 000000000000..448ecb199dce
--- /dev/null
+++ b/packages/vercel-edge/src/client.ts
@@ -0,0 +1,44 @@
+import type { ServerRuntimeClientOptions } from '@sentry/core';
+import { SDK_VERSION, ServerRuntimeClient } from '@sentry/core';
+
+import type { VercelEdgeClientOptions } from './types';
+
+declare const process: {
+ env: Record;
+};
+
+/**
+ * The Sentry Vercel Edge Runtime SDK Client.
+ *
+ * @see VercelEdgeClientOptions for documentation on configuration options.
+ * @see ServerRuntimeClient for usage documentation.
+ */
+export class VercelEdgeClient extends ServerRuntimeClient {
+ /**
+ * Creates a new Vercel Edge Runtime SDK instance.
+ * @param options Configuration options for this SDK.
+ */
+ public constructor(options: VercelEdgeClientOptions) {
+ options._metadata = options._metadata || {};
+ options._metadata.sdk = options._metadata.sdk || {
+ name: 'sentry.javascript.vercel-edge',
+ packages: [
+ {
+ name: 'npm:@sentry/vercel-edge',
+ version: SDK_VERSION,
+ },
+ ],
+ version: SDK_VERSION,
+ };
+
+ const clientOptions: ServerRuntimeClientOptions = {
+ ...options,
+ platform: 'vercel-edge',
+ // TODO: Grab version information
+ runtime: { name: 'vercel-edge' },
+ serverName: options.serverName || process.env.SENTRY_NAME,
+ };
+
+ super(clientOptions);
+ }
+}
diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts
new file mode 100644
index 000000000000..cd596269a36f
--- /dev/null
+++ b/packages/vercel-edge/src/index.ts
@@ -0,0 +1,73 @@
+export type {
+ Breadcrumb,
+ BreadcrumbHint,
+ PolymorphicRequest,
+ Request,
+ SdkInfo,
+ Event,
+ EventHint,
+ Exception,
+ Session,
+ // eslint-disable-next-line deprecation/deprecation
+ Severity,
+ SeverityLevel,
+ Span,
+ StackFrame,
+ Stacktrace,
+ Thread,
+ Transaction,
+ User,
+} from '@sentry/types';
+export type { AddRequestDataToEventOptions } from '@sentry/utils';
+
+export type { VercelEdgeOptions } from './types';
+
+export {
+ addGlobalEventProcessor,
+ addBreadcrumb,
+ captureException,
+ captureEvent,
+ captureMessage,
+ close,
+ configureScope,
+ createTransport,
+ extractTraceparentData,
+ flush,
+ getActiveTransaction,
+ getHubFromCarrier,
+ getCurrentHub,
+ Hub,
+ lastEventId,
+ makeMain,
+ runWithAsyncContext,
+ Scope,
+ startTransaction,
+ SDK_VERSION,
+ setContext,
+ setExtra,
+ setExtras,
+ setTag,
+ setTags,
+ setUser,
+ spanStatusfromHttpCode,
+ trace,
+ withScope,
+ captureCheckIn,
+ setMeasurement,
+ getActiveSpan,
+ startSpan,
+ startInactiveSpan,
+ startSpanManual,
+} from '@sentry/core';
+export type { SpanStatusType } from '@sentry/core';
+
+export { VercelEdgeClient } from './client';
+export { defaultIntegrations, init } from './sdk';
+
+import { Integrations as CoreIntegrations } from '@sentry/core';
+
+const INTEGRATIONS = {
+ ...CoreIntegrations,
+};
+
+export { INTEGRATIONS as Integrations };
diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts
new file mode 100644
index 000000000000..f2c57c7f9546
--- /dev/null
+++ b/packages/vercel-edge/src/sdk.ts
@@ -0,0 +1,96 @@
+import { getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core';
+import { createStackParser, GLOBAL_OBJ, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils';
+
+import { setAsyncLocalStorageAsyncContextStrategy } from './async';
+import { VercelEdgeClient } from './client';
+import { makeEdgeTransport } from './transports';
+import type { VercelEdgeClientOptions, VercelEdgeOptions } from './types';
+import { getVercelEnv } from './utils/vercel';
+
+declare const process: {
+ env: Record;
+};
+
+const nodeStackParser = createStackParser(nodeStackLineParser());
+
+export const defaultIntegrations = [new CoreIntegrations.InboundFilters(), new CoreIntegrations.FunctionToString()];
+
+/** Inits the Sentry NextJS SDK on the Edge Runtime. */
+export function init(options: VercelEdgeOptions = {}): void {
+ setAsyncLocalStorageAsyncContextStrategy();
+
+ if (options.defaultIntegrations === undefined) {
+ options.defaultIntegrations = defaultIntegrations;
+ }
+
+ if (options.dsn === undefined && process.env.SENTRY_DSN) {
+ options.dsn = process.env.SENTRY_DSN;
+ }
+
+ if (options.tracesSampleRate === undefined && process.env.SENTRY_TRACES_SAMPLE_RATE) {
+ const tracesSampleRate = parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE);
+ if (isFinite(tracesSampleRate)) {
+ options.tracesSampleRate = tracesSampleRate;
+ }
+ }
+
+ if (options.release === undefined) {
+ const detectedRelease = getSentryRelease();
+ if (detectedRelease !== undefined) {
+ options.release = detectedRelease;
+ } else {
+ // If release is not provided, then we should disable autoSessionTracking
+ options.autoSessionTracking = false;
+ }
+ }
+
+ options.environment =
+ options.environment || process.env.SENTRY_ENVIRONMENT || getVercelEnv(false) || process.env.NODE_ENV;
+
+ if (options.autoSessionTracking === undefined && options.dsn !== undefined) {
+ options.autoSessionTracking = true;
+ }
+
+ if (options.instrumenter === undefined) {
+ options.instrumenter = 'sentry';
+ }
+
+ const clientOptions: VercelEdgeClientOptions = {
+ ...options,
+ stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser),
+ integrations: getIntegrationsToSetup(options),
+ transport: options.transport || makeEdgeTransport,
+ };
+
+ initAndBind(VercelEdgeClient, clientOptions);
+}
+
+/**
+ * Returns a release dynamically from environment variables.
+ */
+export function getSentryRelease(fallback?: string): string | undefined {
+ // Always read first as Sentry takes this as precedence
+ if (process.env.SENTRY_RELEASE) {
+ return process.env.SENTRY_RELEASE;
+ }
+
+ // This supports the variable that sentry-webpack-plugin injects
+ if (GLOBAL_OBJ.SENTRY_RELEASE && GLOBAL_OBJ.SENTRY_RELEASE.id) {
+ return GLOBAL_OBJ.SENTRY_RELEASE.id;
+ }
+
+ return (
+ // GitHub Actions - https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables
+ process.env.GITHUB_SHA ||
+ // Vercel - https://vercel.com/docs/v2/build-step#system-environment-variables
+ process.env.VERCEL_GIT_COMMIT_SHA ||
+ process.env.VERCEL_GITHUB_COMMIT_SHA ||
+ process.env.VERCEL_GITLAB_COMMIT_SHA ||
+ process.env.VERCEL_BITBUCKET_COMMIT_SHA ||
+ // Zeit (now known as Vercel)
+ process.env.ZEIT_GITHUB_COMMIT_SHA ||
+ process.env.ZEIT_GITLAB_COMMIT_SHA ||
+ process.env.ZEIT_BITBUCKET_COMMIT_SHA ||
+ fallback
+ );
+}
diff --git a/packages/vercel-edge/src/transports/index.ts b/packages/vercel-edge/src/transports/index.ts
new file mode 100644
index 000000000000..a479425f96e6
--- /dev/null
+++ b/packages/vercel-edge/src/transports/index.ts
@@ -0,0 +1,103 @@
+import { createTransport } from '@sentry/core';
+import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types';
+import { SentryError } from '@sentry/utils';
+
+export interface VercelEdgeTransportOptions extends BaseTransportOptions {
+ /** Fetch API init parameters. */
+ fetchOptions?: RequestInit;
+ /** Custom headers for the transport. */
+ headers?: { [key: string]: string };
+}
+
+const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;
+
+/**
+ * This is a modified promise buffer that collects tasks until drain is called.
+ * We need this in the edge runtime because edge function invocations may not share I/O objects, like fetch requests
+ * and responses, and the normal PromiseBuffer inherently buffers stuff inbetween incoming requests.
+ *
+ * A limitation we need to be aware of is that DEFAULT_TRANSPORT_BUFFER_SIZE is the maximum amount of payloads the
+ * SDK can send for a given edge function invocation.
+ */
+export class IsolatedPromiseBuffer {
+ // We just have this field because the promise buffer interface requires it.
+ // If we ever remove it from the interface we should also remove it here.
+ public $: Array>;
+
+ private _taskProducers: (() => PromiseLike)[];
+
+ private readonly _bufferSize: number;
+
+ public constructor(_bufferSize = DEFAULT_TRANSPORT_BUFFER_SIZE) {
+ this.$ = [];
+ this._taskProducers = [];
+ this._bufferSize = _bufferSize;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public add(taskProducer: () => PromiseLike): PromiseLike {
+ if (this._taskProducers.length >= this._bufferSize) {
+ return Promise.reject(new SentryError('Not adding Promise because buffer limit was reached.'));
+ }
+
+ this._taskProducers.push(taskProducer);
+ return Promise.resolve();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public drain(timeout?: number): PromiseLike {
+ const oldTaskProducers = [...this._taskProducers];
+ this._taskProducers = [];
+
+ return new Promise(resolve => {
+ const timer = setTimeout(() => {
+ if (timeout && timeout > 0) {
+ resolve(false);
+ }
+ }, timeout);
+
+ void Promise.all(
+ oldTaskProducers.map(taskProducer =>
+ taskProducer().then(null, () => {
+ // catch all failed requests
+ }),
+ ),
+ ).then(() => {
+ // resolve to true if all fetch requests settled
+ clearTimeout(timer);
+ resolve(true);
+ });
+ });
+ }
+}
+
+/**
+ * Creates a Transport that uses the Edge Runtimes native fetch API to send events to Sentry.
+ */
+export function makeEdgeTransport(options: VercelEdgeTransportOptions): Transport {
+ function makeRequest(request: TransportRequest): PromiseLike {
+ const requestOptions: RequestInit = {
+ body: request.body,
+ method: 'POST',
+ referrerPolicy: 'origin',
+ headers: options.headers,
+ ...options.fetchOptions,
+ };
+
+ return fetch(options.url, requestOptions).then(response => {
+ return {
+ statusCode: response.status,
+ headers: {
+ 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
+ 'retry-after': response.headers.get('Retry-After'),
+ },
+ };
+ });
+ }
+
+ return createTransport(options, makeRequest, new IsolatedPromiseBuffer(options.bufferSize));
+}
diff --git a/packages/vercel-edge/src/transports/types.ts b/packages/vercel-edge/src/transports/types.ts
new file mode 100644
index 000000000000..e6c0ab869662
--- /dev/null
+++ b/packages/vercel-edge/src/transports/types.ts
@@ -0,0 +1,8 @@
+import type { BaseTransportOptions } from '@sentry/types';
+
+export interface VercelEdgeTransportOptions extends BaseTransportOptions {
+ /** Fetch API init parameters. */
+ fetchOptions?: RequestInit;
+ /** Custom headers for the transport. */
+ headers?: { [key: string]: string };
+}
diff --git a/packages/vercel-edge/src/types.ts b/packages/vercel-edge/src/types.ts
new file mode 100644
index 000000000000..ec91431d8e60
--- /dev/null
+++ b/packages/vercel-edge/src/types.ts
@@ -0,0 +1,68 @@
+import type { ClientOptions, Options, TracePropagationTargets } from '@sentry/types';
+
+import type { VercelEdgeClient } from './client';
+import type { VercelEdgeTransportOptions } from './transports';
+
+export interface BaseVercelEdgeOptions {
+ /**
+ * List of strings/regex controlling to which outgoing requests
+ * the SDK will attach tracing headers.
+ *
+ * By default the SDK will attach those headers to all outgoing
+ * requests. If this option is provided, the SDK will match the
+ * request URL of outgoing requests against the items in this
+ * array, and only attach tracing headers if a match was found.
+ *
+ * @example
+ * ```js
+ * Sentry.init({
+ * tracePropagationTargets: ['api.site.com'],
+ * });
+ * ```
+ */
+ tracePropagationTargets?: TracePropagationTargets;
+
+ /** Sets an optional server name (device name) */
+ serverName?: string;
+
+ /**
+ * Specify a custom VercelEdgeClient to be used. Must extend VercelEdgeClient!
+ * This is not a public, supported API, but used internally only.
+ *
+ * @hidden
+ * */
+ clientClass?: typeof VercelEdgeClient;
+
+ // TODO (v8): Remove this in v8
+ /**
+ * @deprecated Moved to constructor options of the `Http` and `Undici` integration.
+ * @example
+ * ```js
+ * Sentry.init({
+ * integrations: [
+ * new Sentry.Integrations.Http({
+ * tracing: {
+ * shouldCreateSpanForRequest: (url: string) => false,
+ * }
+ * });
+ * ],
+ * });
+ * ```
+ */
+ shouldCreateSpanForRequest?(this: void, url: string): boolean;
+
+ /** Callback that is executed when a fatal global error occurs. */
+ onFatalError?(this: void, error: Error): void;
+}
+
+/**
+ * Configuration options for the Sentry VercelEdge SDK
+ * @see @sentry/types Options for more information.
+ */
+export interface VercelEdgeOptions extends Options, BaseVercelEdgeOptions {}
+
+/**
+ * Configuration options for the Sentry VercelEdge SDK Client class
+ * @see VercelEdgeClient for more information.
+ */
+export interface VercelEdgeClientOptions extends ClientOptions, BaseVercelEdgeOptions {}
diff --git a/packages/vercel-edge/src/utils/vercel.ts b/packages/vercel-edge/src/utils/vercel.ts
new file mode 100644
index 000000000000..cb640aaff1c5
--- /dev/null
+++ b/packages/vercel-edge/src/utils/vercel.ts
@@ -0,0 +1,13 @@
+declare const process: {
+ env: Record;
+};
+
+/**
+ * Returns an environment setting value determined by Vercel's `VERCEL_ENV` environment variable.
+ *
+ * @param isClient Flag to indicate whether to use the `NEXT_PUBLIC_` prefixed version of the environment variable.
+ */
+export function getVercelEnv(isClient: boolean): string | undefined {
+ const vercelEnvVar = isClient ? process.env.NEXT_PUBLIC_VERCEL_ENV : process.env.VERCEL_ENV;
+ return vercelEnvVar ? `vercel-${vercelEnvVar}` : undefined;
+}
diff --git a/packages/vercel-edge/test/transports/index.test.ts b/packages/vercel-edge/test/transports/index.test.ts
new file mode 100644
index 000000000000..cab31eca5bf2
--- /dev/null
+++ b/packages/vercel-edge/test/transports/index.test.ts
@@ -0,0 +1,163 @@
+import type { EventEnvelope, EventItem } from '@sentry/types';
+import { createEnvelope, serializeEnvelope } from '@sentry/utils';
+import { TextEncoder } from 'util';
+
+import type { VercelEdgeTransportOptions } from '../../src/transports';
+import { IsolatedPromiseBuffer, makeEdgeTransport } from '../../src/transports';
+
+const DEFAULT_EDGE_TRANSPORT_OPTIONS: VercelEdgeTransportOptions = {
+ url: 'https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7',
+ recordDroppedEvent: () => undefined,
+ textEncoder: new TextEncoder(),
+};
+
+const ERROR_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
+ [{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem,
+]);
+
+class Headers {
+ headers: { [key: string]: string } = {};
+ get(key: string) {
+ return this.headers[key] || null;
+ }
+ set(key: string, value: string) {
+ this.headers[key] = value;
+ }
+}
+
+const mockFetch = jest.fn();
+
+// @ts-expect-error fetch is not on global
+const oldFetch = global.fetch;
+// @ts-expect-error fetch is not on global
+global.fetch = mockFetch;
+
+afterAll(() => {
+ // @ts-expect-error fetch is not on global
+ global.fetch = oldFetch;
+});
+
+describe('Edge Transport', () => {
+ it('calls fetch with the given URL', async () => {
+ mockFetch.mockImplementationOnce(() =>
+ Promise.resolve({
+ headers: new Headers(),
+ status: 200,
+ text: () => Promise.resolve({}),
+ }),
+ );
+
+ const transport = makeEdgeTransport(DEFAULT_EDGE_TRANSPORT_OPTIONS);
+
+ expect(mockFetch).toHaveBeenCalledTimes(0);
+ await transport.send(ERROR_ENVELOPE);
+ await transport.flush();
+ expect(mockFetch).toHaveBeenCalledTimes(1);
+
+ expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_EDGE_TRANSPORT_OPTIONS.url, {
+ body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()),
+ method: 'POST',
+ referrerPolicy: 'origin',
+ });
+ });
+
+ it('sets rate limit headers', async () => {
+ const headers = {
+ get: jest.fn(),
+ };
+
+ mockFetch.mockImplementationOnce(() =>
+ Promise.resolve({
+ headers,
+ status: 200,
+ text: () => Promise.resolve({}),
+ }),
+ );
+
+ const transport = makeEdgeTransport(DEFAULT_EDGE_TRANSPORT_OPTIONS);
+
+ expect(headers.get).toHaveBeenCalledTimes(0);
+ await transport.send(ERROR_ENVELOPE);
+ await transport.flush();
+
+ expect(headers.get).toHaveBeenCalledTimes(2);
+ expect(headers.get).toHaveBeenCalledWith('X-Sentry-Rate-Limits');
+ expect(headers.get).toHaveBeenCalledWith('Retry-After');
+ });
+
+ it('allows for custom options to be passed in', async () => {
+ mockFetch.mockImplementationOnce(() =>
+ Promise.resolve({
+ headers: new Headers(),
+ status: 200,
+ text: () => Promise.resolve({}),
+ }),
+ );
+
+ const REQUEST_OPTIONS: RequestInit = {
+ referrerPolicy: 'strict-origin',
+ keepalive: false,
+ referrer: 'http://example.org',
+ };
+
+ const transport = makeEdgeTransport({ ...DEFAULT_EDGE_TRANSPORT_OPTIONS, fetchOptions: REQUEST_OPTIONS });
+
+ await transport.send(ERROR_ENVELOPE);
+ await transport.flush();
+ expect(mockFetch).toHaveBeenLastCalledWith(DEFAULT_EDGE_TRANSPORT_OPTIONS.url, {
+ body: serializeEnvelope(ERROR_ENVELOPE, new TextEncoder()),
+ method: 'POST',
+ ...REQUEST_OPTIONS,
+ });
+ });
+});
+
+describe('IsolatedPromiseBuffer', () => {
+ it('should not call tasks until drained', async () => {
+ const ipb = new IsolatedPromiseBuffer();
+
+ const task1 = jest.fn(() => Promise.resolve({}));
+ const task2 = jest.fn(() => Promise.resolve({}));
+
+ await ipb.add(task1);
+ await ipb.add(task2);
+
+ expect(task1).not.toHaveBeenCalled();
+ expect(task2).not.toHaveBeenCalled();
+
+ await ipb.drain();
+
+ expect(task1).toHaveBeenCalled();
+ expect(task2).toHaveBeenCalled();
+ });
+
+ it('should not allow adding more items than the specified limit', async () => {
+ const ipb = new IsolatedPromiseBuffer(3);
+
+ const task1 = jest.fn(() => Promise.resolve({}));
+ const task2 = jest.fn(() => Promise.resolve({}));
+ const task3 = jest.fn(() => Promise.resolve({}));
+ const task4 = jest.fn(() => Promise.resolve({}));
+
+ await ipb.add(task1);
+ await ipb.add(task2);
+ await ipb.add(task3);
+
+ await expect(ipb.add(task4)).rejects.toThrowError('Not adding Promise because buffer limit was reached.');
+ });
+
+ it('should not throw when one of the tasks throws when drained', async () => {
+ const ipb = new IsolatedPromiseBuffer();
+
+ const task1 = jest.fn(() => Promise.resolve({}));
+ const task2 = jest.fn(() => Promise.reject(new Error()));
+
+ await ipb.add(task1);
+ await ipb.add(task2);
+
+ await expect(ipb.drain()).resolves.toEqual(true);
+
+ expect(task1).toHaveBeenCalled();
+ expect(task2).toHaveBeenCalled();
+ });
+});
diff --git a/packages/vercel-edge/tsconfig.json b/packages/vercel-edge/tsconfig.json
new file mode 100644
index 000000000000..f288bd1b84e2
--- /dev/null
+++ b/packages/vercel-edge/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../../tsconfig.json",
+
+ "include": ["src/**/*"],
+
+ "compilerOptions": {
+ // package-specific options
+ "types": ["@edge-runtime/types"]
+ }
+}
diff --git a/packages/vercel-edge/tsconfig.test.json b/packages/vercel-edge/tsconfig.test.json
new file mode 100644
index 000000000000..87f6afa06b86
--- /dev/null
+++ b/packages/vercel-edge/tsconfig.test.json
@@ -0,0 +1,12 @@
+{
+ "extends": "./tsconfig.json",
+
+ "include": ["test/**/*"],
+
+ "compilerOptions": {
+ // should include all types from `./tsconfig.json` plus types for all test frameworks used
+ "types": ["node", "jest"]
+
+ // other package-specific, test-specific options
+ }
+}
diff --git a/packages/vercel-edge/tsconfig.types.json b/packages/vercel-edge/tsconfig.types.json
new file mode 100644
index 000000000000..65455f66bd75
--- /dev/null
+++ b/packages/vercel-edge/tsconfig.types.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "emitDeclarationOnly": true,
+ "outDir": "build/types"
+ }
+}
diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts
index 835e83c44896..28167e15d557 100644
--- a/scripts/node-unit-tests.ts
+++ b/scripts/node-unit-tests.ts
@@ -1,158 +1,175 @@
-import * as childProcess from 'child_process';
-import * as fs from 'fs';
-
-const CURRENT_NODE_VERSION = process.version.replace('v', '').split('.')[0];
-
-const DEFAULT_SKIP_TESTS_PACKAGES = [
- '@sentry-internal/eslint-plugin-sdk',
- '@sentry/ember',
- '@sentry/browser',
- '@sentry/vue',
- '@sentry/react',
- '@sentry/angular',
- '@sentry/svelte',
- '@sentry/replay',
- '@sentry/wasm',
- '@sentry/bun',
-];
-
-// These packages don't support Node 8 for syntax or dependency reasons.
-const NODE_8_SKIP_TESTS_PACKAGES = [
- '@sentry/gatsby',
- '@sentry/serverless',
- '@sentry/nextjs',
- '@sentry/remix',
- '@sentry/sveltekit',
- '@sentry-internal/replay-worker',
- '@sentry/node-experimental',
-];
-
-// We have to downgrade some of our dependencies in order to run tests in Node 8 and 10.
-const NODE_8_LEGACY_DEPENDENCIES = [
- 'jsdom@15.x',
- 'jest@25.x',
- 'jest-environment-jsdom@25.x',
- 'jest-environment-node@25.x',
- 'ts-jest@25.x',
- 'lerna@3.13.4',
-];
-
-const NODE_10_SKIP_TESTS_PACKAGES = [
- '@sentry/remix',
- '@sentry/sveltekit',
- '@sentry-internal/replay-worker',
- '@sentry/node-experimental',
-];
-const NODE_10_LEGACY_DEPENDENCIES = ['jsdom@16.x', 'lerna@3.13.4'];
-
-const NODE_12_SKIP_TESTS_PACKAGES = ['@sentry/remix', '@sentry/sveltekit', '@sentry/node-experimental'];
-const NODE_12_LEGACY_DEPENDENCIES = ['lerna@3.13.4'];
-
-const NODE_14_SKIP_TESTS_PACKAGES = ['@sentry/sveltekit'];
-
-type JSONValue = string | number | boolean | null | JSONArray | JSONObject;
-
-type JSONObject = {
- [key: string]: JSONValue;
-};
-type JSONArray = Array;
-
-interface TSConfigJSON extends JSONObject {
- compilerOptions: { lib: string[]; target: string };
-}
-
-/**
- * Run the given shell command, piping the shell process's `stdin`, `stdout`, and `stderr` to that of the current
- * process. Returns contents of `stdout`.
- */
-function run(cmd: string, options?: childProcess.ExecSyncOptions): void {
- childProcess.execSync(cmd, { stdio: 'inherit', ...options });
-}
-
-/**
- * Install the given legacy dependencies, for compatibility with tests run in older versions of Node.
- */
-function installLegacyDeps(legacyDeps: string[] = []): void {
- // Ignoring engines and scripts lets us get away with having incompatible things installed for SDK packages we're not
- // testing in the current node version, and ignoring the root check lets us install things at the repo root.
- run(`yarn add --dev --ignore-engines --ignore-scripts --ignore-workspace-root-check ${legacyDeps.join(' ')}`);
-}
-
-/**
- * Modify a json file on disk.
- *
- * @param filepath The path to the file to be modified
- * @param transformer A function which takes the JSON data as input and returns a mutated version. It may mutate the
- * JSON data in place, but it isn't required to do so.
- */
-export function modifyJSONFile(filepath: string, transformer: (json: JSONObject) => JSONObject): void {
- const fileContents = fs
- .readFileSync(filepath)
- .toString()
- // get rid of comments, which the `jsonc` format allows, but which will crash `JSON.parse`
- .replace(/\/\/.*\n/g, '');
- const json = JSON.parse(fileContents);
- const newJSON = transformer(json);
- fs.writeFileSync(filepath, JSON.stringify(newJSON, null, 2));
-}
-
-const es6ifyTestTSConfig = (pkg: string): void => {
- const filepath = `packages/${pkg}/tsconfig.test.json`;
- const transformer = (json: JSONObject): JSONObject => {
- const tsconfig = json as TSConfigJSON;
- tsconfig.compilerOptions.target = 'es6';
- return json;
- };
- modifyJSONFile(filepath, transformer);
-};
-
-/**
- * Skip tests which don't run in Node 8.
- * We're forced to skip these tests for compatibility reasons.
- */
-function skipNodeV8Tests(): void {
- run('rm -rf packages/tracing/test/browser');
-}
-
-/**
- * Run tests, ignoring the given packages
- */
-function runWithIgnores(skipPackages: string[] = []): void {
- const ignoreFlags = skipPackages.map(dep => `--ignore="${dep}"`).join(' ');
- run(`yarn test ${ignoreFlags}`);
-}
-
-/**
- * Run the tests, accounting for compatibility problems in older versions of Node.
- */
-function runTests(): void {
- const ignores = new Set();
-
- DEFAULT_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep));
-
- switch (CURRENT_NODE_VERSION) {
- case '8':
- NODE_8_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep));
- installLegacyDeps(NODE_8_LEGACY_DEPENDENCIES);
- skipNodeV8Tests();
- es6ifyTestTSConfig('utils');
- break;
- case '10':
- NODE_10_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep));
- installLegacyDeps(NODE_10_LEGACY_DEPENDENCIES);
- es6ifyTestTSConfig('utils');
- break;
- case '12':
- NODE_12_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep));
- installLegacyDeps(NODE_12_LEGACY_DEPENDENCIES);
- es6ifyTestTSConfig('utils');
- break;
- case '14':
- NODE_14_SKIP_TESTS_PACKAGES.forEach(dep => ignores.add(dep));
- break;
- }
-
- runWithIgnores(Array.from(ignores));
-}
-
-runTests();
+import * as childProcess from 'child_process';
+import * as fs from 'fs';
+
+type NodeVersion = '8' | '10' | '12' | '14' | '16';
+
+interface VersionConfig {
+ ignoredPackages: Array<`@${'sentry' | 'sentry-internal'}/${string}`>;
+ legacyDeps: Array<`${string}@${string}`>;
+ shouldES6Utils: boolean;
+}
+
+const CURRENT_NODE_VERSION = process.version.replace('v', '').split('.')[0] as NodeVersion;
+
+const DEFAULT_SKIP_TESTS_PACKAGES = [
+ '@sentry-internal/eslint-plugin-sdk',
+ '@sentry/ember',
+ '@sentry/browser',
+ '@sentry/vue',
+ '@sentry/react',
+ '@sentry/angular',
+ '@sentry/svelte',
+ '@sentry/replay',
+ '@sentry/wasm',
+ '@sentry/bun',
+];
+
+const SKIP_TEST_PACKAGES: Record = {
+ '8': {
+ ignoredPackages: [
+ '@sentry/gatsby',
+ '@sentry/serverless',
+ '@sentry/nextjs',
+ '@sentry/remix',
+ '@sentry/sveltekit',
+ '@sentry-internal/replay-worker',
+ '@sentry/node-experimental',
+ '@sentry/vercel-edge',
+ ],
+ legacyDeps: [
+ 'jsdom@15.x',
+ 'jest@25.x',
+ 'jest-environment-jsdom@25.x',
+ 'jest-environment-node@25.x',
+ 'ts-jest@25.x',
+ 'lerna@3.13.4',
+ ],
+ shouldES6Utils: true,
+ },
+ '10': {
+ ignoredPackages: [
+ '@sentry/remix',
+ '@sentry/sveltekit',
+ '@sentry-internal/replay-worker',
+ '@sentry/node-experimental',
+ '@sentry/vercel-edge',
+ ],
+ legacyDeps: ['jsdom@16.x', 'lerna@3.13.4'],
+ shouldES6Utils: true,
+ },
+ '12': {
+ ignoredPackages: ['@sentry/remix', '@sentry/sveltekit', '@sentry/node-experimental', '@sentry/vercel-edge'],
+ legacyDeps: ['lerna@3.13.4'],
+ shouldES6Utils: true,
+ },
+ '14': {
+ ignoredPackages: ['@sentry/sveltekit', '@sentry/vercel-edge'],
+ legacyDeps: [],
+ shouldES6Utils: false,
+ },
+ '16': {
+ ignoredPackages: ['@sentry/vercel-edge'],
+ legacyDeps: [],
+ shouldES6Utils: false,
+ },
+};
+
+type JSONValue = string | number | boolean | null | JSONArray | JSONObject;
+
+type JSONObject = {
+ [key: string]: JSONValue;
+};
+
+type JSONArray = Array;
+
+interface TSConfigJSON extends JSONObject {
+ compilerOptions: { lib: string[]; target: string };
+}
+
+/**
+ * Run the given shell command, piping the shell process's `stdin`, `stdout`, and `stderr` to that of the current
+ * process. Returns contents of `stdout`.
+ */
+function run(cmd: string, options?: childProcess.ExecSyncOptions): void {
+ childProcess.execSync(cmd, { stdio: 'inherit', ...options });
+}
+
+/**
+ * Install the given legacy dependencies, for compatibility with tests run in older versions of Node.
+ */
+function installLegacyDeps(legacyDeps: string[] = []): void {
+ // Ignoring engines and scripts lets us get away with having incompatible things installed for SDK packages we're not
+ // testing in the current node version, and ignoring the root check lets us install things at the repo root.
+ run(`yarn add --dev --ignore-engines --ignore-scripts --ignore-workspace-root-check ${legacyDeps.join(' ')}`);
+}
+
+/**
+ * Modify a json file on disk.
+ *
+ * @param filepath The path to the file to be modified
+ * @param transformer A function which takes the JSON data as input and returns a mutated version. It may mutate the
+ * JSON data in place, but it isn't required to do so.
+ */
+export function modifyJSONFile(filepath: string, transformer: (json: T) => T): void {
+ const fileContents = fs
+ .readFileSync(filepath)
+ .toString()
+ // get rid of comments, which the `jsonc` format allows, but which will crash `JSON.parse`
+ .replace(/\/\/.*\n/g, '');
+ const json = JSON.parse(fileContents);
+ const newJSON = transformer(json);
+ fs.writeFileSync(filepath, JSON.stringify(newJSON, null, 2));
+}
+
+const es6ifyTestTSConfig = (pkg: string): void => {
+ const filepath = `packages/${pkg}/tsconfig.test.json`;
+ const transformer = (tsconfig: TSConfigJSON): TSConfigJSON => {
+ tsconfig.compilerOptions.target = 'es6';
+ return tsconfig;
+ };
+ modifyJSONFile(filepath, transformer);
+};
+
+/**
+ * Skip tests which don't run in Node 8.
+ * We're forced to skip these tests for compatibility reasons.
+ */
+function skipNodeV8Tests(): void {
+ run('rm -rf packages/tracing/test/browser');
+}
+
+/**
+ * Run tests, ignoring the given packages
+ */
+function runWithIgnores(skipPackages: string[] = []): void {
+ const ignoreFlags = skipPackages.map(dep => `--ignore="${dep}"`).join(' ');
+ run(`yarn test ${ignoreFlags}`);
+}
+
+/**
+ * Run the tests, accounting for compatibility problems in older versions of Node.
+ */
+function runTests(): void {
+ const ignores = new Set();
+
+ DEFAULT_SKIP_TESTS_PACKAGES.forEach(pkg => ignores.add(pkg));
+
+ if (CURRENT_NODE_VERSION === '8') {
+ skipNodeV8Tests();
+ }
+
+ const versionConfig = SKIP_TEST_PACKAGES[CURRENT_NODE_VERSION];
+ if (versionConfig) {
+ versionConfig.ignoredPackages.forEach(dep => ignores.add(dep));
+ if (versionConfig.legacyDeps.length > 0) {
+ installLegacyDeps(versionConfig.legacyDeps);
+ }
+ if (versionConfig.shouldES6Utils) {
+ es6ifyTestTSConfig('utils');
+ }
+ }
+
+ runWithIgnores(Array.from(ignores));
+}
+
+runTests();
diff --git a/yarn.lock b/yarn.lock
index 8b412653caec..d283d72b7ad2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2281,6 +2281,41 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d"
integrity sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==
+"@edge-runtime/jest-environment@2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@edge-runtime/jest-environment/-/jest-environment-2.2.3.tgz#2fef094d769f45b5018b33bdc58e664b35bbc312"
+ integrity sha512-5DEv8nzuMFGoVbNYbOz7/mileYSbq1/oIvisyTeSfyjId7Pc5Qh2t3BH7ixLa62aVz7oCQlALM4cYGbZQZw1YQ==
+ dependencies:
+ "@edge-runtime/vm" "3.0.3"
+ "@jest/environment" "29.5.0"
+ "@jest/fake-timers" "29.5.0"
+ jest-mock "29.5.0"
+ jest-util "29.5.0"
+
+"@edge-runtime/primitives@3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@edge-runtime/primitives/-/primitives-3.0.3.tgz#adc6a4bd34c44faf81c954cf8e5816c7d408a1ea"
+ integrity sha512-YnfMWMRQABAH8IsnFMJWMW+SyB4ZeYBPnR7V0aqdnew7Pq60cbH5DyFjS/FhiLwvHQk9wBREmXD7PP0HooEQ1A==
+
+"@edge-runtime/primitives@4.0.1":
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/@edge-runtime/primitives/-/primitives-4.0.1.tgz#12efffac1caa8a29ae8f86a3f87f20cc0ae07131"
+ integrity sha512-hxWUzx1SeyOed/Ea9Z6y6tyFKSj8gQWIdLilybTR2ene1IthLZE01A1SLGoch1szUdhFlUwpWDaYBYQw00lj2g==
+
+"@edge-runtime/types@2.2.3":
+ version "2.2.3"
+ resolved "https://registry.yarnpkg.com/@edge-runtime/types/-/types-2.2.3.tgz#cb57b7215bcf406324ec591346b7b51c75a54bdf"
+ integrity sha512-zL0ENQWwdocECEQXVopGTfnqI0tJ8wzDOCoQymoc8MLRz+Zw2V1W0ex9vczniTUzB+H/P7ubMgx3GFzLp3NPBg==
+ dependencies:
+ "@edge-runtime/primitives" "4.0.1"
+
+"@edge-runtime/vm@3.0.3":
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/@edge-runtime/vm/-/vm-3.0.3.tgz#92f1930d1eb8d0ccf6a3c165561cc22b2d9ddff8"
+ integrity sha512-SPfI1JeIRNs/4EEE2Oc0X6gG3RqjD1TnKu2lwmwFXq0435xgZGKhc3UiKkYAdoMn2dNFD73nlabMKHBRoMRpxg==
+ dependencies:
+ "@edge-runtime/primitives" "3.0.3"
+
"@ember-data/rfc395-data@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843"
@@ -2999,6 +3034,16 @@
slash "^3.0.0"
strip-ansi "^6.0.0"
+"@jest/environment@29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65"
+ integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==
+ dependencies:
+ "@jest/fake-timers" "^29.5.0"
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ jest-mock "^29.5.0"
+
"@jest/environment@^27.5.1":
version "27.5.1"
resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74"
@@ -3009,6 +3054,18 @@
"@types/node" "*"
jest-mock "^27.5.1"
+"@jest/fake-timers@29.5.0":
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.5.0.tgz#d4d09ec3286b3d90c60bdcd66ed28d35f1b4dc2c"
+ integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==
+ dependencies:
+ "@jest/types" "^29.5.0"
+ "@sinonjs/fake-timers" "^10.0.2"
+ "@types/node" "*"
+ jest-message-util "^29.5.0"
+ jest-mock "^29.5.0"
+ jest-util "^29.5.0"
+
"@jest/fake-timers@^27.5.1":
version "27.5.1"
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74"
@@ -3021,6 +3078,18 @@
jest-mock "^27.5.1"
jest-util "^27.5.1"
+"@jest/fake-timers@^29.5.0":
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.7.0.tgz#fd91bf1fffb16d7d0d24a426ab1a47a49881a565"
+ integrity sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@sinonjs/fake-timers" "^10.0.2"
+ "@types/node" "*"
+ jest-message-util "^29.7.0"
+ jest-mock "^29.7.0"
+ jest-util "^29.7.0"
+
"@jest/globals@^27.5.1":
version "27.5.1"
resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b"
@@ -3068,6 +3137,13 @@
dependencies:
"@sinclair/typebox" "^0.25.16"
+"@jest/schemas@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
+ integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
+ dependencies:
+ "@sinclair/typebox" "^0.27.8"
+
"@jest/source-map@^27.5.1":
version "27.5.1"
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf"
@@ -3129,6 +3205,18 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"
+"@jest/types@^29.5.0", "@jest/types@^29.6.3":
+ version "29.6.3"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59"
+ integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==
+ dependencies:
+ "@jest/schemas" "^29.6.3"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^3.0.0"
+ "@types/node" "*"
+ "@types/yargs" "^17.0.8"
+ chalk "^4.0.0"
+
"@josephg/resolvable@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb"
@@ -4514,6 +4602,11 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==
+"@sinclair/typebox@^0.27.8":
+ version "0.27.8"
+ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
+ integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
+
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@@ -5653,6 +5746,13 @@
dependencies:
"@types/yargs-parser" "*"
+"@types/yargs@^17.0.8":
+ version "17.0.24"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902"
+ integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==
+ dependencies:
+ "@types/yargs-parser" "*"
+
"@types/yauzl@^2.9.1":
version "2.10.0"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
@@ -17055,6 +17155,30 @@ jest-message-util@^27.5.1:
slash "^3.0.0"
stack-utils "^2.0.3"
+jest-message-util@^29.5.0, jest-message-util@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3"
+ integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==
+ dependencies:
+ "@babel/code-frame" "^7.12.13"
+ "@jest/types" "^29.6.3"
+ "@types/stack-utils" "^2.0.0"
+ chalk "^4.0.0"
+ graceful-fs "^4.2.9"
+ micromatch "^4.0.4"
+ pretty-format "^29.7.0"
+ slash "^3.0.0"
+ stack-utils "^2.0.3"
+
+jest-mock@29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.5.0.tgz#26e2172bcc71d8b0195081ff1f146ac7e1518aed"
+ integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==
+ dependencies:
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ jest-util "^29.5.0"
+
jest-mock@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6"
@@ -17063,6 +17187,15 @@ jest-mock@^27.5.1:
"@jest/types" "^27.5.1"
"@types/node" "*"
+jest-mock@^29.5.0, jest-mock@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.7.0.tgz#4e836cf60e99c6fcfabe9f99d017f3fdd50a6347"
+ integrity sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ jest-util "^29.7.0"
+
jest-pnp-resolver@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
@@ -17189,6 +17322,18 @@ jest-snapshot@^27.5.1:
pretty-format "^27.5.1"
semver "^7.3.2"
+jest-util@29.5.0:
+ version "29.5.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f"
+ integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==
+ dependencies:
+ "@jest/types" "^29.5.0"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.9"
+ picomatch "^2.2.3"
+
jest-util@^27.0.0, jest-util@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9"
@@ -17201,6 +17346,18 @@ jest-util@^27.0.0, jest-util@^27.5.1:
graceful-fs "^4.2.9"
picomatch "^2.2.3"
+jest-util@^29.5.0, jest-util@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc"
+ integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==
+ dependencies:
+ "@jest/types" "^29.6.3"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.9"
+ picomatch "^2.2.3"
+
jest-validate@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067"
@@ -23002,6 +23159,15 @@ pretty-format@^29.5.0:
ansi-styles "^5.0.0"
react-is "^18.0.0"
+pretty-format@^29.7.0:
+ version "29.7.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
+ integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
+ dependencies:
+ "@jest/schemas" "^29.6.3"
+ ansi-styles "^5.0.0"
+ react-is "^18.0.0"
+
pretty-ms@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8"