diff --git a/.craft.yml b/.craft.yml index aa9119014db4..19480826c78f 100644 --- a/.craft.yml +++ b/.craft.yml @@ -94,6 +94,9 @@ targets: - name: npm id: '@sentry/bun' includeNames: /^sentry-bun-\d.*\.tgz$/ + - name: npm + id: '@sentry/elysia' + includeNames: /^sentry-elysia-\d.*\.tgz$/ - name: npm id: '@sentry/hono' includeNames: /^sentry-hono-\d.*\.tgz$/ @@ -194,6 +197,8 @@ targets: onlyIfPresent: /^sentry-cloudflare-\d.*\.tgz$/ 'npm:@sentry/deno': onlyIfPresent: /^sentry-deno-\d.*\.tgz$/ + 'npm:@sentry/elysia': + onlyIfPresent: /^sentry-elysia-\d.*\.tgz$/ 'npm:@sentry/ember': onlyIfPresent: /^sentry-ember-\d.*\.tgz$/ 'npm:@sentry/gatsby': diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index c09984de5c3b..d5e202ea714b 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -45,6 +45,7 @@ body: - '@sentry/cloudflare' - '@sentry/cloudflare - hono' - '@sentry/deno' + - '@sentry/elysia' - '@sentry/ember' - '@sentry/gatsby' - '@sentry/google-cloud-serverless' diff --git a/README.md b/README.md index 5a3453dbe004..390420e52224 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ package. Please refer to the README and instructions of those SDKs for more deta - [`@sentry/capacitor`](https://github.com/getsentry/sentry-capacitor): SDK for Capacitor Apps and Ionic with support for native crashes - [`@sentry/bun`](https://github.com/getsentry/sentry-javascript/tree/master/packages/bun): SDK for Bun +- [`@sentry/elysia`](https://github.com/getsentry/sentry-javascript/tree/master/packages/elysia): SDK for Elysia - [`@sentry/deno`](https://github.com/getsentry/sentry-javascript/tree/master/packages/deno): SDK for Deno - [`@sentry/cloudflare`](https://github.com/getsentry/sentry-javascript/tree/master/packages/cloudflare): SDK for Cloudflare diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/.gitignore b/dev-packages/e2e-tests/test-applications/bun-elysia/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/.gitignore @@ -0,0 +1 @@ +dist diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/.npmrc b/dev-packages/e2e-tests/test-applications/bun-elysia/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/package.json b/dev-packages/e2e-tests/test-applications/bun-elysia/package.json new file mode 100644 index 000000000000..77fd6e13c0cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/package.json @@ -0,0 +1,25 @@ +{ + "name": "bun-elysia-app", + "version": "1.0.0", + "private": true, + "scripts": { + "start": "bun src/app.ts", + "test": "playwright test", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@elysiajs/opentelemetry": "^1.4.0", + "@sentry/elysia": "latest || *", + "elysia": "^1.4.0" + }, + "devDependencies": { + "@playwright/test": "~1.56.0", + "@sentry-internal/test-utils": "link:../../../test-utils", + "bun-types": "^1.2.9" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/bun-elysia/playwright.config.mjs new file mode 100644 index 000000000000..e684456a1896 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `bun src/app.ts`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/src/app.ts b/dev-packages/e2e-tests/test-applications/bun-elysia/src/app.ts new file mode 100644 index 000000000000..db6a5fff8bd1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/src/app.ts @@ -0,0 +1,142 @@ +import * as Sentry from '@sentry/elysia'; +import { Elysia } from 'elysia'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, + tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], +}); + +const app = Sentry.withElysia(new Elysia()); + +// Simple success route +app.get('/test-success', () => ({ version: 'v1' })); + +// Parameterized route +app.get('/test-param/:param', ({ params }) => ({ paramWas: params.param })); + +// Multiple params +app.get('/test-multi-param/:param1/:param2', ({ params }) => ({ + param1: params.param1, + param2: params.param2, +})); + +// Route that throws an error (will be caught by onError) +app.get('/test-exception/:id', ({ params }) => { + throw new Error(`This is an exception with id ${params.id}`); +}); + +// Route with a custom span +app.get('/test-transaction', () => { + Sentry.startSpan({ name: 'test-span' }, () => { + Sentry.startSpan({ name: 'child-span' }, () => {}); + }); + return { status: 'ok' }; +}); + +// Route with specific middleware via .guard or .use +app.group('/with-middleware', app => + app + .onBeforeHandle(() => { + // This is a route-specific middleware + }) + .get('/test', () => ({ middleware: true })), +); + +// Error with specific status code +app.post('/test-post-error', () => { + throw new Error('Post error'); +}); + +// Route that returns a non-500 error +app.get('/test-4xx', ({ set }) => { + set.status = 400; + return { error: 'Bad Request' }; +}); + +// Error that reaches the error handler with status still set to 200 (unusual, should still be captured) +app.get('/test-error-with-200-status', ({ set }) => { + set.status = 200; + throw new Error('Error with 200 status'); +}); + +// POST route that echoes body +app.post('/test-post', ({ body }) => ({ status: 'ok', body })); + +// Route that returns inbound headers (for propagation tests) +app.get('/test-inbound-headers/:id', ({ params, request }) => { + const headers = Object.fromEntries(request.headers.entries()); + return { headers, id: params.id }; +}); + +// Outgoing fetch propagation +app.get('/test-outgoing-fetch/:id', async ({ params }) => { + const id = params.id; + const response = await fetch(`http://localhost:3030/test-inbound-headers/${id}`); + const data = await response.json(); + return data; +}); + +// Outgoing fetch to external (allowed by tracePropagationTargets) +app.get('/test-outgoing-fetch-external-allowed', async () => { + const response = await fetch(`http://localhost:3040/external-allowed`); + const data = await response.json(); + return data; +}); + +// Outgoing fetch to external (disallowed by tracePropagationTargets) +app.get('/test-outgoing-fetch-external-disallowed', async () => { + const response = await fetch(`http://localhost:3040/external-disallowed`); + const data = await response.json(); + return data; +}); + +// Route that throws a string (not an Error object) +app.get('/test-string-error', () => { + // eslint-disable-next-line no-throw-literal + throw 'String error message'; +}); + +// Route for concurrent isolation tests — returns scope data in response +app.get('/test-isolation/:userId', async ({ params }) => { + Sentry.setUser({ id: params.userId }); + Sentry.setTag('user_id', params.userId); + + // Simulate async work to increase overlap between concurrent requests + await new Promise(resolve => setTimeout(resolve, 200)); + + return { + userId: params.userId, + isolationScopeUserId: Sentry.getIsolationScope().getUser()?.id, + isolationScopeTag: Sentry.getIsolationScope().getScopeData().tags?.user_id, + }; +}); + +// Flush route for waiting on events +app.get('/flush', async () => { + await Sentry.flush(); + return { ok: true }; +}); + +app.listen(3030, () => { + console.log('Elysia app listening on port 3030'); +}); + +// Second app for external propagation tests +const app2 = new Elysia(); + +app2.get('/external-allowed', ({ request }) => { + const headers = Object.fromEntries(request.headers.entries()); + return { headers, route: '/external-allowed' }; +}); + +app2.get('/external-disallowed', ({ request }) => { + const headers = Object.fromEntries(request.headers.entries()); + return { headers, route: '/external-disallowed' }; +}); + +app2.listen(3040, () => { + console.log('External app listening on port 3040'); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/bun-elysia/start-event-proxy.mjs new file mode 100644 index 000000000000..400ce8a5e380 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'bun-elysia', +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/errors.test.ts new file mode 100644 index 000000000000..1dc7a4e5c7c8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/errors.test.ts @@ -0,0 +1,109 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Captures an error thrown in a route handler', async ({ baseURL, request }) => { + const errorEventPromise = waitForError('bun-elysia', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; + }); + + await request.get(`${baseURL}/test-exception/123`); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + const exception = errorEvent.exception?.values?.[0]; + expect(exception?.value).toBe('This is an exception with id 123'); + expect(exception?.mechanism).toEqual({ + type: 'elysia', + handled: false, + }); + + expect(errorEvent.transaction).toEqual('GET /test-exception/:id'); + + expect(errorEvent.contexts?.trace).toEqual( + expect.objectContaining({ + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + }), + ); +}); + +test('Error event includes request metadata', async ({ baseURL, request }) => { + const errorEventPromise = waitForError('bun-elysia', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 456'; + }); + + await request.get(`${baseURL}/test-exception/456`); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.request).toEqual( + expect.objectContaining({ + method: 'GET', + url: expect.stringContaining('/test-exception/456'), + headers: expect.any(Object), + }), + ); +}); + +test('Does not capture errors for 4xx responses', async ({ baseURL, request }) => { + const transactionPromise = waitForTransaction('bun-elysia', transactionEvent => { + return transactionEvent?.transaction === 'GET /test-4xx'; + }); + + const response = await request.get(`${baseURL}/test-4xx`); + // Wait for the transaction to ensure the request was processed + await transactionPromise; + + expect(response.status()).toBe(400); +}); + +test('Captures errors even when status is <= 299 in error handler', async ({ baseURL, request }) => { + const errorEventPromise = waitForError('bun-elysia', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Error with 200 status'; + }); + + await request.get(`${baseURL}/test-error-with-200-status`); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values?.[0]?.value).toBe('Error with 200 status'); + expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({ + type: 'elysia', + handled: false, + }); +}); + +test('Captures POST route errors', async ({ baseURL, request }) => { + const errorEventPromise = waitForError('bun-elysia', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Post error'; + }); + + await request.post(`${baseURL}/test-post-error`); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values?.[0]?.value).toBe('Post error'); + expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual({ + type: 'elysia', + handled: false, + }); +}); + +test('Captures thrown string errors', async ({ baseURL, request }) => { + const errorEventPromise = waitForError('bun-elysia', event => { + return !event.type && event.exception?.values?.[0]?.value === 'String error message'; + }); + + await request.get(`${baseURL}/test-string-error`); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values?.[0]?.value).toBe('String error message'); + expect(errorEvent.exception?.values?.[0]?.mechanism).toEqual( + expect.objectContaining({ + type: 'elysia', + handled: false, + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/tests/isolation.test.ts b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/isolation.test.ts new file mode 100644 index 000000000000..3bdc1cc2e99d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/isolation.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; + +// The Elysia integration currently does not fork isolation scopes per request, +// so `setUser`/`setTag` on the isolation scope leaks between concurrent requests. +// This test documents the expected behavior once per-request isolation is implemented. +test.fixme('Concurrent requests have isolated scope data', async ({ baseURL }) => { + // Fire 3 concurrent requests with different user IDs + const [response1, response2, response3] = await Promise.all([ + fetch(`${baseURL}/test-isolation/user-1`), + fetch(`${baseURL}/test-isolation/user-2`), + fetch(`${baseURL}/test-isolation/user-3`), + ]); + + const data1 = await response1.json(); + const data2 = await response2.json(); + const data3 = await response3.json(); + + // Each response should have its own user ID — no leaking between requests + expect(data1.userId).toBe('user-1'); + expect(data1.isolationScopeUserId).toBe('user-1'); + expect(data1.isolationScopeTag).toBe('user-1'); + + expect(data2.userId).toBe('user-2'); + expect(data2.isolationScopeUserId).toBe('user-2'); + expect(data2.isolationScopeTag).toBe('user-2'); + + expect(data3.userId).toBe('user-3'); + expect(data3.isolationScopeUserId).toBe('user-3'); + expect(data3.isolationScopeTag).toBe('user-3'); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/propagation.test.ts new file mode 100644 index 000000000000..b7af9b893a6d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/propagation.test.ts @@ -0,0 +1,113 @@ +import { randomUUID } from 'node:crypto'; +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Includes sentry-trace and baggage in response headers', async ({ baseURL }) => { + const response = await fetch(`${baseURL}/test-success`); + + const sentryTrace = response.headers.get('sentry-trace'); + const baggage = response.headers.get('baggage'); + + expect(sentryTrace).toMatch(/[a-f0-9]{32}-[a-f0-9]{16}-[01]/); + expect(baggage).toContain('sentry-environment=qa'); + expect(baggage).toContain('sentry-trace_id='); +}); + +// Bun's native fetch does not emit undici diagnostics channels, +// so the nativeNodeFetchIntegration cannot inject sentry-trace/baggage headers. +// These tests document the desired behavior and will pass once Bun adds support +// for undici diagnostics channels or an alternative propagation mechanism is added. + +test.fixme('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { + const id = randomUUID(); + + const inboundTransactionPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.transaction === 'GET /test-inbound-headers/:id' + ); + }); + + const outboundTransactionPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.transaction === 'GET /test-outgoing-fetch/:id' + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch/${id}`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + const outboundTransaction = await outboundTransactionPromise; + + const traceId = outboundTransaction?.contexts?.trace?.trace_id; + expect(traceId).toEqual(expect.any(String)); + + // Verify sentry-trace header was propagated to the inbound request + const inboundHeaderSentryTrace = data.headers?.['sentry-trace']; + const inboundHeaderBaggage = data.headers?.['baggage']; + + expect(inboundHeaderSentryTrace).toMatch(new RegExp(`^${traceId}-[a-f0-9]{16}-1$`)); + expect(inboundHeaderBaggage).toBeDefined(); + + const baggage = (inboundHeaderBaggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); + + // Both transactions should share the same trace ID + expect(inboundTransaction.contexts?.trace?.trace_id).toBe(traceId); +}); + +test.fixme('Propagates trace for outgoing fetch to external allowed URL', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.transaction === 'GET /test-outgoing-fetch-external-allowed' + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch-external-allowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + + expect(traceId).toEqual(expect.any(String)); + + expect(data.route).toBe('/external-allowed'); + expect(data.headers?.['sentry-trace']).toMatch(/[a-f0-9]{32}-[a-f0-9]{16}-1/); + expect(data.headers?.baggage).toBeDefined(); + + const baggage = (data.headers.baggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); +}); + +test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.transaction === 'GET /test-outgoing-fetch-external-disallowed' + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch-external-disallowed`); + const data = await response.json(); + + await inboundTransactionPromise; + + expect(data.route).toBe('/external-disallowed'); + expect(data.headers?.['sentry-trace']).toBeUndefined(); + expect(data.headers?.baggage).toBeUndefined(); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/transactions.test.ts new file mode 100644 index 000000000000..fe9dc4b609b9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/tests/transactions.test.ts @@ -0,0 +1,190 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends a transaction for a successful route', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /test-success' + ); + }); + + await request.get(`${baseURL}/test-success`); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent).toEqual( + expect.objectContaining({ + transaction: 'GET /test-success', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); + + expect(transactionEvent.contexts?.trace).toEqual( + expect.objectContaining({ + op: 'http.server', + status: 'ok', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + span_id: expect.stringMatching(/[a-f0-9]{16}/), + }), + ); +}); + +test('Sends a transaction with parameterized route name', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-param/:param' + ); + }); + + await request.get(`${baseURL}/test-param/123`); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.transaction).toBe('GET /test-param/:param'); + expect(transactionEvent.transaction_info?.source).toBe('route'); +}); + +test('Sends a transaction with multiple parameterized segments', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-multi-param/:param1/:param2' + ); + }); + + await request.get(`${baseURL}/test-multi-param/foo/bar`); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.transaction).toBe('GET /test-multi-param/:param1/:param2'); + expect(transactionEvent.transaction_info?.source).toBe('route'); +}); + +test('Sends a transaction for an errored route', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-exception/:id' + ); + }); + + await request.get(`${baseURL}/test-exception/777`); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.transaction).toBe('GET /test-exception/:id'); + expect(transactionEvent.contexts?.trace?.status).toBe('internal_error'); +}); + +test('Includes manually started spans with parent-child relationship', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-transaction' + ); + }); + + await request.get(`${baseURL}/test-transaction`); + + const transactionEvent = await transactionEventPromise; + const spans = transactionEvent.spans || []; + + const testSpan = spans.find(span => span.description === 'test-span'); + const childSpan = spans.find(span => span.description === 'child-span'); + + expect(testSpan).toEqual( + expect.objectContaining({ + description: 'test-span', + origin: 'manual', + }), + ); + + expect(childSpan).toEqual( + expect.objectContaining({ + description: 'child-span', + origin: 'manual', + parent_span_id: testSpan?.span_id, + }), + ); +}); + +test('Creates lifecycle spans for Elysia hooks', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'GET /test-success' + ); + }); + + await request.get(`${baseURL}/test-success`); + + const transactionEvent = await transactionEventPromise; + const spans = transactionEvent.spans || []; + + // Elysia should produce lifecycle spans enriched with sentry attributes + const elysiaSpans = spans.filter(span => span.origin === 'auto.http.otel.elysia'); + expect(elysiaSpans.length).toBeGreaterThan(0); + + // The Handle span should be present as a request handler + expect(spans).toContainEqual( + expect.objectContaining({ + description: 'Handle', + op: 'request_handler.elysia', + origin: 'auto.http.otel.elysia', + }), + ); +}); + +test('Creates lifecycle spans for route-specific middleware', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /with-middleware/test' + ); + }); + + await request.get(`${baseURL}/with-middleware/test`); + + const transactionEvent = await transactionEventPromise; + const spans = transactionEvent.spans || []; + + // BeforeHandle span should be present from the route-specific middleware + expect(spans).toContainEqual( + expect.objectContaining({ + description: 'BeforeHandle', + op: 'middleware.elysia', + origin: 'auto.http.otel.elysia', + }), + ); +}); + +test('Captures request metadata for POST requests', async ({ baseURL, request }) => { + const transactionEventPromise = waitForTransaction('bun-elysia', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && transactionEvent?.transaction === 'POST /test-post' + ); + }); + + const response = await request.post(`${baseURL}/test-post`, { + data: { foo: 'bar', other: 1 }, + headers: { 'Content-Type': 'application/json' }, + }); + const resBody = await response.json(); + + expect(resBody).toEqual({ status: 'ok', body: { foo: 'bar', other: 1 } }); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.request).toEqual( + expect.objectContaining({ + method: 'POST', + url: expect.stringContaining('/test-post'), + headers: expect.objectContaining({ + 'content-type': 'application/json', + }), + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/bun-elysia/tsconfig.json b/dev-packages/e2e-tests/test-applications/bun-elysia/tsconfig.json new file mode 100644 index 000000000000..869427e44d4d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/bun-elysia/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "types": ["bun-types"], + "esModuleInterop": true, + "lib": ["es2020"], + "strict": true, + "outDir": "dist", + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] +} diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 17c6f714c499..1ddb8e57c0b3 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -41,6 +41,8 @@ const DEPENDENTS: Dependent[] = [ ignoreExports: [ // Not needed for Astro 'setupFastifyErrorHandler', + 'elysiaIntegration', + 'withElysia', ], }, { @@ -75,6 +77,8 @@ const DEPENDENTS: Dependent[] = [ ignoreExports: [ // Not needed for Serverless 'setupFastifyErrorHandler', + 'elysiaIntegration', + 'withElysia', ], }, { @@ -84,6 +88,8 @@ const DEPENDENTS: Dependent[] = [ ignoreExports: [ // Not needed for Serverless 'setupFastifyErrorHandler', + 'elysiaIntegration', + 'withElysia', ], }, { diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 6e57ee2ea812..df4181565e13 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -74,6 +74,12 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/elysia': + access: $all + publish: $all + unpublish: $all + # proxy: npmjs # Don't proxy for E2E tests! + '@sentry/ember': access: $all publish: $all diff --git a/package.json b/package.json index 01c52cfd6a36..e57f96957b05 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "packages/core", "packages/cloudflare", "packages/deno", + "packages/elysia", "packages/ember", "packages/eslint-config-sdk", "packages/eslint-plugin-sdk", diff --git a/packages/elysia/.eslintrc.js b/packages/elysia/.eslintrc.js new file mode 100644 index 000000000000..6da218bd8641 --- /dev/null +++ b/packages/elysia/.eslintrc.js @@ -0,0 +1,9 @@ +module.exports = { + env: { + node: true, + }, + extends: ['../../.eslintrc.js'], + rules: { + '@sentry-internal/sdk/no-class-field-initializers': 'off', + }, +}; diff --git a/packages/elysia/LICENSE b/packages/elysia/LICENSE new file mode 100644 index 000000000000..b3c4b18a6317 --- /dev/null +++ b/packages/elysia/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Functional Software, Inc. dba Sentry + +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/elysia/README.md b/packages/elysia/README.md new file mode 100644 index 000000000000..29143f36cb63 --- /dev/null +++ b/packages/elysia/README.md @@ -0,0 +1,34 @@ +

+ + Sentry + +

+ +# Official Sentry SDK for Elysia + +[![npm version](https://img.shields.io/npm/v/@sentry/elysia.svg)](https://www.npmjs.com/package/@sentry/elysia) +[![npm dm](https://img.shields.io/npm/dm/@sentry/elysia.svg)](https://www.npmjs.com/package/@sentry/elysia) +[![npm dt](https://img.shields.io/npm/dt/@sentry/elysia.svg)](https://www.npmjs.com/package/@sentry/elysia) + +> **Alpha**: This SDK is in alpha stage and may have breaking changes in future releases. + +## Links + +- [Official SDK Docs](https://docs.sentry.io/platforms/javascript/guides/elysia/) +- [TypeDoc](http://getsentry.github.io/sentry-javascript/) + +## Usage + +```javascript +import * as Sentry from '@sentry/elysia'; +import { Elysia } from 'elysia'; + +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); + +const app = Sentry.withElysia(new Elysia()) + .get('/', () => 'Hello World') + .listen(3000); +``` diff --git a/packages/elysia/package.json b/packages/elysia/package.json new file mode 100644 index 000000000000..43d14ab6ec94 --- /dev/null +++ b/packages/elysia/package.json @@ -0,0 +1,75 @@ +{ + "name": "@sentry/elysia", + "version": "10.42.0", + "description": "Official Sentry SDK for Elysia", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/elysia", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "files": [ + "/build" + ], + "main": "build/cjs/index.js", + "module": "build/esm/index.js", + "types": "build/types/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./build/types/index.d.ts", + "default": "./build/esm/index.js" + }, + "require": { + "types": "./build/types/index.d.ts", + "default": "./build/cjs/index.js" + } + } + }, + "typesVersions": { + "<5.0": { + "build/types/index.d.ts": [ + "build/types-ts3.8/index.d.ts" + ] + } + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@elysiajs/opentelemetry": "^1.4.0", + "@sentry/bun": "10.42.0", + "@sentry/core": "10.42.0", + "@sentry/node": "10.42.0", + "elysia": "^1.4.0" + }, + "devDependencies": { + "bun-types": "^1.2.9" + }, + "scripts": { + "build": "run-p build:transpile build:types", + "build:dev": "yarn build", + "build:transpile": "rollup -c rollup.npm.config.mjs", + "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:dev:watch": "yarn build:watch", + "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", + "build:tarball": "npm pack", + "circularDepCheck": "madge --circular src/index.ts", + "clean": "rimraf build coverage sentry-elysia-*.tgz", + "fix": "eslint . --format stylish --fix", + "lint": "eslint . --format stylish", + "lint:es-compatibility": "es-check es2022 ./build/cjs/*.js && es-check es2022 ./build/esm/*.js --module", + "test": "vitest run", + "test:watch": "vitest --watch", + "yalc:publish": "yalc publish --push --sig" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false +} diff --git a/packages/elysia/rollup.npm.config.mjs b/packages/elysia/rollup.npm.config.mjs new file mode 100644 index 000000000000..84a06f2fb64a --- /dev/null +++ b/packages/elysia/rollup.npm.config.mjs @@ -0,0 +1,3 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; + +export default makeNPMConfigVariants(makeBaseNPMConfig()); diff --git a/packages/elysia/src/index.ts b/packages/elysia/src/index.ts new file mode 100644 index 000000000000..8a5c5e622de1 --- /dev/null +++ b/packages/elysia/src/index.ts @@ -0,0 +1,195 @@ +// Re-export everything from @sentry/bun +export { + addEventProcessor, + addBreadcrumb, + addIntegration, + captureException, + captureEvent, + captureMessage, + captureCheckIn, + captureFeedback, + startSession, + captureSession, + endSession, + withMonitor, + createTransport, + getClient, + isInitialized, + isEnabled, + generateInstrumentOnce, + getCurrentScope, + getGlobalScope, + getIsolationScope, + getTraceData, + getTraceMetaTags, + setCurrentClient, + Scope, + SDK_VERSION, + setContext, + setConversationId, + setExtra, + setExtras, + setTag, + setTags, + setUser, + getSpanStatusFromHttpCode, + setHttpStatus, + withScope, + withIsolationScope, + makeNodeTransport, + NodeClient, + defaultStackParser, + lastEventId, + flush, + close, + getSentryRelease, + createGetModuleFromFilename, + createLangChainCallbackHandler, + httpHeadersToSpanAttributes, + winterCGHeadersToDict, + // eslint-disable-next-line deprecation/deprecation + anrIntegration, + // eslint-disable-next-line deprecation/deprecation + disableAnrDetectionForCallback, + consoleIntegration, + httpIntegration, + httpServerIntegration, + httpServerSpansIntegration, + nativeNodeFetchIntegration, + onUncaughtExceptionIntegration, + onUnhandledRejectionIntegration, + openAIIntegration, + langChainIntegration, + langGraphIntegration, + modulesIntegration, + contextLinesIntegration, + nodeContextIntegration, + localVariablesIntegration, + requestDataIntegration, + fsIntegration, + functionToStringIntegration, + // eslint-disable-next-line deprecation/deprecation + inboundFiltersIntegration, + eventFiltersIntegration, + linkedErrorsIntegration, + setMeasurement, + getActiveSpan, + startSpan, + startInactiveSpan, + startSpanManual, + startNewTrace, + suppressTracing, + withActiveSpan, + getRootSpan, + getSpanDescendants, + continueTrace, + getAutoPerformanceIntegrations, + cron, + parameterize, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, + dataloaderIntegration, + expressIntegration, + expressErrorHandler, + setupExpressErrorHandler, + fastifyIntegration, + setupFastifyErrorHandler, + firebaseIntegration, + koaIntegration, + setupKoaErrorHandler, + connectIntegration, + setupConnectErrorHandler, + genericPoolIntegration, + graphqlIntegration, + knexIntegration, + kafkaIntegration, + lruMemoizerIntegration, + mongoIntegration, + mongooseIntegration, + mysqlIntegration, + mysql2Integration, + redisIntegration, + tediousIntegration, + postgresIntegration, + postgresJsIntegration, + prismaIntegration, + processSessionIntegration, + hapiIntegration, + setupHapiErrorHandler, + honoIntegration, + setupHonoErrorHandler, + spotlightIntegration, + initOpenTelemetry, + spanToJSON, + spanToTraceHeader, + spanToBaggageHeader, + trpcMiddleware, + updateSpanName, + supabaseIntegration, + instrumentSupabaseClient, + instrumentOpenAiClient, + instrumentAnthropicAiClient, + instrumentGoogleGenAIClient, + instrumentLangGraph, + instrumentStateGraphCompile, + zodErrorsIntegration, + profiler, + amqplibIntegration, + anthropicAIIntegration, + googleGenAIIntegration, + vercelAIIntegration, + logger, + consoleLoggingIntegration, + createConsolaReporter, + createSentryWinstonTransport, + wrapMcpServerWithSentry, + featureFlagsIntegration, + launchDarklyIntegration, + growthbookIntegration, + buildLaunchDarklyFlagUsedHandler, + openFeatureIntegration, + OpenFeatureIntegrationHook, + statsigIntegration, + unleashIntegration, + metrics, + bunServerIntegration, + makeFetchTransport, +} from '@sentry/bun'; + +export type { + Breadcrumb, + BreadcrumbHint, + PolymorphicRequest, + RequestEventData, + SdkInfo, + Event, + EventHint, + ErrorEvent, + Exception, + Session, + SeverityLevel, + Span, + StackFrame, + Stacktrace, + Thread, + User, + FeatureFlagsIntegration, + Metric, + ExclusiveEventHintOrCaptureContext, + CaptureContext, +} from '@sentry/core'; + +export { + captureConsoleIntegration, + dedupeIntegration, + extraErrorDataIntegration, + rewriteFramesIntegration, +} from '@sentry/core'; + +export type { ElysiaOptions } from './types'; + +// Elysia-specific exports +export { withElysia } from './withElysia'; +export { getDefaultIntegrations, init } from './sdk'; diff --git a/packages/elysia/src/sdk.ts b/packages/elysia/src/sdk.ts new file mode 100644 index 000000000000..17318dc1e514 --- /dev/null +++ b/packages/elysia/src/sdk.ts @@ -0,0 +1,61 @@ +import * as os from 'node:os'; +import type { NodeClient } from '@sentry/bun'; +import { + bunServerIntegration, + getDefaultIntegrations as getBunDefaultIntegrations, + makeFetchTransport, +} from '@sentry/bun'; +import type { Integration, Options } from '@sentry/core'; +import { applySdkMetadata } from '@sentry/core'; +import { init as initNode } from '@sentry/node'; +import type { ElysiaOptions } from './types'; + +/** Get the default integrations for the Elysia SDK. */ +export function getDefaultIntegrations(_options: Options): Integration[] { + // Filter out bunServerIntegration + // Elysia already produces an HTTP server span, so we don't need Bun's competing root span. + return getBunDefaultIntegrations(_options).filter(i => i.name !== bunServerIntegration().name); +} + +/** + * Get the runtime name and version. + */ +function getRuntime(): { name: string; version: string } { + if (typeof Bun !== 'undefined') { + return { name: 'bun', version: Bun.version }; + } + + return { name: 'node', version: process.version }; +} + +/** + * Initializes the Sentry Elysia SDK. + * + * @example + * ```javascript + * import * as Sentry from '@sentry/elysia'; + * + * Sentry.init({ + * dsn: '__DSN__', + * tracesSampleRate: 1.0, + * }); + * ``` + */ +export function init(userOptions: ElysiaOptions = {}): NodeClient | undefined { + applySdkMetadata(userOptions, 'elysia'); + + const options = { + ...userOptions, + platform: 'javascript', + runtime: getRuntime(), + serverName: userOptions.serverName || global.process.env.SENTRY_NAME || os.hostname(), + }; + + options.transport = options.transport || makeFetchTransport; + + if (options.defaultIntegrations === undefined) { + options.defaultIntegrations = getDefaultIntegrations(options); + } + + return initNode(options); +} diff --git a/packages/elysia/src/types.ts b/packages/elysia/src/types.ts new file mode 100644 index 000000000000..78b4afd14e87 --- /dev/null +++ b/packages/elysia/src/types.ts @@ -0,0 +1,6 @@ +import type { BunOptions } from '@sentry/bun'; + +/** + * Configuration options for the Sentry Elysia SDK. + */ +export type ElysiaOptions = BunOptions; diff --git a/packages/elysia/src/withElysia.ts b/packages/elysia/src/withElysia.ts new file mode 100644 index 000000000000..9106762b7bbc --- /dev/null +++ b/packages/elysia/src/withElysia.ts @@ -0,0 +1,159 @@ +import { opentelemetry } from '@elysiajs/opentelemetry'; +import { + captureException, + getClient, + getIsolationScope, + getTraceData, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + spanToJSON, + winterCGHeadersToDict, +} from '@sentry/core'; +import type { Elysia, ErrorContext } from 'elysia'; + +interface ElysiaHandlerOptions { + shouldHandleError: (context: ErrorContext) => boolean; +} + +const ELYSIA_ORIGIN = 'auto.http.otel.elysia'; + +let isClientHooksSetup = false; +const emptySpanIds = new Set(); + +const ELYSIA_LIFECYCLE_OP_MAP: Record = { + Request: 'middleware.elysia', + Parse: 'middleware.elysia', + Transform: 'middleware.elysia', + BeforeHandle: 'middleware.elysia', + Handle: 'request_handler.elysia', + AfterHandle: 'middleware.elysia', + MapResponse: 'middleware.elysia', + AfterResponse: 'middleware.elysia', + Error: 'middleware.elysia', +}; + +function defaultShouldHandleError(context: ErrorContext): boolean { + const status = context.set.status; + if (status === undefined) { + return true; + } + const statusCode = typeof status === 'string' ? parseInt(status, 10) : status; + // Capture server errors (5xx) and unusual status codes (<= 299 in an error handler). + // 3xx and 4xx are not captured by default (client errors / redirects). + return statusCode >= 500 || statusCode <= 299; +} + +/** + * Integrate Sentry with an Elysia app for error handling, request context, + * and tracing. Returns the app instance for chaining. + * + * Should be called at the **start** of the chain before defining routes. + * + * @param app The Elysia instance + * @param options Configuration options + * @returns The same Elysia instance for chaining + * + * @example + * ```javascript + * import * as Sentry from '@sentry/elysia'; + * import { Elysia } from 'elysia'; + * + * Sentry.withElysia(new Elysia()) + * .get('/', () => 'Hello World') + * .listen(3000); + * ``` + */ +export function withElysia(app: T, options?: Partial): T { + // Register the opentelemetry plugin + // https://elysiajs.com/plugins/opentelemetry + app.use(opentelemetry()); + + if (!isClientHooksSetup) { + const client = getClient(); + if (client) { + isClientHooksSetup = true; + + // Enrich Elysia lifecycle spans with semantic op and origin, + // and mark empty spans that Elysia produces as children of lifecycle spans. + client.on('spanEnd', span => { + const spanData = spanToJSON(span); + + // Elysia produces empty spans for each function handler + // users usually use arrow functions for handlers so they will show up as + // here we drop them so they don't clutter the transaction, if they get named by the user + // they will still show up as the name of the function + if (!spanData.description && (!spanData.data || Object.keys(spanData.data).length === 0)) { + emptySpanIds.add(spanData.span_id); + return; + } + + // Enrich Elysia lifecycle spans with semantic op and origin. + // We mutate the attributes directly because the span has already ended + // and `setAttribute()` is a no-op on ended OTel spans. + const op = ELYSIA_LIFECYCLE_OP_MAP[spanData.description || '']; + if (op && spanData.data) { + const attrs = spanData.data; + attrs[SEMANTIC_ATTRIBUTE_SENTRY_OP] = op; + attrs[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] = ELYSIA_ORIGIN; + } + }); + + // Filter out the empty spans we marked above before sending the transaction, + // and delete matched IDs individually to avoid clearing IDs from concurrent transactions. + client.on('beforeSendEvent', event => { + if (event.type === 'transaction' && event.spans) { + event.spans = event.spans.filter(span => { + if (!emptySpanIds.has(span.span_id)) { + return true; + } + + emptySpanIds.delete(span.span_id); + + return false; + }); + } + }); + } + } + + // Set SDK processing metadata for all requests + app.onRequest(context => { + getIsolationScope().setSDKProcessingMetadata({ + normalizedRequest: { + method: context.request.method, + url: context.request.url, + headers: winterCGHeadersToDict(context.request.headers), + }, + }); + }); + + // Propagate trace data to all response headers + app.onAfterHandle({ as: 'global' }, context => { + const traceData = getTraceData(); + if (traceData['sentry-trace']) { + context.set.headers['sentry-trace'] = traceData['sentry-trace']; + } + if (traceData.baggage) { + context.set.headers['baggage'] = traceData.baggage; + } + }); + + // Register the error handler for all routes + app.onError({ as: 'global' }, context => { + if (context.route) { + getIsolationScope().setTransactionName(`${context.request.method} ${context.route}`); + } + + const shouldHandleError = options?.shouldHandleError || defaultShouldHandleError; + if (shouldHandleError(context)) { + captureException(context.error, { + mechanism: { + type: 'elysia', + handled: false, + }, + }); + } + }); + + return app; +} diff --git a/packages/elysia/test/sdk.test.ts b/packages/elysia/test/sdk.test.ts new file mode 100644 index 000000000000..dee537acd008 --- /dev/null +++ b/packages/elysia/test/sdk.test.ts @@ -0,0 +1,126 @@ +import type { Integration } from '@sentry/core'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +const mockApplySdkMetadata = vi.fn(); +const mockInitNode = vi.fn(); +const mockGetBunDefaultIntegrations = vi.fn(() => [] as Integration[]); +const mockMakeFetchTransport = vi.fn(); + +vi.mock('@sentry/core', async importActual => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + applySdkMetadata: mockApplySdkMetadata, + }; +}); + +vi.mock('@sentry/node', () => ({ + init: mockInitNode, +})); + +vi.mock('@sentry/bun', () => ({ + getDefaultIntegrations: mockGetBunDefaultIntegrations, + makeFetchTransport: mockMakeFetchTransport, + bunServerIntegration: () => ({ name: 'BunServer', setupOnce: vi.fn() }), +})); + +// Must import after mocks are set up +// @ts-expect-error - dynamic import +const { init, getDefaultIntegrations } = await import('../src/sdk'); + +describe('init', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('sets SDK metadata to elysia', () => { + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); + + expect(mockApplySdkMetadata).toHaveBeenCalledWith( + expect.objectContaining({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }), + 'elysia', + ); + }); + + it('calls initNode with the options', () => { + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); + + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0', + platform: 'javascript', + }), + ); + }); + + it('uses makeFetchTransport by default', () => { + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); + + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + transport: mockMakeFetchTransport, + }), + ); + }); + + it('allows overriding transport', () => { + const customTransport = vi.fn(); + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0', transport: customTransport }); + + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + transport: customTransport, + }), + ); + }); + + it('sets default integrations from bun and filters out BunServer', () => { + const mockIntegration = { name: 'MockIntegration', setupOnce: vi.fn() }; + const bunServerMock = { name: 'BunServer', setupOnce: vi.fn() }; + mockGetBunDefaultIntegrations.mockReturnValueOnce([mockIntegration, bunServerMock]); + + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); + + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + defaultIntegrations: [mockIntegration], + }), + ); + }); + + it('does not override user-provided defaultIntegrations', () => { + const userIntegrations = [{ name: 'UserIntegration', setupOnce: vi.fn() }]; + + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0', defaultIntegrations: userIntegrations }); + + expect(mockInitNode).toHaveBeenCalledWith( + expect.objectContaining({ + defaultIntegrations: userIntegrations, + }), + ); + expect(mockGetBunDefaultIntegrations).not.toHaveBeenCalled(); + }); + + it('detects runtime correctly', () => { + init({ dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0' }); + + const calledOptions = mockInitNode.mock.calls[0]![0]; + // In vitest (Node), Bun is not defined, so runtime should be node + expect(calledOptions.runtime.name).toBe('node'); + expect(calledOptions.runtime.version).toBe(process.version); + }); +}); + +describe('getDefaultIntegrations', () => { + it('returns bun default integrations without BunServer', () => { + const mockIntegration = { name: 'MockIntegration', setupOnce: vi.fn() }; + const bunServerMock = { name: 'BunServer', setupOnce: vi.fn() }; + mockGetBunDefaultIntegrations.mockReturnValueOnce([mockIntegration, bunServerMock]); + + const integrations = getDefaultIntegrations({}); + + expect(integrations).toEqual([mockIntegration]); + expect(mockGetBunDefaultIntegrations).toHaveBeenCalledWith({}); + }); +}); diff --git a/packages/elysia/test/withElysia.test.ts b/packages/elysia/test/withElysia.test.ts new file mode 100644 index 000000000000..acc4b66e13cf --- /dev/null +++ b/packages/elysia/test/withElysia.test.ts @@ -0,0 +1,178 @@ +import type { ErrorContext } from 'elysia'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +// Capture the handlers registered by withElysia +let onAfterHandleHandler: (context: unknown) => void; +let onErrorHandler: (context: unknown) => void; + +const mockApp = { + use: vi.fn().mockReturnThis(), + onRequest: vi.fn(() => { + return mockApp; + }), + onAfterHandle: vi.fn((_opts: unknown, handler: (context: unknown) => void) => { + onAfterHandleHandler = handler; + return mockApp; + }), + onError: vi.fn((_opts: unknown, handler: (context: unknown) => void) => { + onErrorHandler = handler; + return mockApp; + }), +}; + +const mockCaptureException = vi.fn(); +const mockGetIsolationScope = vi.fn(() => ({ + setSDKProcessingMetadata: vi.fn(), + setTransactionName: vi.fn(), +})); +const mockGetClient = vi.fn(() => ({ + on: vi.fn(), +})); +const mockGetTraceData = vi.fn(() => ({ + 'sentry-trace': 'abc123-def456-1', + baggage: 'sentry-environment=test,sentry-trace_id=abc123', +})); + +vi.mock('@elysiajs/opentelemetry', () => ({ + opentelemetry: vi.fn(() => 'otel-plugin'), +})); + +vi.mock('@sentry/core', async importActual => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actual = await importActual(); + return { + ...actual, + captureException: (...args: unknown[]) => mockCaptureException(...args), + getIsolationScope: () => mockGetIsolationScope(), + getClient: () => mockGetClient(), + getTraceData: () => mockGetTraceData(), + }; +}); + +// @ts-expect-error - dynamic import after mocks +const { withElysia } = await import('../src/withElysia'); + +describe('withElysia', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('registers opentelemetry plugin', () => { + // @ts-expect-error - mock app + withElysia(mockApp); + expect(mockApp.use).toHaveBeenCalledWith('otel-plugin'); + }); + + it('registers onRequest, onAfterHandle, and onError hooks', () => { + // @ts-expect-error - mock app + withElysia(mockApp); + expect(mockApp.onRequest).toHaveBeenCalled(); + expect(mockApp.onAfterHandle).toHaveBeenCalledWith({ as: 'global' }, expect.any(Function)); + expect(mockApp.onError).toHaveBeenCalledWith({ as: 'global' }, expect.any(Function)); + }); + + it('returns the app instance for chaining', () => { + // @ts-expect-error - mock app + const result = withElysia(mockApp); + expect(result).toBe(mockApp); + }); + + describe('response trace headers', () => { + it('injects sentry-trace and baggage into response headers', () => { + // @ts-expect-error - mock app + withElysia(mockApp); + const headers: Record = {}; + onAfterHandleHandler({ set: { headers } }); + + expect(headers['sentry-trace']).toBe('abc123-def456-1'); + expect(headers['baggage']).toBe('sentry-environment=test,sentry-trace_id=abc123'); + }); + + it('does not set headers when trace data is empty', () => { + mockGetTraceData.mockReturnValueOnce({}); + // @ts-expect-error - mock app + withElysia(mockApp); + const headers: Record = {}; + onAfterHandleHandler({ set: { headers } }); + + expect(headers['sentry-trace']).toBeUndefined(); + expect(headers['baggage']).toBeUndefined(); + }); + }); + + describe('defaultShouldHandleError', () => { + function triggerError(status: number | string | undefined): void { + // @ts-expect-error - mock app + withElysia(mockApp); + onErrorHandler({ + route: '/test', + request: { method: 'GET' }, + error: new Error('test'), + set: { status }, + } as unknown as ErrorContext); + } + + it('captures errors with status >= 500', () => { + triggerError(500); + expect(mockCaptureException).toHaveBeenCalled(); + }); + + it('captures errors with status 503', () => { + triggerError(503); + expect(mockCaptureException).toHaveBeenCalled(); + }); + + it('captures errors with undefined status', () => { + triggerError(undefined); + expect(mockCaptureException).toHaveBeenCalled(); + }); + + it('captures errors with status <= 299 (unusual in error handler)', () => { + triggerError(200); + expect(mockCaptureException).toHaveBeenCalled(); + }); + + it('does not capture 4xx errors', () => { + triggerError(400); + expect(mockCaptureException).not.toHaveBeenCalled(); + }); + + it('does not capture 404 errors', () => { + triggerError(404); + expect(mockCaptureException).not.toHaveBeenCalled(); + }); + + it('does not capture 3xx responses', () => { + triggerError(302); + expect(mockCaptureException).not.toHaveBeenCalled(); + }); + + it('handles string status codes', () => { + triggerError('500'); + expect(mockCaptureException).toHaveBeenCalled(); + }); + + it('does not capture string 4xx status codes', () => { + triggerError('400'); + expect(mockCaptureException).not.toHaveBeenCalled(); + }); + }); + + describe('custom shouldHandleError', () => { + it('uses custom shouldHandleError when provided', () => { + const customShouldHandle = vi.fn(() => false); + // @ts-expect-error - mock app + withElysia(mockApp, { shouldHandleError: customShouldHandle }); + + onErrorHandler({ + route: '/test', + request: { method: 'GET' }, + error: new Error('test'), + set: { status: 500 }, + } as unknown as ErrorContext); + + expect(customShouldHandle).toHaveBeenCalled(); + expect(mockCaptureException).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/elysia/tsconfig.json b/packages/elysia/tsconfig.json new file mode 100644 index 000000000000..dcbef254b942 --- /dev/null +++ b/packages/elysia/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + + "include": ["src/**/*"], + + "compilerOptions": { + // package-specific options + "types": ["bun-types"] + } +} diff --git a/packages/elysia/tsconfig.test.json b/packages/elysia/tsconfig.test.json new file mode 100644 index 000000000000..4cbbffaccbbc --- /dev/null +++ b/packages/elysia/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": ["bun-types"] + + // other package-specific, test-specific options + } +} diff --git a/packages/elysia/tsconfig.types.json b/packages/elysia/tsconfig.types.json new file mode 100644 index 000000000000..65455f66bd75 --- /dev/null +++ b/packages/elysia/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/types" + } +} diff --git a/packages/node-core/src/utils/ensureIsWrapped.ts b/packages/node-core/src/utils/ensureIsWrapped.ts index 921d01da8207..3d7e34c428fd 100644 --- a/packages/node-core/src/utils/ensureIsWrapped.ts +++ b/packages/node-core/src/utils/ensureIsWrapped.ts @@ -9,7 +9,7 @@ import { isCjs } from './detection'; */ export function ensureIsWrapped( maybeWrappedFunction: unknown, - name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa' | 'hono', + name: 'express' | 'connect' | 'fastify' | 'hapi' | 'koa' | 'hono' | 'elysia', ): void { const clientOptions = getClient()?.getOptions(); if ( diff --git a/yarn.lock b/yarn.lock index 0802c0c39941..80b951af68c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3269,6 +3269,15 @@ dependencies: "@edge-runtime/primitives" "6.0.0" +"@elysiajs/opentelemetry@^1.4.0": + version "1.4.10" + resolved "https://registry.yarnpkg.com/@elysiajs/opentelemetry/-/opentelemetry-1.4.10.tgz#1c107d9071fbc284737f7b47cdf15554ef075521" + integrity sha512-2GH187Rr3n3Rq+R7fogn/jcmdwWk9OMtbYhnaJg5ydiLOJvtrztDp0p+zbyGFG2gspx8U9vpaCvSJ69Aq1zZkA== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@opentelemetry/instrumentation" "^0.200.0" + "@opentelemetry/sdk-node" "^0.200.0" + "@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" @@ -4633,6 +4642,24 @@ dependencies: dom-mutator "^0.6.0" +"@grpc/grpc-js@^1.7.1": + version "1.14.3" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.14.3.tgz#4c9b817a900ae4020ddc28515ae4b52c78cfb8da" + integrity sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA== + dependencies: + "@grpc/proto-loader" "^0.8.0" + "@js-sdsl/ordered-map" "^4.4.2" + +"@grpc/proto-loader@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.8.0.tgz#b6c324dd909c458a0e4aa9bfd3d69cf78a4b9bd8" + integrity sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.5.3" + yargs "^17.7.2" + "@handlebars/parser@~2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-2.0.0.tgz#5e8b7298f31ff8f7b260e6b7363c7e9ceed7d9c5" @@ -5202,6 +5229,11 @@ resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-5.6.3.tgz#41ae1c07de1ebe0f6dde1abcbc9700a09b9c6056" integrity sha512-T1rRxzdqkEXcou0ZprN1q9yDRlvzCPLqmlNt5IIsGBzoEVgLCCYrKEwc84+TvsXuAc95VAZwtWD2zVsKPY4bcA== +"@js-sdsl/ordered-map@^4.4.2": + version "4.4.2" + resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c" + integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw== + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -6175,6 +6207,13 @@ dependencies: "@octokit/openapi-types" "^12.11.0" +"@opentelemetry/api-logs@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.200.0.tgz#f9015fd844920c13968715b3cdccf5a4d4ff907e" + integrity sha512-IKJBQxh91qJ+3ssRly5hYEJ8NDHu9oY/B1PXVSCWf7zytmYO9RNLB0Ox9XQ/fJ8m6gY6Q6NtBWlmXfaXt5Uc4Q== + dependencies: + "@opentelemetry/api" "^1.3.0" + "@opentelemetry/api-logs@0.207.0": version "0.207.0" resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.207.0.tgz#ae991c51eedda55af037a3e6fc1ebdb12b289f49" @@ -6201,10 +6240,22 @@ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== +"@opentelemetry/context-async-hooks@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.0.0.tgz#c98a727238ca199cda943780acf6124af8d8cd80" + integrity sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA== + "@opentelemetry/context-async-hooks@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz#457b8f9c1e219bf6e22b549d90f773db0a38fe06" - integrity sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw== + version "2.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz#6c824e900630b378233c1a78ca7f0dc5a3b460b2" + integrity sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q== + +"@opentelemetry/core@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.0.0.tgz#37e9f0e9ddec4479b267aca6f32d88757c941b3a" + integrity sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ== + dependencies: + "@opentelemetry/semantic-conventions" "^1.29.0" "@opentelemetry/core@2.5.0": version "2.5.0" @@ -6213,13 +6264,140 @@ dependencies: "@opentelemetry/semantic-conventions" "^1.29.0" -"@opentelemetry/core@2.5.1", "@opentelemetry/core@^2.0.0", "@opentelemetry/core@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.5.1.tgz#b5d830ab499bc13e29f6efa88a165630f25d2ad2" - integrity sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA== +"@opentelemetry/core@2.6.0", "@opentelemetry/core@^2.0.0", "@opentelemetry/core@^2.5.1": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-2.6.0.tgz#719c829ed98bd7af808a2d2c83374df1fd1f3c66" + integrity sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg== dependencies: "@opentelemetry/semantic-conventions" "^1.29.0" +"@opentelemetry/exporter-logs-otlp-grpc@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-grpc/-/exporter-logs-otlp-grpc-0.200.0.tgz#693e0f7041c533061d0689ab43d64d039078ee7a" + integrity sha512-+3MDfa5YQPGM3WXxW9kqGD85Q7s9wlEMVNhXXG7tYFLnIeaseUt9YtCeFhEDFzfEktacdFpOtXmJuNW8cHbU5A== + dependencies: + "@grpc/grpc-js" "^1.7.1" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-grpc-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/sdk-logs" "0.200.0" + +"@opentelemetry/exporter-logs-otlp-http@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.200.0.tgz#3a99c9554f871b5c6cddb8716316c125d4edca6c" + integrity sha512-KfWw49htbGGp9s8N4KI8EQ9XuqKJ0VG+yVYVYFiCYSjEV32qpQ5qZ9UZBzOZ6xRb+E16SXOSCT3RkqBVSABZ+g== + dependencies: + "@opentelemetry/api-logs" "0.200.0" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/sdk-logs" "0.200.0" + +"@opentelemetry/exporter-logs-otlp-proto@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-logs-otlp-proto/-/exporter-logs-otlp-proto-0.200.0.tgz#53573ea43bce4129bcb18bda172a95c6535bb1a2" + integrity sha512-GmahpUU/55hxfH4TP77ChOfftADsCq/nuri73I/AVLe2s4NIglvTsaACkFVZAVmnXXyPS00Fk3x27WS3yO07zA== + dependencies: + "@opentelemetry/api-logs" "0.200.0" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-logs" "0.200.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + +"@opentelemetry/exporter-metrics-otlp-grpc@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.200.0.tgz#f9a4d209083a6a12489c4ae4c20e6923a1780c88" + integrity sha512-uHawPRvKIrhqH09GloTuYeq2BjyieYHIpiklOvxm9zhrCL2eRsnI/6g9v2BZTVtGp8tEgIa7rCQ6Ltxw6NBgew== + dependencies: + "@grpc/grpc-js" "^1.7.1" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/exporter-metrics-otlp-http" "0.200.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-grpc-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-metrics" "2.0.0" + +"@opentelemetry/exporter-metrics-otlp-http@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.200.0.tgz#daa28a2b868bacf02efb153fa8780d078807919e" + integrity sha512-5BiR6i8yHc9+qW7F6LqkuUnIzVNA7lt0qRxIKcKT+gq3eGUPHZ3DY29sfxI3tkvnwMgtnHDMNze5DdxW39HsAw== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-metrics" "2.0.0" + +"@opentelemetry/exporter-metrics-otlp-proto@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.200.0.tgz#5a494e2df8703be2f1f5f01629dfd48a6d39e5a6" + integrity sha512-E+uPj0yyvz81U9pvLZp3oHtFrEzNSqKGVkIViTQY1rH3TOobeJPSpLnTVXACnCwkPR5XeTvPnK3pZ2Kni8AFMg== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/exporter-metrics-otlp-http" "0.200.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-metrics" "2.0.0" + +"@opentelemetry/exporter-prometheus@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-prometheus/-/exporter-prometheus-0.200.0.tgz#8f3dd3a8903447563a5be30ddf9e7bfb1e7ad127" + integrity sha512-ZYdlU9r0USuuYppiDyU2VFRA0kFl855ylnb3N/2aOlXrbA4PMCznen7gmPbetGQu7pz8Jbaf4fwvrDnVdQQXSw== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-metrics" "2.0.0" + +"@opentelemetry/exporter-trace-otlp-grpc@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.200.0.tgz#e259367f324c01342bf3f0175c52d9f4e61a345f" + integrity sha512-hmeZrUkFl1YMsgukSuHCFPYeF9df0hHoKeHUthRKFCxiURs+GwF1VuabuHmBMZnjTbsuvNjOB+JSs37Csem/5Q== + dependencies: + "@grpc/grpc-js" "^1.7.1" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-grpc-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + +"@opentelemetry/exporter-trace-otlp-http@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.200.0.tgz#ddf2bbdff5157a89f64aad6dad44c394872d589d" + integrity sha512-Goi//m/7ZHeUedxTGVmEzH19NgqJY+Bzr6zXo1Rni1+hwqaksEyJ44gdlEMREu6dzX1DlAaH/qSykSVzdrdafA== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + +"@opentelemetry/exporter-trace-otlp-proto@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.200.0.tgz#f3f149e6bad8c899c8f1e5c58e5d855ce07f7319" + integrity sha512-V9TDSD3PjK1OREw2iT9TUTzNYEVWJk4Nhodzhp9eiz4onDMYmPy3LaGbPv81yIR6dUb/hNp/SIhpiCHwFUq2Vg== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + +"@opentelemetry/exporter-zipkin@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.0.0.tgz#6aca658d64f5e8bc079b07ee0a3076c4ca328ec9" + integrity sha512-icxaKZ+jZL/NHXX8Aru4HGsrdhK0MLcuRXkX5G5IRmCgoRLw+Br6I/nMVozX2xjGGwV7hw2g+4Slj8K7s4HbVg== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + "@opentelemetry/instrumentation-amqplib@0.58.0": version "0.58.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.58.0.tgz#e3dc86ebfa7d72fe861a63b1c24a062faeb64a8c" @@ -6428,6 +6606,17 @@ "@opentelemetry/instrumentation" "^0.211.0" "@opentelemetry/semantic-conventions" "^1.24.0" +"@opentelemetry/instrumentation@0.200.0", "@opentelemetry/instrumentation@^0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.200.0.tgz#29d1d4f70cbf0cb1ca9f2f78966379b0be96bddc" + integrity sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg== + dependencies: + "@opentelemetry/api-logs" "0.200.0" + "@types/shimmer" "^1.2.0" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + shimmer "^1.2.1" + "@opentelemetry/instrumentation@0.211.0", "@opentelemetry/instrumentation@^0.211.0": version "0.211.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.211.0.tgz#d45e20eafa75b5d3e8a9745a6205332893c55f37" @@ -6455,28 +6644,144 @@ import-in-the-middle "^2.0.0" require-in-the-middle "^8.0.0" +"@opentelemetry/otlp-exporter-base@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.200.0.tgz#906bcf2e59815c8ded732d328f6bc060fb7b0459" + integrity sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-transformer" "0.200.0" + +"@opentelemetry/otlp-grpc-exporter-base@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.200.0.tgz#cfc6cfd4def7d47f84e43d438d75cb463c67bf0d" + integrity sha512-CK2S+bFgOZ66Bsu5hlDeOX6cvW5FVtVjFFbWuaJP0ELxJKBB6HlbLZQ2phqz/uLj1cWap5xJr/PsR3iGoB7Vqw== + dependencies: + "@grpc/grpc-js" "^1.7.1" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/otlp-exporter-base" "0.200.0" + "@opentelemetry/otlp-transformer" "0.200.0" + +"@opentelemetry/otlp-transformer@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/otlp-transformer/-/otlp-transformer-0.200.0.tgz#19afb2274554cb74e2d2b7e32a54a7f7d83c8642" + integrity sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw== + dependencies: + "@opentelemetry/api-logs" "0.200.0" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-logs" "0.200.0" + "@opentelemetry/sdk-metrics" "2.0.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + protobufjs "^7.3.0" + +"@opentelemetry/propagator-b3@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-b3/-/propagator-b3-2.0.0.tgz#1b6244ef2d08a70672521a9aff56e485bd607c17" + integrity sha512-blx9S2EI49Ycuw6VZq+bkpaIoiJFhsDuvFGhBIoH3vJ5oYjJ2U0s3fAM5jYft99xVIAv6HqoPtlP9gpVA2IZtA== + dependencies: + "@opentelemetry/core" "2.0.0" + +"@opentelemetry/propagator-jaeger@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/propagator-jaeger/-/propagator-jaeger-2.0.0.tgz#288d6767dea554db684fd5e144ad8653d83fd2ea" + integrity sha512-Mbm/LSFyAtQKP0AQah4AfGgsD+vsZcyreZoQ5okFBk33hU7AquU4TltgyL9dvaO8/Zkoud8/0gEvwfOZ5d7EPA== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/redis-common@^0.38.2": version "0.38.2" resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz#cefa4f3e79db1cd54f19e233b7dfb56621143955" integrity sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA== -"@opentelemetry/resources@2.5.1", "@opentelemetry/resources@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.5.1.tgz#90ccc27cea02b543f20a7db9834852ec11784c1a" - integrity sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ== +"@opentelemetry/resources@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.0.0.tgz#15c04794c32b7d0b3c7589225ece6ae9bba25989" + integrity sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/resources@2.6.0", "@opentelemetry/resources@^2.5.1": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-2.6.0.tgz#1a945dbb8986043d8b593c358d5d8e3de6becf5a" + integrity sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ== + dependencies: + "@opentelemetry/core" "2.6.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-logs@0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-logs/-/sdk-logs-0.200.0.tgz#893d86cefa6f2c02a7cd03d5cb4a959eed3653d1" + integrity sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA== + dependencies: + "@opentelemetry/api-logs" "0.200.0" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/resources" "2.0.0" + +"@opentelemetry/sdk-metrics@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.0.tgz#aba86060bc363c661ca286339c5b04590e298b69" + integrity sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA== + dependencies: + "@opentelemetry/core" "2.0.0" + "@opentelemetry/resources" "2.0.0" + +"@opentelemetry/sdk-node@^0.200.0": + version "0.200.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-node/-/sdk-node-0.200.0.tgz#033d0641da628f1537cf7442f41cd77c048923ae" + integrity sha512-S/YSy9GIswnhYoDor1RusNkmRughipvTCOQrlF1dzI70yQaf68qgf5WMnzUxdlCl3/et/pvaO75xfPfuEmCK5A== + dependencies: + "@opentelemetry/api-logs" "0.200.0" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/exporter-logs-otlp-grpc" "0.200.0" + "@opentelemetry/exporter-logs-otlp-http" "0.200.0" + "@opentelemetry/exporter-logs-otlp-proto" "0.200.0" + "@opentelemetry/exporter-metrics-otlp-grpc" "0.200.0" + "@opentelemetry/exporter-metrics-otlp-http" "0.200.0" + "@opentelemetry/exporter-metrics-otlp-proto" "0.200.0" + "@opentelemetry/exporter-prometheus" "0.200.0" + "@opentelemetry/exporter-trace-otlp-grpc" "0.200.0" + "@opentelemetry/exporter-trace-otlp-http" "0.200.0" + "@opentelemetry/exporter-trace-otlp-proto" "0.200.0" + "@opentelemetry/exporter-zipkin" "2.0.0" + "@opentelemetry/instrumentation" "0.200.0" + "@opentelemetry/propagator-b3" "2.0.0" + "@opentelemetry/propagator-jaeger" "2.0.0" + "@opentelemetry/resources" "2.0.0" + "@opentelemetry/sdk-logs" "0.200.0" + "@opentelemetry/sdk-metrics" "2.0.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + "@opentelemetry/sdk-trace-node" "2.0.0" + "@opentelemetry/semantic-conventions" "^1.29.0" + +"@opentelemetry/sdk-trace-base@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.0.tgz#ebc06ea7537dea62f3882f8236c1234f4faf6b23" + integrity sha512-qQnYdX+ZCkonM7tA5iU4fSRsVxbFGml8jbxOgipRGMFHKaXKHQ30js03rTobYjKjIfnOsZSbHKWF0/0v0OQGfw== dependencies: - "@opentelemetry/core" "2.5.1" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/resources" "2.0.0" "@opentelemetry/semantic-conventions" "^1.29.0" "@opentelemetry/sdk-trace-base@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz#4f55f37e18ac3f971936d4717b6bfd43cfd72d61" - integrity sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw== + version "2.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz#d7e752a0906f2bcae3c1261e224aef3e3b3746f9" + integrity sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ== dependencies: - "@opentelemetry/core" "2.5.1" - "@opentelemetry/resources" "2.5.1" + "@opentelemetry/core" "2.6.0" + "@opentelemetry/resources" "2.6.0" "@opentelemetry/semantic-conventions" "^1.29.0" +"@opentelemetry/sdk-trace-node@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.0.0.tgz#ef9f8ab77ccb41a9c9ff272f6bf4bb6999491f5b" + integrity sha512-omdilCZozUjQwY3uZRBwbaRMJ3p09l4t187Lsdf0dGMye9WKD4NGcpgZRvqhI1dwcH6og+YXQEtoO9Wx3ykilg== + dependencies: + "@opentelemetry/context-async-hooks" "2.0.0" + "@opentelemetry/core" "2.0.0" + "@opentelemetry/sdk-trace-base" "2.0.0" + "@opentelemetry/semantic-conventions@^1.24.0", "@opentelemetry/semantic-conventions@^1.27.0", "@opentelemetry/semantic-conventions@^1.28.0", "@opentelemetry/semantic-conventions@^1.29.0", "@opentelemetry/semantic-conventions@^1.30.0", "@opentelemetry/semantic-conventions@^1.33.0", "@opentelemetry/semantic-conventions@^1.33.1", "@opentelemetry/semantic-conventions@^1.34.0", "@opentelemetry/semantic-conventions@^1.36.0", "@opentelemetry/semantic-conventions@^1.37.0", "@opentelemetry/semantic-conventions@^1.39.0": version "1.39.0" resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz#f653b2752171411feb40310b8a8953d7e5c543b7" @@ -9505,12 +9810,12 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=18": - version "22.10.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.2.tgz#a485426e6d1fdafc7b0d4c7b24e2c78182ddabb9" - integrity sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ== +"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=13.7.0", "@types/node@>=18": + version "25.3.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.3.3.tgz#605862544ee7ffd7a936bcbf0135a14012f1e549" + integrity sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ== dependencies: - undici-types "~6.20.0" + undici-types "~7.18.0" "@types/node@^14.8.0": version "14.18.63" @@ -9692,6 +9997,11 @@ "@types/mime" "*" "@types/node" "*" +"@types/shimmer@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.2.0.tgz#9b706af96fa06416828842397a70dfbbf1c14ded" + integrity sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg== + "@types/sinon@^17.0.3": version "17.0.3" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" @@ -13161,6 +13471,11 @@ citty@^0.2.0: resolved "https://registry.yarnpkg.com/citty/-/citty-0.2.0.tgz#2426b145c4bf2a2c5c33bd66a634dfe3d5ea67ea" integrity sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA== +cjs-module-lexer@^1.2.2: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz#0f79731eb8cfe1ec72acd4066efac9d61991b00d" + integrity sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q== + cjs-module-lexer@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz#b3ca5101843389259ade7d88c77bd06ce55849ca" @@ -13729,7 +14044,7 @@ cookie@^0.7.1, cookie@^0.7.2, cookie@~0.7.1, cookie@~0.7.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== -cookie@^1.0.1, cookie@^1.0.2: +cookie@^1.0.1, cookie@^1.0.2, cookie@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== @@ -14889,6 +15204,16 @@ electron-to-chromium@^1.5.263: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e" integrity sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A== +elysia@^1.4.0: + version "1.4.27" + resolved "https://registry.yarnpkg.com/elysia/-/elysia-1.4.27.tgz#709f07f54c0d0400aab8a18bac4d94223203ec45" + integrity sha512-2UlmNEjPJVA/WZVPYKy+KdsrfFwwNlqSBW1lHz6i2AHc75k7gV4Rhm01kFeotH7PDiHIX2G8X3KnRPc33SGVIg== + dependencies: + cookie "^1.1.1" + exact-mirror "^0.2.7" + fast-decode-uri-component "^1.0.1" + memoirist "^0.4.0" + ember-auto-import@^2.5.0, ember-auto-import@^2.7.2: version "2.8.1" resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.8.1.tgz#03977e87ce178e6f9e4f89809185ff8f0fee9fcb" @@ -16677,6 +17002,11 @@ events@^3.0.0, events@^3.2.0, events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +exact-mirror@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/exact-mirror/-/exact-mirror-0.2.7.tgz#ee8e75c362a67ca0e07cb13fea92b61adaabfa29" + integrity sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg== + exec-sh@^0.3.2, exec-sh@^0.3.4: version "0.3.6" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" @@ -16946,6 +17276,11 @@ fast-content-type-parse@^3.0.0: resolved "https://registry.yarnpkg.com/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz#5590b6c807cc598be125e6740a9fde589d2b7afb" integrity sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg== +fast-decode-uri-component@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz#46f8b6c22b30ff7a81357d4f59abfae938202543" + integrity sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -18956,6 +19291,16 @@ import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" +import-in-the-middle@^1.8.1: + version "1.15.0" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz#9e20827a322bbadaeb5e3bac49ea8f6d4685fdd8" + integrity sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA== + dependencies: + acorn "^8.14.0" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-in-the-middle@^2.0.0, import-in-the-middle@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz#1972337bfe020d05f6b5e020c13334567436324f" @@ -20658,7 +21003,7 @@ lodash.assign@^3.2.0: lodash._createassigner "^3.0.0" lodash.keys "^3.0.0" -lodash.camelcase@^4.1.1: +lodash.camelcase@^4.1.1, lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= @@ -20872,10 +21217,10 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== -long@^5.2.1: - version "5.2.3" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" - integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== +long@^5.0.0, long@^5.2.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== longest-streak@^3.0.0: version "3.1.0" @@ -21387,6 +21732,11 @@ memfs@^3.4.3: dependencies: fs-monkey "^1.0.4" +memoirist@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/memoirist/-/memoirist-0.4.0.tgz#7677aa70f8c2f7f0791f8af1b689495c8dbc906d" + integrity sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg== + memory-pager@^1.0.2: version "1.5.0" resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" @@ -25207,6 +25557,24 @@ property-information@^7.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== +protobufjs@^7.3.0, protobufjs@^7.5.3: + version "7.5.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" + integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@^2.0.7, proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -26010,6 +26378,15 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-in-the-middle@^7.1.1: + version "7.5.2" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz#dc25b148affad42e570cf0e41ba30dc00f1703ec" + integrity sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ== + dependencies: + debug "^4.3.5" + module-details-from-path "^1.0.3" + resolve "^1.22.8" + require-in-the-middle@^8.0.0: version "8.0.1" resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz#dbde2587f669398626d56b20c868ab87bf01cce4" @@ -27045,6 +27422,11 @@ shikiji@^0.6.8: dependencies: hast-util-to-html "^9.0.0" +shimmer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + side-channel-list@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" @@ -29200,10 +29582,10 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +undici-types@~7.18.0: + version "7.18.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" + integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== undici@7.18.2: version "7.18.2" @@ -30920,7 +31302,7 @@ yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.2.1, yargs@^17.5.1, yargs@^17.6.0, yargs@^17.6.2: +yargs@^17.2.1, yargs@^17.5.1, yargs@^17.6.0, yargs@^17.6.2, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==