diff --git a/.github/actions/build-and-deploy-api/action.yml b/.github/actions/build-and-deploy-api/action.yml index cd8c645e..341e4e19 100644 --- a/.github/actions/build-and-deploy-api/action.yml +++ b/.github/actions/build-and-deploy-api/action.yml @@ -2,12 +2,21 @@ name: 'build-and-deploy-api' description: 'Builds API Project for Production and Deploys it to a given WA Slot' inputs: + releaseVersion: + required: true + description: "Release Version (Commit or Tag)" slot: required: true description: "Slot Identifier" mongoUri: required: true description: "Mongo Connection URI" + sentryKey: + required: true + description: "Sentry DSN Key" + sentryAuthToken: + required: true + description: "Sentry Auth Token" outputs: url: description: "API URL" @@ -19,6 +28,9 @@ runs: - run: envsubst < apps/api/src/.env.template > apps/api/src/.env env: MONGODB_URI: ${{ inputs.mongoUri }} + ENVIRONMENT_NAME: ${{ inputs.slot }} + RELEASE_VERSION: ${{ inputs.releaseVersion }} + SENTRY_KEY: ${{ inputs.sentryKey }} shell: bash - run: | npx nx build api --prod @@ -32,3 +44,13 @@ runs: app-name: 'kordis-api' slot-name: ${{ inputs.slot }} package: dist/apps/api/ + - name: Create Sentry release + uses: getsentry/action-release@v1 + env: + SENTRY_AUTH_TOKEN: ${{ inputs.sentryAuthToken }} + SENTRY_ORG: kordis-leitstelle + SENTRY_PROJECT: kordis-api + with: + environment: ${{ inputs.slot }} + version: ${{ inputs.releaseVersion }} + sourcemaps: ./dist/apps/api diff --git a/.github/actions/build-and-deploy-spa/action.yml b/.github/actions/build-and-deploy-spa/action.yml index 8f86054d..1caacef1 100644 --- a/.github/actions/build-and-deploy-spa/action.yml +++ b/.github/actions/build-and-deploy-spa/action.yml @@ -8,15 +8,21 @@ inputs: oauthConfig: required: true description: "OAuthConfig from the angular-oauth2-oidc package" - deploymentName: + releaseVersion: required: true - description: "Unique identifier for the Deployment" + description: "Release Version (Commit or Tag)" deploymentEnv: required: true description: "Azure SWA Deployment Environment" publishToken: required: true description: "Azure Static Web App Deployment Token" + sentryKey: + required: true + description: "Sentry DSN Key" + sentryAuthToken: + required: true + description: "Sentry Auth Token" outputs: url: description: "SPA URL" @@ -31,9 +37,11 @@ runs: shell: bash env: IS_PRODUCTION: true - DEPLOYMENT_NAME: ${{ inputs.deploymentName }} + ENVIRONMENT_NAME: ${{ inputs.deploymentEnv }} API_URL: ${{ inputs.apiUrl }} OAUTH_CONFIG: ${{ inputs.oauthConfig }} + RELEASE_VERSION: ${{ inputs.releaseVersion }} + SENTRY_KEY: ${{ inputs.sentryKey }} - name: Deploy SPA id: spa-deployment uses: Azure/static-web-apps-deploy@v1 @@ -45,3 +53,16 @@ runs: skip_app_build: true skip_api_build: true deployment_environment: ${{ inputs.deploymentEnv }} + - name: Build SPA with source maps + run: npx nx build spa --prod --source-map=true + shell: bash + - name: Create Sentry release + uses: getsentry/action-release@v1 + env: + SENTRY_AUTH_TOKEN: ${{ inputs.sentryAuthToken }} + SENTRY_ORG: kordis-leitstelle + SENTRY_PROJECT: kordis-spa + with: + environment: ${{ inputs.deploymentEnv }} + version: ${{ inputs.releaseVersion }} + sourcemaps: ./dist/apps/spa diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e44e6c8e..29e991f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,17 +43,26 @@ jobs: run: envsubst < apps/spa/src/environments/environment.template > apps/spa/src/environments/environment.prod.ts env: IS_PRODUCTION: true - DEPLOYMENT_NAME: E2E Runner + ENVIRONMENT_NAME: 'ci' + RELEASE_VERSION: ${{ github.sha }} API_URL: http://localhost:3000/ OAUTH_CONFIG: undefined + - uses: actions/upload-artifact@v3 + with: + name: env-spa-prod + path: apps/spa/src/environments/environment.prod.ts - name: Create API Environment File run: | envsubst < apps/api/src/.env.template > apps/api/src/.env echo "PORT=3000" >> .env env: MONGODB_URI: mongodb://127.0.0.1:27017/e2edb + ENVIRONMENT_NAME: 'ci' + RELEASE_VERSION: ${{ github.sha }} + SENTRY_KEY: ${{ secrets.SENTRY_KEY }} + - name: Build - run: npx nx affected --target=build --parallel=3 + run: npx nx run-many -t build --all --parallel=3 - name: Start and prepare MongoDB for E2Es run: ./tools/db/kordis-db.sh init e2edb diff --git a/.github/workflows/next-deployment.yml b/.github/workflows/next-deployment.yml index 2df77032..907ae09e 100644 --- a/.github/workflows/next-deployment.yml +++ b/.github/workflows/next-deployment.yml @@ -30,7 +30,10 @@ jobs: uses: ./.github/actions/build-and-deploy-api with: slot: "next" + releaseVersion: ${{ github.sha }} mongoUri: ${{ secrets.DEV_MONGODB_URI }} + sentryKey: ${{ secrets.API_SENTRY_KEY }} + sentryAuthToken: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Apply Database Migrations run: ./tools/db/kordis-db.sh apply-pending-migrations env: @@ -43,9 +46,11 @@ jobs: with: apiUrl: ${{ steps.api-deployment.outputs.url }} oauthConfig: ${{ secrets.DEV_OAUTH_CONFIG }} - deploymentName: "main.${{ github.sha }}" + releaseVersion: ${{ github.sha }} deploymentEnv: "next" publishToken: ${{ secrets.AZURE_STATIC_WEB_APP_TOKEN }} + sentryKey: ${{ secrets.SPA_SENTRY_KEY }} + sentryAuthToken: ${{ secrets.SENTRY_AUTH_TOKEN }} e2e: needs: deployment diff --git a/.github/workflows/preview-deployment.yml b/.github/workflows/preview-deployment.yml index b81f42b4..096c0a68 100644 --- a/.github/workflows/preview-deployment.yml +++ b/.github/workflows/preview-deployment.yml @@ -94,15 +94,20 @@ jobs: uses: ./.github/actions/build-and-deploy-api with: slot: "pr${{ github.event.issue.number }}" + releaseVersion: ${{ github.sha }} + sentryKey: ${{ secrets.API_SENTRY_KEY }} + sentryAuthToken: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Build and Deploy SPA id: spa-deployment uses: ./.github/actions/build-and-deploy-spa with: apiUrl: ${{ steps.api-deployment.outputs.url }} - oauthConfig: ${{ secrets.OAUTH_CONFIG }} - deploymentName: "PR-${{ github.event.issue.number }}.{{ github.sha }}" - deploymentEnv: "pr${{github.event.issue.number }}" + oauthConfig: ${{ secrets.DEV_OAUTH_CONFIG }} + releaseVersion: ${{ github.sha }} + deploymentEnv: "pr${{ github.event.issue.number }}" publishToken: ${{ secrets.AZURE_STATIC_WEB_APP_TOKEN }} + sentryKey: ${{ secrets.SPA_SENTRY_KEY }} + sentryAuthToken: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Update PR Preview Comment uses: peter-evans/create-or-update-comment@v3.0.1 with: @@ -169,15 +174,20 @@ jobs: uses: ./.github/actions/build-and-deploy-api with: slot: "pr${{ github.event.pull_request.number }}" + releaseVersion: ${{ github.sha }} + sentryKey: ${{ secrets.API_SENTRY_KEY }} + sentryAuthToken: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Build and Deploy SPA id: spa-deployment uses: ./.github/actions/build-and-deploy-spa with: apiUrl: ${{ steps.api-deployment.outputs.url }} - oauthConfig: ${{ secrets.OAUTH_CONFIG }} - deploymentName: "PR-${{ github.event.pull_request.number }}.{{ github.sha }}" + oauthConfig: ${{ secrets.DEV_OAUTH_CONFIG }} + releaseVersion: ${{ github.sha }} deploymentEnv: "pr${{ github.event.pull_request.number }}" publishToken: ${{ secrets.AZURE_STATIC_WEB_APP_TOKEN }} + sentryKey: ${{ secrets.SPA_SENTRY_KEY }} + sentryAuthToken: ${{ secrets.SENTRY_AUTH_TOKEN }} - name: Update PR Preview Comment uses: peter-evans/create-or-update-comment@v3.0.1 with: diff --git a/.prettierignore b/.prettierignore index 054671ae..d62afe44 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,7 @@ /coverage package-lock.json .github + +.angular + +apps/api/src/main.ts diff --git a/apps/api/src/.env.template b/apps/api/src/.env.template index 5dd9e0a2..183c5c2b 100644 --- a/apps/api/src/.env.template +++ b/apps/api/src/.env.template @@ -1 +1,4 @@ MONGODB_URI=$MONGODB_URI +ENVIRONMENT_NAME=$ENVIRONMENT_NAME +RELEASE_VERSION=$RELEASE_VERSION +SENTRY_KEY=$SENTRY_KEY diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index ff654e6e..1272d795 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -6,6 +6,7 @@ import { MongooseModule } from '@nestjs/mongoose'; import * as path from 'path'; import { AuthModule } from '@kordis/api/auth'; +import { SentryObservabilityModule } from '@kordis/api/observability'; import { SharedKernel } from '@kordis/api/shared'; import { AppResolver } from './app.resolver'; @@ -19,13 +20,20 @@ import { GraphqlSubscriptionsController } from './controllers/graphql-subscripti cache: true, envFilePath: path.resolve(__dirname, '.env'), }), - GraphQLModule.forRoot({ + GraphQLModule.forRootAsync({ + imports: [ConfigModule], driver: ApolloDriver, - autoSchemaFile: - process.env.NODE_ENV !== 'production' - ? path.join(process.cwd(), 'apps/api/src/schema.gql') - : true, - playground: process.env.NODE_ENV !== 'production', + useFactory: (config: ConfigService) => ({ + autoSchemaFile: + config.get('NODE_ENV') !== 'production' + ? path.join(process.cwd(), 'apps/api/src/schema.gql') + : true, + subscriptions: { + 'graphql-ws': true, + }, + playground: config.get('NODE_ENV') !== 'production', + }), + inject: [ConfigService], }), MongooseModule.forRootAsync({ imports: [ConfigModule], @@ -36,6 +44,9 @@ import { GraphqlSubscriptionsController } from './controllers/graphql-subscripti }), SharedKernel, AuthModule, + ...(process.env.NODE_ENV === 'production' && !process.env.GITHUB_ACTIONS + ? [SentryObservabilityModule] + : []), ], providers: [AppService, AppResolver], controllers: [GraphqlSubscriptionsController], diff --git a/apps/api/src/app/app.service.ts b/apps/api/src/app/app.service.ts index 6de1fe4c..a4f1bc35 100644 --- a/apps/api/src/app/app.service.ts +++ b/apps/api/src/app/app.service.ts @@ -1,7 +1,10 @@ import { Injectable } from '@nestjs/common'; +import { Trace } from '@kordis/api/observability'; + @Injectable() export class AppService { + @Trace() getData(): { message: string } { return { message: 'Welcome to api!' }; } diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 49ee0fe8..e63b53bb 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,14 +1,20 @@ -import { Logger } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { NestFactory } from '@nestjs/core'; +// @formatter:off otelSdk has to imported on the very top! +// until https://github.com/open-telemetry/opentelemetry-js/issues/3450 is fixed, we have to import the oTel sdk via a relative path +// eslint-disable-next-line @nx/enforce-module-boundaries +import '../../../libs/api/observability/src/lib/oTelSdk'; + +import {Logger} from '@nestjs/common'; +import {ConfigService} from '@nestjs/config'; +import {NestFactory} from '@nestjs/core'; + +import {AppModule} from './app/app.module'; -import { AppModule } from './app/app.module'; async function bootstrap(): Promise { const app = await NestFactory.create(AppModule, { cors: true }); const config = app.get(ConfigService); - const envPort = config.get('PORT'); + const port = envPort ? +envPort : 3000; await app.listen(port); diff --git a/apps/spa/src/app/app.module.ts b/apps/spa/src/app/app.module.ts index a12b3fe5..d47fbd28 100644 --- a/apps/spa/src/app/app.module.ts +++ b/apps/spa/src/app/app.module.ts @@ -1,27 +1,18 @@ import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; -import { RouterModule, Routes } from '@angular/router'; +import { RouterModule } from '@angular/router'; -import { AuthModule, DevAuthModule, authGuard } from '@kordis/spa/auth'; +import { AuthModule, DevAuthModule } from '@kordis/spa/auth'; +import { + NoopObservabilityModule, + SentryObservabilityModule, +} from '@kordis/spa/observability'; import { environment } from '../environments/environment'; -import { AppComponent } from './app.component'; -import { ProtectedComponent } from './protected.component'; - -const routes: Routes = [ - { - path: '', - redirectTo: 'protected', - pathMatch: 'full', - }, - { - path: 'protected', - component: ProtectedComponent, - canActivate: [authGuard], - }, - { path: '**', redirectTo: 'protected' }, -]; +import { AppComponent } from './component/app.component'; +import { ProtectedComponent } from './component/protected.component'; +import routes from './routes'; @NgModule({ declarations: [AppComponent, ProtectedComponent], @@ -35,6 +26,14 @@ const routes: Routes = [ environment.oauth.discoveryDocumentUrl, ) : DevAuthModule.forRoot(), + // for now, we accept that we have the sentry module and dependencies in our dev bundle as well + environment.sentryKey + ? SentryObservabilityModule.forRoot( + environment.sentryKey, + environment.environmentName, + environment.releaseVersion, + ) + : NoopObservabilityModule.forRoot(), ], providers: [], bootstrap: [AppComponent], diff --git a/apps/spa/src/app/app.component.ts b/apps/spa/src/app/component/app.component.ts similarity index 75% rename from apps/spa/src/app/app.component.ts rename to apps/spa/src/app/component/app.component.ts index 922b0c0d..11c0e280 100644 --- a/apps/spa/src/app/app.component.ts +++ b/apps/spa/src/app/component/app.component.ts @@ -1,9 +1,11 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { TraceComponent } from '@kordis/spa/observability'; + @Component({ selector: 'kordis-root', template: ` `, - changeDetection: ChangeDetectionStrategy.OnPush, }) +@TraceComponent() export class AppComponent {} diff --git a/apps/spa/src/app/protected.component.ts b/apps/spa/src/app/component/protected.component.ts similarity index 77% rename from apps/spa/src/app/protected.component.ts rename to apps/spa/src/app/component/protected.component.ts index dd8f67d8..b081fc81 100644 --- a/apps/spa/src/app/protected.component.ts +++ b/apps/spa/src/app/component/protected.component.ts @@ -1,9 +1,12 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { TraceComponent } from '@kordis/spa/observability'; + // placeholder until we have a feature structure @Component({ selector: 'kordis-root', template: ` ganz geheim `, changeDetection: ChangeDetectionStrategy.OnPush, }) +@TraceComponent() export class ProtectedComponent {} diff --git a/apps/spa/src/app/routes.ts b/apps/spa/src/app/routes.ts new file mode 100644 index 00000000..3f56af7a --- /dev/null +++ b/apps/spa/src/app/routes.ts @@ -0,0 +1,21 @@ +import { Routes } from '@angular/router'; + +import { authGuard } from '@kordis/spa/auth'; + +import { ProtectedComponent } from './component/protected.component'; + +const routes: Routes = [ + { + path: '', + redirectTo: 'protected', + pathMatch: 'full', + }, + { + path: 'protected', + component: ProtectedComponent, + canActivate: [authGuard], + }, + { path: '**', redirectTo: 'protected' }, +]; + +export default routes; diff --git a/apps/spa/src/environments/environment.model.ts b/apps/spa/src/environments/environment.model.ts index 1f6f13a6..3fb6f9c8 100644 --- a/apps/spa/src/environments/environment.model.ts +++ b/apps/spa/src/environments/environment.model.ts @@ -3,6 +3,8 @@ import { AuthConfig } from 'angular-oauth2-oidc'; export type Environment = { production: boolean; apiUrl: string; - deploymentName: string; + environmentName: string; + releaseVersion: string; oauth?: { discoveryDocumentUrl: string; config: AuthConfig }; + sentryKey?: string; }; diff --git a/apps/spa/src/environments/environment.template b/apps/spa/src/environments/environment.template index 1842d0e4..39034127 100644 --- a/apps/spa/src/environments/environment.template +++ b/apps/spa/src/environments/environment.template @@ -2,7 +2,9 @@ import { Environment } from './environment.model'; export const environment: Environment = { production: $IS_PRODUCTION, - deploymentName: '$DEPLOYMENT_NAME', + environmentName: '$ENVIRONMENT_NAME', + sentryKey: '$SENTRY_KEY', apiUrl: '$API_URL', + releaseVersion: '$RELEASE_VERSION', oauth: $OAUTH_CONFIG as any, }; diff --git a/apps/spa/src/environments/environment.ts b/apps/spa/src/environments/environment.ts index 68de3f65..9bf141c3 100644 --- a/apps/spa/src/environments/environment.ts +++ b/apps/spa/src/environments/environment.ts @@ -2,6 +2,7 @@ import { Environment } from './environment.model'; export const environment: Environment = { production: false, - deploymentName: 'Dev Local', + environmentName: 'Dev Local', + releaseVersion: '0.0.0-development', apiUrl: 'https://localhost:3000', }; diff --git a/libs/api/observability/.eslintrc.json b/libs/api/observability/.eslintrc.json new file mode 100644 index 00000000..79fd7c1d --- /dev/null +++ b/libs/api/observability/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/api/observability/README.md b/libs/api/observability/README.md new file mode 100644 index 00000000..8cc2ced7 --- /dev/null +++ b/libs/api/observability/README.md @@ -0,0 +1,23 @@ +# API Observability + +This module handles Observability/Telemetry for the API. For transmitting +telemetry data, OpenTelemetry is used, as it is the industry standard can easily +be integrated with other tools such as Prometheus etc. Currently, we rely on +Sentry, which also offers exceptions tracking. Using other providers is +possible, but not implemented. The OTel SDK has to be created once in the +bootstrap file (`main.ts`) via import. + +Automatically traced are: + +- GraphQL resolutions (through the OpenTelemetry GraphQL Instrumentation) +- Resolvers +- Interceptors +- Methods that are decorated with `@Trace('optional trace name')` + +OTel and the Module will only be initialized if the node environment is set to +`production`. + +## Running unit tests + +Run `nx test api-observability` to execute the unit tests via +[Jest](https://jestjs.io). diff --git a/libs/api/observability/jest.config.ts b/libs/api/observability/jest.config.ts new file mode 100644 index 00000000..2876bf64 --- /dev/null +++ b/libs/api/observability/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'api-observability', + preset: '../../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../../coverage/libs/api/observability', +}; diff --git a/libs/api/observability/project.json b/libs/api/observability/project.json new file mode 100644 index 00000000..0ed1ec18 --- /dev/null +++ b/libs/api/observability/project.json @@ -0,0 +1,30 @@ +{ + "name": "api-observability", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/api/observability/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/api/observability/**/*.ts"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/api/observability/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + } + }, + "tags": [] +} diff --git a/libs/api/observability/src/index.ts b/libs/api/observability/src/index.ts new file mode 100644 index 00000000..84cd89bf --- /dev/null +++ b/libs/api/observability/src/index.ts @@ -0,0 +1,2 @@ +export * from './lib/sentry-observability.module'; +export * from './lib/decorators/trace.decorator'; diff --git a/libs/api/observability/src/lib/decorators/trace.decorator.spec.ts b/libs/api/observability/src/lib/decorators/trace.decorator.spec.ts new file mode 100644 index 00000000..4f8250b9 --- /dev/null +++ b/libs/api/observability/src/lib/decorators/trace.decorator.spec.ts @@ -0,0 +1,33 @@ +import { SPAN_ACTIVE, TRACE_NAME, Trace } from './trace.decorator'; + +describe('TraceDecorator', () => { + it('should set SPAN_ACTIVE metadata without traceName', () => { + class TestClass { + @Trace() + testMethod(): void {} + } + + expect( + Reflect.getMetadata(SPAN_ACTIVE, TestClass.prototype.testMethod), + ).toBe(true); + expect( + Reflect.getMetadata(TRACE_NAME, TestClass.prototype.testMethod), + ).toBe(undefined); + }); + + it('should set SPAN_ACTIVE and TRACE_NAME metadata with traceName', () => { + const traceName = 'test-trace-name'; + + class TestClass { + @Trace(traceName) + testMethod(): void {} + } + + expect( + Reflect.getMetadata(SPAN_ACTIVE, TestClass.prototype.testMethod), + ).toBe(true); + expect( + Reflect.getMetadata(TRACE_NAME, TestClass.prototype.testMethod), + ).toBe(traceName); + }); +}); diff --git a/libs/api/observability/src/lib/decorators/trace.decorator.ts b/libs/api/observability/src/lib/decorators/trace.decorator.ts new file mode 100644 index 00000000..162b3268 --- /dev/null +++ b/libs/api/observability/src/lib/decorators/trace.decorator.ts @@ -0,0 +1,16 @@ +import { SetMetadata } from '@nestjs/common'; + +export const SPAN_ACTIVE = Symbol('SPAN_ACTIVE'); +export const TRACE_NAME = Symbol('TRACE_NAME'); + +/** + * This will mark the method as a traceable method, meaning it will be proxied with a span. This only works in injected Providers! + */ +export function Trace(traceName?: string) { + return (target: T, propertyKey: string, descriptor: PropertyDescriptor) => { + SetMetadata(SPAN_ACTIVE, true)(target, propertyKey, descriptor); + if (traceName) { + SetMetadata(TRACE_NAME, traceName)(target, propertyKey, descriptor); + } + }; +} diff --git a/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.spec.ts b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.spec.ts new file mode 100644 index 00000000..20add49c --- /dev/null +++ b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.spec.ts @@ -0,0 +1,68 @@ +import { createMock } from '@golevelup/ts-jest'; +import { CallHandler } from '@nestjs/common/interfaces'; +import { Span, trace } from '@opentelemetry/api'; +import * as Sentry from '@sentry/node'; +import { Observable, firstValueFrom, of } from 'rxjs'; + +import { KordisRequest } from '@kordis/api/shared'; +import { createGqlContextForRequest } from '@kordis/api/test-helpers'; +import { AuthUser } from '@kordis/shared/auth'; + +import { SentryOTelUserContextInterceptor } from './sentry-otel-user-context.interceptor'; + +describe('SentryOTelUserContextInterceptor', () => { + let interceptor: SentryOTelUserContextInterceptor; + + beforeEach(() => { + interceptor = new SentryOTelUserContextInterceptor(); + }); + + it('should be defined', () => { + expect(interceptor).toBeDefined(); + }); + + it('should set user context and attributes for Sentry and OpenTelemetry', async () => { + const user: AuthUser = { + id: '123', + email: 'test@example.com', + firstName: 'John', + lastName: 'Doe', + }; + + const ctx = createGqlContextForRequest( + createMock({ + user, + }), + ); + const handler = createMock({ + handle(): Observable { + return of(true); + }, + }); + + const getActiveSpanSpy = jest.spyOn(trace, 'getActiveSpan'); + getActiveSpanSpy.mockReturnValue(createMock()); + + const sentrySetUserSpy = jest.spyOn(Sentry, 'setUser'); + + await expect( + firstValueFrom(interceptor.intercept(ctx, handler)), + ).resolves.toBeTruthy(); + + expect(getActiveSpanSpy).toHaveBeenCalled(); + expect( + getActiveSpanSpy.mock.results[0].value.setAttributes, + ).toHaveBeenCalledWith({ + 'user.id': user.id, + 'user.email': user.email, + 'user.name': `${user.firstName} ${user.lastName}`, + }); + + expect(sentrySetUserSpy).toHaveBeenCalled(); + expect(sentrySetUserSpy).toHaveBeenCalledWith({ + id: user.id, + email: user.email, + username: `${user.firstName} ${user.lastName}`, + }); + }); +}); diff --git a/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.ts b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.ts new file mode 100644 index 00000000..1a455dfb --- /dev/null +++ b/libs/api/observability/src/lib/interceptors/sentry-otel-user-context.interceptor.ts @@ -0,0 +1,34 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { GqlExecutionContext } from '@nestjs/graphql'; +import { trace } from '@opentelemetry/api'; +import * as Sentry from '@sentry/node'; +import { Observable } from 'rxjs'; + +import { KordisGqlContext } from '@kordis/api/shared'; + +@Injectable() +export class SentryOTelUserContextInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const ctx = GqlExecutionContext.create(context); + const { user } = ctx.getContext().req; + + trace.getActiveSpan()?.setAttributes({ + 'user.id': user.id, + 'user.email': user.email, + 'user.name': `${user.firstName} ${user.lastName}`, + }); + + Sentry.setUser({ + id: user.id, + email: user.email, + username: `${user.firstName} ${user.lastName}`, + }); + + return next.handle(); + } +} diff --git a/libs/api/observability/src/lib/oTel.factory.ts b/libs/api/observability/src/lib/oTel.factory.ts new file mode 100644 index 00000000..42b68d17 --- /dev/null +++ b/libs/api/observability/src/lib/oTel.factory.ts @@ -0,0 +1,39 @@ +import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'; +import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql'; +import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose'; +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { + SentryPropagator, + SentrySpanProcessor, +} from '@sentry/opentelemetry-node'; + +export abstract class OTelSDKFactory { + protected readonly defaultInstrumentations = [ + new GraphQLInstrumentation(), + new MongooseInstrumentation(), + ] as const; + protected readonly serviceName = 'kordis-api'; + abstract makeSdk(): NodeSDK; +} + +export class SentryOTelSDKFactory extends OTelSDKFactory { + makeSdk(): NodeSDK { + return new NodeSDK({ + traceExporter: new OTLPTraceExporter(), + instrumentations: [...this.defaultInstrumentations], + spanProcessor: new SentrySpanProcessor(), + textMapPropagator: new SentryPropagator(), + serviceName: this.serviceName, + }); + } +} + +export class NoopOTelSDKFactory extends OTelSDKFactory { + makeSdk(): NodeSDK { + return { + start: () => { + // noop + }, + } as NodeSDK; + } +} diff --git a/libs/api/observability/src/lib/oTelSdk.ts b/libs/api/observability/src/lib/oTelSdk.ts new file mode 100644 index 00000000..bb830897 --- /dev/null +++ b/libs/api/observability/src/lib/oTelSdk.ts @@ -0,0 +1,18 @@ +import { + NoopOTelSDKFactory, + OTelSDKFactory, + SentryOTelSDKFactory, +} from './oTel.factory'; + +/** + * this file has to be the first import of the bootstrap file! otherwise the oTel sdk will not be initialized correctly. + * The SDK has to be created as a first step, otherwise some instrumentations might not work! + * This is why we have to check the environment and create a noop sdk when we are in a test environment. + **/ +const sdkFactory: OTelSDKFactory = + process.env.NODE_ENV === 'production' && !process.env.GITHUB_ACTIONS + ? new SentryOTelSDKFactory() + : new NoopOTelSDKFactory(); +const oTelSDK = sdkFactory.makeSdk(); + +export default oTelSDK; diff --git a/libs/api/observability/src/lib/sentry-observability.module.ts b/libs/api/observability/src/lib/sentry-observability.module.ts new file mode 100644 index 00000000..54e2410c --- /dev/null +++ b/libs/api/observability/src/lib/sentry-observability.module.ts @@ -0,0 +1,39 @@ +import { Module, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { APP_INTERCEPTOR, ModulesContainer } from '@nestjs/core'; +import { init as initSentry } from '@sentry/node'; +import { ProfilingIntegration } from '@sentry/profiling-node'; + +import { SentryOTelUserContextInterceptor } from './interceptors/sentry-otel-user-context.interceptor'; +import oTelSDK from './oTelSdk'; +import { wrapProvidersWithTracingSpans } from './trace-wrapper'; + +// This Module must come after the AuthModule, because it depends on the use set by the AuthInterceptor +@Module({ + providers: [ + { + provide: APP_INTERCEPTOR, + useClass: SentryOTelUserContextInterceptor, + }, + ], +}) +export class SentryObservabilityModule implements OnModuleInit { + constructor( + private readonly config: ConfigService, + private readonly modulesContainer: ModulesContainer, + ) {} + + onModuleInit(): void { + initSentry({ + dsn: this.config.get('SENTRY_KEY'), + tracesSampleRate: 1.0, + profilesSampleRate: 1.0, + instrumenter: 'otel', + environment: this.config.get('ENVIRONMENT_NAME') ?? 'local-dev', + release: this.config.get('RELEASE_VERSION') ?? '0.0.0-development', + integrations: [new ProfilingIntegration()], + }); + wrapProvidersWithTracingSpans(this.modulesContainer); + oTelSDK.start(); + } +} diff --git a/libs/api/observability/src/lib/trace-wrapper.ts b/libs/api/observability/src/lib/trace-wrapper.ts new file mode 100644 index 00000000..6980e788 --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrapper.ts @@ -0,0 +1,23 @@ +import { ModulesContainer } from '@nestjs/core'; + +import { + InterceptorTraceWrapper, + ResolverTraceWrapper, + TraceDecoratorTraceWrapper, + TraceWrapper, +} from './trace-wrappers'; + +// this has to be called from a NestJS import (main.ts or any provider/module) context, otherwise prototypes are not correctly patched +export function wrapProvidersWithTracingSpans( + modulesContainer: ModulesContainer, +): void { + const wrappers: TraceWrapper[] = [ + new ResolverTraceWrapper(modulesContainer), + new TraceDecoratorTraceWrapper(modulesContainer), + new InterceptorTraceWrapper(modulesContainer), + ]; + + for (const wrapper of wrappers) { + wrapper.wrapWithSpans(); + } +} diff --git a/libs/api/observability/src/lib/trace-wrappers/abstract-trace-wrapper.ts b/libs/api/observability/src/lib/trace-wrappers/abstract-trace-wrapper.ts new file mode 100644 index 00000000..6b7cde96 --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/abstract-trace-wrapper.ts @@ -0,0 +1,65 @@ +import { MetadataScanner, ModulesContainer } from '@nestjs/core'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { trace } from '@opentelemetry/api'; + +export abstract class TraceWrapper { + private readonly metadataScanner: MetadataScanner = new MetadataScanner(); + + protected constructor(private readonly modulesContainer: ModulesContainer) {} + + abstract wrapWithSpans(): void; + + protected asWrapped( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + prototype: Record, + traceName: string, + attributes = {}, + ): unknown { + const method = { + [prototype.name]: function (...args: unknown[]) { + const tracer = trace.getTracer('default'); + const currentSpan = tracer.startSpan(traceName); + currentSpan.setAttributes(attributes); + + let hasAsyncResponse = false; + + try { + const res = prototype.apply(this, args); + if (res instanceof Promise) { + hasAsyncResponse = true; + return res.finally(() => currentSpan.end()); + } + return res; + } finally { + if (!hasAsyncResponse) { + currentSpan.end(); + } + } + }, + }[prototype.name]; + + // decorate with metadata from original method implementation + const keys = Reflect.getMetadataKeys(prototype); + + for (const key of keys) { + const meta = Reflect.getMetadata(key, prototype); + Reflect.defineMetadata(key, meta, method); + } + + return method; + } + + protected *getProviders(): Generator { + for (const module of this.modulesContainer.values()) { + for (const provider of module.providers.values()) { + if (provider?.metatype?.prototype) { + yield provider; + } + } + } + } + + protected getFilteredMethodNames(prototype: object): string[] { + return this.metadataScanner.getAllMethodNames(prototype); + } +} diff --git a/libs/api/observability/src/lib/trace-wrappers/index.ts b/libs/api/observability/src/lib/trace-wrappers/index.ts new file mode 100644 index 00000000..5a524814 --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/index.ts @@ -0,0 +1,4 @@ +export * from './interceptor-trace-wrapper'; +export * from './abstract-trace-wrapper'; +export * from './resolver-trace-wrapper'; +export * from './trace-decorator-trace-wrapper'; diff --git a/libs/api/observability/src/lib/trace-wrappers/interceptor-trace-wrapper.spec.ts b/libs/api/observability/src/lib/trace-wrappers/interceptor-trace-wrapper.spec.ts new file mode 100644 index 00000000..018f9c85 --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/interceptor-trace-wrapper.spec.ts @@ -0,0 +1,99 @@ +import { createMock } from '@golevelup/ts-jest'; +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { APP_INTERCEPTOR, ModulesContainer } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; +import { Observable, firstValueFrom, of } from 'rxjs'; + +import { + createTraceMocks, + getComparablePrototypeSnapshot, +} from '@kordis/api/test-helpers'; + +import { SentryOTelUserContextInterceptor } from '../interceptors/sentry-otel-user-context.interceptor'; +import { InterceptorTraceWrapper } from './interceptor-trace-wrapper'; + +describe('InterceptorTraceWrapper', () => { + it('should wrap interceptors with spans', async () => { + const originalImplementationFn = jest.fn(); + + @Injectable() + class TestInterceptor implements NestInterceptor { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + intercept( + context: ExecutionContext, + next: CallHandler, + ): Observable { + originalImplementationFn(context, next); + return next.handle(); + } + } + + const moduleRef = await Test.createTestingModule({ + providers: [ + { + provide: APP_INTERCEPTOR, + useClass: TestInterceptor, + }, + ], + }).compile(); + + const traceWrapper = new InterceptorTraceWrapper( + moduleRef.get(ModulesContainer), + ); + traceWrapper.wrapWithSpans(); + + const intercept = TestInterceptor.prototype.intercept; + const ctx = createMock(); + const handler = createMock({ + handle: () => of(true), + }); + + const { startSpanSpy, endSpanSpy, spanSetAttributesSpy } = + createTraceMocks(); + + await firstValueFrom(intercept(ctx, handler)); + + // validate that original implementation gets called correctly + expect(originalImplementationFn).toHaveBeenCalledTimes(1); + expect(originalImplementationFn).toHaveBeenCalledWith(ctx, handler); + + // validate that span gets correctly created + expect(startSpanSpy).toHaveBeenCalled(); + expect(startSpanSpy).toHaveBeenCalledWith('TestInterceptor (Interceptor)'); + expect(endSpanSpy).toHaveBeenCalled(); + expect(spanSetAttributesSpy).toHaveBeenCalledWith({ + interceptor: 'TestInterceptor', + }); + }); + + it('should ignore SentryOTelUserContextInterceptor', async () => { + const moduleRef = await Test.createTestingModule({ + providers: [ + { + provide: APP_INTERCEPTOR, + useClass: SentryOTelUserContextInterceptor, + }, + ], + }).compile(); + + const prototypeSnapshot = getComparablePrototypeSnapshot( + SentryOTelUserContextInterceptor.prototype, + ); + + const traceWrapper = new InterceptorTraceWrapper( + moduleRef.get(ModulesContainer), + ); + traceWrapper.wrapWithSpans(); + + expect( + getComparablePrototypeSnapshot( + SentryOTelUserContextInterceptor.prototype, + ), + ).toBe(prototypeSnapshot); + }); +}); diff --git a/libs/api/observability/src/lib/trace-wrappers/interceptor-trace-wrapper.ts b/libs/api/observability/src/lib/trace-wrappers/interceptor-trace-wrapper.ts new file mode 100644 index 00000000..d346239d --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/interceptor-trace-wrapper.ts @@ -0,0 +1,29 @@ +import { ModulesContainer } from '@nestjs/core'; + +import { TraceWrapper } from './abstract-trace-wrapper'; + +/** + * Wraps all NestJS Interceptors `intercept` methods with OpenTelemetry spans + */ +export class InterceptorTraceWrapper extends TraceWrapper { + constructor(modulesContainer: ModulesContainer) { + super(modulesContainer); + } + + wrapWithSpans(): void { + for (const provider of this.getProviders()) { + if ( + provider.subtype === 'interceptor' && + provider.name !== 'SentryOTelUserContextInterceptor' + ) { + provider.metatype.prototype['intercept'] = this.asWrapped( + provider.metatype.prototype['intercept'], + `${provider.name} (Interceptor)`, + { + interceptor: provider.name, + }, + ); + } + } + } +} diff --git a/libs/api/observability/src/lib/trace-wrappers/resolver-trace-wrapper.spec.ts b/libs/api/observability/src/lib/trace-wrappers/resolver-trace-wrapper.spec.ts new file mode 100644 index 00000000..68d106fb --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/resolver-trace-wrapper.spec.ts @@ -0,0 +1,78 @@ +import { ModulesContainer } from '@nestjs/core'; +import { Mutation, Query, Resolver } from '@nestjs/graphql'; +import { Test } from '@nestjs/testing'; + +import { + createTraceMocks, + getComparablePrototypeSnapshot, +} from '@kordis/api/test-helpers'; + +import { ResolverTraceWrapper } from './resolver-trace-wrapper'; + +describe('ResolverTraceWrapper', () => { + it('should only wrap queries and mutations with spans', async () => { + @Resolver() + class TestResolver { + @Query() + someQuery() { + return 'original someQuery implementation'; + } + + @Mutation() + someMutation() { + return 'original someMutation implementation'; + } + + notOfInterest() {} + } + + const moduleRef = await Test.createTestingModule({ + providers: [TestResolver], + }).compile(); + const traceWrapper = new ResolverTraceWrapper( + moduleRef.get(ModulesContainer), + ); + + const { startSpanSpy, endSpanSpy, spanSetAttributesSpy } = + createTraceMocks(); + const notOfInterestPrototypeSnapshot = getComparablePrototypeSnapshot( + TestResolver.prototype.notOfInterest, + ); + + traceWrapper.wrapWithSpans(); + + expect(TestResolver.prototype.someQuery()).toBe( + 'original someQuery implementation', + ); + expect(TestResolver.prototype.someMutation()).toBe( + 'original someMutation implementation', + ); + + // validate that span gets correctly created + expect(startSpanSpy).toHaveBeenCalledTimes(2); + + expect(startSpanSpy.mock.calls).toEqual([ + ['TestResolver (Resolver) -> someQuery (Query)'], + ['TestResolver (Resolver) -> someMutation (Mutation)'], + ]); + expect(endSpanSpy).toHaveBeenCalledTimes(2); + expect(spanSetAttributesSpy.mock.calls).toEqual([ + [ + { + resolver: 'TestResolver', + method: 'someQuery', + }, + ], + [ + { + resolver: 'TestResolver', + method: 'someMutation', + }, + ], + ]); + + expect( + getComparablePrototypeSnapshot(TestResolver.prototype.notOfInterest), + ).toBe(notOfInterestPrototypeSnapshot); + }); +}); diff --git a/libs/api/observability/src/lib/trace-wrappers/resolver-trace-wrapper.ts b/libs/api/observability/src/lib/trace-wrappers/resolver-trace-wrapper.ts new file mode 100644 index 00000000..eceb594f --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/resolver-trace-wrapper.ts @@ -0,0 +1,65 @@ +import { ModulesContainer } from '@nestjs/core'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { + RESOLVER_NAME_METADATA, + RESOLVER_TYPE_METADATA, +} from '@nestjs/graphql'; + +import { TraceWrapper } from './abstract-trace-wrapper'; + +/** + * Wraps all NestJS Resolvers `Query` and `Mutation` methods with OpenTelemetry spans + */ +export class ResolverTraceWrapper extends TraceWrapper { + constructor(modulesContainer: ModulesContainer) { + super(modulesContainer); + } + + wrapWithSpans(): void { + for (const provider of this.getProviders()) { + if (this.isResolver(provider)) { + this.wrapResolver(provider); + } + } + } + + private isResolver(provider: InstanceWrapper): boolean { + const { instance } = provider; + if (!instance) { + return false; + } + + const metadataKeys = Reflect.getMetadataKeys(instance.constructor); + return metadataKeys.some( + (key) => key === RESOLVER_TYPE_METADATA || key === RESOLVER_NAME_METADATA, + ); + } + + private wrapResolver(provider: InstanceWrapper): void { + const methods = Object.getOwnPropertyNames(provider.metatype.prototype); + + for (const methodName of methods) { + if (methodName === 'constructor') { + continue; + } + + const type = Reflect.getMetadata( + RESOLVER_TYPE_METADATA, + provider.metatype.prototype[methodName], + ); + + if (!type || (type !== 'Query' && type !== 'Mutation')) { + continue; + } + + provider.metatype.prototype[methodName] = this.asWrapped( + provider.metatype.prototype[methodName], + `${provider.metatype.name} (Resolver) -> ${methodName} (${type})`, + { + resolver: provider.name, + method: methodName, + }, + ); + } + } +} diff --git a/libs/api/observability/src/lib/trace-wrappers/trace-decorator-trace-wrapper.spec.ts b/libs/api/observability/src/lib/trace-wrappers/trace-decorator-trace-wrapper.spec.ts new file mode 100644 index 00000000..ba19e00b --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/trace-decorator-trace-wrapper.spec.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@nestjs/common'; +import { ModulesContainer } from '@nestjs/core'; +import { Test } from '@nestjs/testing'; + +import { createTraceMocks } from '@kordis/api/test-helpers'; + +import { Trace } from '../decorators/trace.decorator'; +import { TraceDecoratorTraceWrapper } from './trace-decorator-trace-wrapper'; + +describe('Trace Decorator', () => { + it('should only wrap queries and mutations with spans', async () => { + @Injectable() + class TestProvider { + @Trace() + traceMe() { + return 'original traceMe implementation'; + } + + @Trace('traceName') + traceMeWithName() { + return 'original traceMeWithName implementation'; + } + } + + const moduleRef = await Test.createTestingModule({ + providers: [TestProvider], + }).compile(); + const traceWrapper = new TraceDecoratorTraceWrapper( + moduleRef.get(ModulesContainer), + ); + + const { startSpanSpy, endSpanSpy, spanSetAttributesSpy } = + createTraceMocks(); + + traceWrapper.wrapWithSpans(); + + expect(TestProvider.prototype.traceMe()).toBe( + 'original traceMe implementation', + ); + expect(TestProvider.prototype.traceMeWithName()).toBe( + 'original traceMeWithName implementation', + ); + + expect(startSpanSpy).toHaveBeenCalledTimes(2); + expect(startSpanSpy.mock.calls).toEqual([ + ['TestProvider (Provider) -> traceMe'], + ['TestProvider (Provider) -> traceMeWithName (traceName)'], + ]); + expect(endSpanSpy).toHaveBeenCalledTimes(2); + expect(spanSetAttributesSpy.mock.calls).toEqual([ + [ + { + provider: 'TestProvider', + method: 'traceMe', + }, + ], + [ + { + provider: 'TestProvider', + method: 'traceMeWithName', + traceName: 'traceName', + }, + ], + ]); + }); +}); diff --git a/libs/api/observability/src/lib/trace-wrappers/trace-decorator-trace-wrapper.ts b/libs/api/observability/src/lib/trace-wrappers/trace-decorator-trace-wrapper.ts new file mode 100644 index 00000000..dcdb1c29 --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/trace-decorator-trace-wrapper.ts @@ -0,0 +1,61 @@ +import { ModulesContainer } from '@nestjs/core'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; + +import { SPAN_ACTIVE, TRACE_NAME } from '../decorators/trace.decorator'; +import { TraceWrapper } from './abstract-trace-wrapper'; + +/** + * Wraps all Methods of Providers with OpenTelemetry spans that are marked with the `Trace` decorator + */ +export class TraceDecoratorTraceWrapper extends TraceWrapper { + constructor(modulesContainer: ModulesContainer) { + super(modulesContainer); + } + + wrapWithSpans(): void { + for (const provider of this.getProviders()) { + for (const traceMethod of this.getTraceMethods(provider)) { + provider.metatype.prototype[traceMethod.methodName] = this.asWrapped( + provider.metatype.prototype[traceMethod.methodName], + `${provider.name} (Provider) -> ${traceMethod.methodName}${ + traceMethod.traceName ? ` (${traceMethod.traceName})` : '' + }`, + { + provider: provider.name, + method: traceMethod.methodName, + ...(traceMethod.traceName + ? { traceName: traceMethod.traceName } + : {}), + }, + ); + } + } + } + + private getTraceMethods( + provider: InstanceWrapper, + ): { methodName: string; traceName?: string }[] { + const methods = this.getFilteredMethodNames(provider.metatype.prototype); + const traceMethods = []; + + for (const methodName of methods) { + const spanActive = Boolean( + Reflect.getMetadata( + SPAN_ACTIVE, + provider.metatype.prototype[methodName], + ), + ); + if (!spanActive) { + continue; + } + + const traceName = Reflect.getMetadata( + TRACE_NAME, + provider.metatype.prototype[methodName], + ); + traceMethods.push({ methodName, traceName }); + } + + return traceMethods; + } +} diff --git a/libs/api/observability/src/lib/trace-wrappers/trace-wrapper.spec.ts b/libs/api/observability/src/lib/trace-wrappers/trace-wrapper.spec.ts new file mode 100644 index 00000000..fb6f14bb --- /dev/null +++ b/libs/api/observability/src/lib/trace-wrappers/trace-wrapper.spec.ts @@ -0,0 +1,136 @@ +import { ModulesContainer } from '@nestjs/core'; + +import { createTraceMocks } from '@kordis/api/test-helpers'; + +import { TraceWrapper } from './abstract-trace-wrapper'; + +// event though the trace wrapper is abstract, we want to ensure that the methods gets wrapped correctly, +// so we don't have to validate everything in the concrete implementations. +describe('TraceWrapper', () => { + class TestableTraceWrapper extends TraceWrapper { + constructor(modulesContainer: ModulesContainer) { + super(modulesContainer); + } + + wrapWithSpans(): void {} + + public proxyAsWrapped( + prototype: Record, + traceName: string, + attributes = {}, + ): any { + return this.asWrapped(prototype, traceName, attributes); + } + } + + let traceWrapper: TestableTraceWrapper; + let modulesContainer: ModulesContainer; + + beforeEach(() => { + modulesContainer = new ModulesContainer(); + traceWrapper = new TestableTraceWrapper(modulesContainer); + }); + + afterEach(() => jest.clearAllMocks()); + + it('should start and end a span for wrapped methods', async () => { + class TestClass { + async asyncMethod() { + return Promise.resolve(); + } + syncMethod() {} + } + + TestClass.prototype.syncMethod = traceWrapper.proxyAsWrapped( + TestClass.prototype.syncMethod, + 'traceName', + ); + + TestClass.prototype.asyncMethod = traceWrapper.proxyAsWrapped( + TestClass.prototype.asyncMethod, + 'traceName', + ); + + const { startSpanSpy, endSpanSpy } = createTraceMocks(); + + TestClass.prototype.syncMethod(); + expect(startSpanSpy).toHaveBeenCalledTimes(1); + expect(endSpanSpy).toHaveBeenCalledTimes(1); + + startSpanSpy.mockClear(); + endSpanSpy.mockClear(); + + await TestClass.prototype.asyncMethod(); + expect(startSpanSpy).toHaveBeenCalledTimes(1); + expect(endSpanSpy).toHaveBeenCalledTimes(1); + }); + + it('should redecorate wrapped methods with their metadata', () => { + class TestClass { + @Reflect.metadata('key', 'value') + originalMethod() {} + } + + TestClass.prototype.originalMethod = traceWrapper.proxyAsWrapped( + TestClass.prototype.originalMethod, + 'traceName', + ); + + expect( + Reflect.getMetadata('key', TestClass.prototype, 'originalMethod'), + ).toBe('value'); + }); + + it('should call the original method', async () => { + class TestClass { + async asyncMethod() { + return Promise.resolve('async'); + } + + syncMethod() { + return 'sync'; + } + } + + TestClass.prototype.asyncMethod = traceWrapper.proxyAsWrapped( + TestClass.prototype.asyncMethod, + 'traceName', + ); + + TestClass.prototype.syncMethod = traceWrapper.proxyAsWrapped( + TestClass.prototype.syncMethod, + 'traceName', + ); + + await expect(TestClass.prototype.asyncMethod()).resolves.toBe('async'); + expect(TestClass.prototype.syncMethod()).toBe('sync'); + }); + + it('should handle and end span on throwing methods', async () => { + class TestClass { + async asyncMethod() { + throw new Error(); + } + + syncMethod() { + throw new Error(); + } + } + + const { endSpanSpy } = createTraceMocks(); + + TestClass.prototype.asyncMethod = traceWrapper.proxyAsWrapped( + TestClass.prototype.asyncMethod, + 'traceName', + ); + + TestClass.prototype.syncMethod = traceWrapper.proxyAsWrapped( + TestClass.prototype.syncMethod, + 'traceName', + ); + + await expect(TestClass.prototype.asyncMethod()).rejects.toThrow(); + expect(TestClass.prototype.syncMethod).toThrow(); + expect(endSpanSpy).toHaveBeenCalledTimes(2); + }); +}); diff --git a/libs/api/observability/tsconfig.json b/libs/api/observability/tsconfig.json new file mode 100644 index 00000000..18d14d61 --- /dev/null +++ b/libs/api/observability/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/api/observability/tsconfig.lib.json b/libs/api/observability/tsconfig.lib.json new file mode 100644 index 00000000..985a012b --- /dev/null +++ b/libs/api/observability/tsconfig.lib.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es6", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "../../../reset.d.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/api/observability/tsconfig.spec.json b/libs/api/observability/tsconfig.spec.json new file mode 100644 index 00000000..231650b3 --- /dev/null +++ b/libs/api/observability/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/libs/api/test-helpers/src/index.ts b/libs/api/test-helpers/src/index.ts index 02ae541b..8a24680f 100644 --- a/libs/api/test-helpers/src/index.ts +++ b/libs/api/test-helpers/src/index.ts @@ -1,2 +1,3 @@ -export { createGqlContextForRequest } from './lib/execution-context.test-helper'; -export { createParamDecoratorFactory } from './lib/decorator.test-helper'; +export * from './lib/execution-context.test-helper'; +export * from './lib/decorator.test-helper'; +export * from './lib/observability.test-helper'; diff --git a/libs/api/test-helpers/src/lib/observability.test-helper.ts b/libs/api/test-helpers/src/lib/observability.test-helper.ts new file mode 100644 index 00000000..0e348a68 --- /dev/null +++ b/libs/api/test-helpers/src/lib/observability.test-helper.ts @@ -0,0 +1,23 @@ +import { createMock } from '@golevelup/ts-jest'; +import { Span, trace } from '@opentelemetry/api'; +import { Tracer } from '@opentelemetry/tracing'; + +export function getComparablePrototypeSnapshot(prototype: Record) { + return JSON.stringify( + Object.getOwnPropertyDescriptors(prototype), + (key, value) => (typeof value === 'function' ? value.toString() : value), + ); +} + +export function createTraceMocks() { + const tracerSpy = jest.spyOn(trace, 'getTracer'); + tracerSpy.mockReturnValue(createMock()); // makes sure always the same tracer is used + const tracer = trace.getTracer('default'); + const startSpanSpy = jest.spyOn(tracer, 'startSpan'); + const spanMock = createMock(); + const spanSetAttributesSpy = jest.spyOn(spanMock, 'setAttributes'); + startSpanSpy.mockReturnValue(spanMock); + const endSpanSpy = jest.spyOn(spanMock, 'end'); + + return { startSpanSpy, endSpanSpy, spanSetAttributesSpy }; +} diff --git a/libs/spa/auth/src/index.ts b/libs/spa/auth/src/index.ts index f4c4c64a..60f17072 100644 --- a/libs/spa/auth/src/index.ts +++ b/libs/spa/auth/src/index.ts @@ -1,4 +1,5 @@ export { authGuard } from './lib/guards/auth.guard'; export { AUTH_SERVICE, AuthService } from './lib/services/auth-service'; +export { DevAuthService } from './lib/services/dev-auth.service'; export { AuthModule } from './lib/auth.module'; export { DevAuthModule } from './lib/dev-auth.module'; diff --git a/libs/spa/auth/src/lib/components/auth.component.spec.ts b/libs/spa/auth/src/lib/components/auth.component.spec.ts index 57579488..2b3d2874 100644 --- a/libs/spa/auth/src/lib/components/auth.component.spec.ts +++ b/libs/spa/auth/src/lib/components/auth.component.spec.ts @@ -1,7 +1,7 @@ -import { createMock } from '@golevelup/ts-jest'; import { SpectatorRouting, createRoutingFactory } from '@ngneat/spectator/jest'; -import { AUTH_SERVICE, AuthService } from '../services/auth-service'; +import { AUTH_SERVICE } from '../services/auth-service'; +import { DevAuthService } from '../services/dev-auth.service'; import { AuthComponent } from './auth.component'; describe('AuthComponent', () => { @@ -11,7 +11,7 @@ describe('AuthComponent', () => { componentProviders: [ { provide: AUTH_SERVICE, - useValue: createMock(), + useClass: DevAuthService, }, ], }); diff --git a/libs/spa/auth/src/lib/services/auth.service.spec.ts b/libs/spa/auth/src/lib/services/auth.service.spec.ts index 73ee40f9..e435285b 100644 --- a/libs/spa/auth/src/lib/services/auth.service.spec.ts +++ b/libs/spa/auth/src/lib/services/auth.service.spec.ts @@ -21,6 +21,8 @@ describe('AuthService', () => { beforeEach(() => (spectator = createService())); + afterEach(() => jest.clearAllMocks()); + it('should not be authenticated', async () => { await expect( firstValueFrom(spectator.service.isAuthenticated$), diff --git a/libs/spa/observability/.eslintrc.json b/libs/spa/observability/.eslintrc.json new file mode 100644 index 00000000..1a176068 --- /dev/null +++ b/libs/spa/observability/.eslintrc.json @@ -0,0 +1,36 @@ +{ + "extends": ["../../../.eslintrc.json", "../../../.eslintrc.angular.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts"], + "rules": { + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "krd", + "style": "camelCase" + } + ], + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "krd", + "style": "kebab-case" + } + ] + }, + "extends": [ + "plugin:@nx/angular", + "plugin:@angular-eslint/template/process-inline-templates" + ] + }, + { + "files": ["*.html"], + "extends": ["plugin:@nx/angular-template"], + "rules": {} + } + ] +} diff --git a/libs/spa/observability/README.md b/libs/spa/observability/README.md new file mode 100644 index 00000000..099a22d4 --- /dev/null +++ b/libs/spa/observability/README.md @@ -0,0 +1,7 @@ +# spa-observability + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test spa-observability` to execute the unit tests. diff --git a/libs/spa/observability/jest.config.ts b/libs/spa/observability/jest.config.ts new file mode 100644 index 00000000..bb5ca11a --- /dev/null +++ b/libs/spa/observability/jest.config.ts @@ -0,0 +1,22 @@ +/* eslint-disable */ +export default { + displayName: 'spa-observability', + preset: '../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../coverage/libs/spa/observability', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], +}; diff --git a/libs/spa/observability/project.json b/libs/spa/observability/project.json new file mode 100644 index 00000000..f571caa8 --- /dev/null +++ b/libs/spa/observability/project.json @@ -0,0 +1,34 @@ +{ + "name": "spa-observability", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/spa/observability/src", + "prefix": "krd", + "tags": [], + "projectType": "library", + "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/spa/observability/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": [ + "libs/spa/observability/**/*.ts", + "libs/spa/observability/**/*.html" + ] + } + } + } +} diff --git a/libs/spa/observability/src/index.ts b/libs/spa/observability/src/index.ts new file mode 100644 index 00000000..e4d158c7 --- /dev/null +++ b/libs/spa/observability/src/index.ts @@ -0,0 +1,4 @@ +export * from './lib/sentry-observability.module'; +export * from './lib/decorators'; +export * from './lib/sentry-observability.service'; +export * from './lib/noop-observability.module'; diff --git a/libs/spa/observability/src/lib/decorators/index.ts b/libs/spa/observability/src/lib/decorators/index.ts new file mode 100644 index 00000000..9f7d7c06 --- /dev/null +++ b/libs/spa/observability/src/lib/decorators/index.ts @@ -0,0 +1,2 @@ +export * from './trace-component.decorator'; +export * from './trace-method.decorator'; diff --git a/libs/spa/observability/src/lib/decorators/trace-component.decorator.ts b/libs/spa/observability/src/lib/decorators/trace-component.decorator.ts new file mode 100644 index 00000000..d90282eb --- /dev/null +++ b/libs/spa/observability/src/lib/decorators/trace-component.decorator.ts @@ -0,0 +1,5 @@ +import { TraceClassDecorator } from '@sentry/angular-ivy'; + +export function TraceComponent(): ClassDecorator { + return TraceClassDecorator(); +} diff --git a/libs/spa/observability/src/lib/decorators/trace-method.decorator.ts b/libs/spa/observability/src/lib/decorators/trace-method.decorator.ts new file mode 100644 index 00000000..1869255d --- /dev/null +++ b/libs/spa/observability/src/lib/decorators/trace-method.decorator.ts @@ -0,0 +1,5 @@ +import { TraceMethodDecorator } from '@sentry/angular-ivy'; + +export function TraceMethod(): MethodDecorator { + return TraceMethodDecorator(); +} diff --git a/libs/spa/observability/src/lib/noop-observability.module.ts b/libs/spa/observability/src/lib/noop-observability.module.ts new file mode 100644 index 00000000..e88a97ca --- /dev/null +++ b/libs/spa/observability/src/lib/noop-observability.module.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { ModuleWithProviders, NgModule } from '@angular/core'; + +import { + NoopObservabilityService, + OBSERVABILITY_SERVICE, +} from './sentry-observability.service'; + +@NgModule({ + imports: [CommonModule], +}) +export class NoopObservabilityModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: NoopObservabilityModule, + providers: [ + { + provide: OBSERVABILITY_SERVICE, + useClass: NoopObservabilityService, + }, + ], + }; + } +} diff --git a/libs/spa/observability/src/lib/sentry-observability.module.ts b/libs/spa/observability/src/lib/sentry-observability.module.ts new file mode 100644 index 00000000..0ad96f1b --- /dev/null +++ b/libs/spa/observability/src/lib/sentry-observability.module.ts @@ -0,0 +1,80 @@ +import { CommonModule } from '@angular/common'; +import { + APP_INITIALIZER, + ErrorHandler, + ModuleWithProviders, + NgModule, +} from '@angular/core'; +import { Router } from '@angular/router'; +import { + BrowserTracing, + Replay, + TraceService, + createErrorHandler, + init as initSentry, + instrumentAngularRouting, +} from '@sentry/angular-ivy'; + +import { + OBSERVABILITY_SERVICE, + SentryObservabilityService, +} from './sentry-observability.service'; + +@NgModule({ + imports: [CommonModule], +}) +export class SentryObservabilityModule { + static forRoot( + sentryKey: string, + environment: string, + release: string, + ): ModuleWithProviders { + this.startSentry(sentryKey, environment, release); + + return { + ngModule: SentryObservabilityModule, + providers: [ + { + provide: ErrorHandler, + useValue: createErrorHandler(), + }, + { + provide: TraceService, + deps: [Router], + }, + { + provide: APP_INITIALIZER, + // eslint-disable-next-line @typescript-eslint/no-empty-function + useFactory: () => () => {}, + deps: [TraceService], + multi: true, + }, + { + provide: OBSERVABILITY_SERVICE, + useClass: SentryObservabilityService, + }, + ], + }; + } + + private static startSentry( + dsn: string, + environment: string, + release: string, + ): void { + initSentry({ + dsn, + integrations: [ + new BrowserTracing({ + routingInstrumentation: instrumentAngularRouting, + }), + new Replay(), + ], + environment, + release, + tracesSampleRate: 1.0, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + }); + } +} diff --git a/libs/spa/observability/src/lib/sentry-observability.service.spec.ts b/libs/spa/observability/src/lib/sentry-observability.service.spec.ts new file mode 100644 index 00000000..73b63621 --- /dev/null +++ b/libs/spa/observability/src/lib/sentry-observability.service.spec.ts @@ -0,0 +1,65 @@ +import { SpectatorService, createServiceFactory } from '@ngneat/spectator/jest'; +import * as SentryAngularIvy from '@sentry/angular-ivy'; + +import { AUTH_SERVICE, DevAuthService } from '@kordis/spa/auth'; + +import { SentryObservabilityService } from './sentry-observability.service'; + +const sentrySetUser = jest.spyOn(SentryAngularIvy, 'setUser'); + +describe('SentryObservabilityService', () => { + let spectator: SpectatorService; + const createService = createServiceFactory({ + service: SentryObservabilityService, + providers: [ + { + provide: AUTH_SERVICE, + useClass: DevAuthService, + }, + ], + }); + + beforeEach(() => (spectator = createService())); + + afterEach(() => { + sentrySetUser.mockClear(); + }); + + it('should set the user when provided with id, email, and username', () => { + const id = '1'; + const email = 'test@example.com'; + const username = 'testuser'; + + spectator.service.setUser(id, email, username); + + expect(sentrySetUser).toHaveBeenCalledTimes(2); + expect(sentrySetUser).toHaveBeenCalledWith({ + id, + email, + username, + }); + }); + + it('should set the user to null when no id is provided', () => { + spectator.service.setUser(); + + expect(sentrySetUser).toHaveBeenCalledTimes(2); + expect(sentrySetUser).toHaveBeenCalledWith(null); + }); + + it('should set observability user on auth user change', async () => { + const authService = spectator.inject(AUTH_SERVICE); + authService.setSession({ + id: '1', + firstName: 'firstname', + lastName: 'lastname', + email: 'testmail', + }); + expect(sentrySetUser).toHaveBeenCalledTimes(2); + expect(sentrySetUser).toHaveBeenCalledWith({ + id: '1', + email: 'testmail', + username: 'firstname lastname', + }); + }); +}); diff --git a/libs/spa/observability/src/lib/sentry-observability.service.ts b/libs/spa/observability/src/lib/sentry-observability.service.ts new file mode 100644 index 00000000..e54e57e8 --- /dev/null +++ b/libs/spa/observability/src/lib/sentry-observability.service.ts @@ -0,0 +1,48 @@ +import { Inject, Injectable, InjectionToken } from '@angular/core'; +import { setUser as sentrySetUser } from '@sentry/angular-ivy'; + +import { AUTH_SERVICE, AuthService } from '@kordis/spa/auth'; + +export interface ObservabilityService { + setUser(id?: string, email?: string, name?: string): void; +} + +export const OBSERVABILITY_SERVICE = new InjectionToken( + 'OBSERVABILITY_SERVICE', +); + +@Injectable() +export class SentryObservabilityService implements ObservabilityService { + constructor(@Inject(AUTH_SERVICE) private readonly authService: AuthService) { + this.subscribeToUserChanges(); + } + + setUser(id?: string, email?: string, username?: string): void { + if (!id) { + sentrySetUser(null); + } else { + sentrySetUser({ + id, + email, + username, + }); + } + } + + private subscribeToUserChanges(): void { + this.authService.user$.subscribe((user) => { + this.setUser( + user?.id, + user?.email, + `${user?.firstName} ${user?.lastName}`, + ); + }); + } +} + +@Injectable() +export class NoopObservabilityService implements ObservabilityService { + setUser(): void { + // noop + } +} diff --git a/libs/spa/observability/src/test-setup.ts b/libs/spa/observability/src/test-setup.ts new file mode 100644 index 00000000..1100b3e8 --- /dev/null +++ b/libs/spa/observability/src/test-setup.ts @@ -0,0 +1 @@ +import 'jest-preset-angular/setup-jest'; diff --git a/libs/spa/observability/tsconfig.json b/libs/spa/observability/tsconfig.json new file mode 100644 index 00000000..5cf0a165 --- /dev/null +++ b/libs/spa/observability/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es2022", + "useDefineForClassFields": false, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../../tsconfig.base.json", + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/libs/spa/observability/tsconfig.lib.json b/libs/spa/observability/tsconfig.lib.json new file mode 100644 index 00000000..f425b436 --- /dev/null +++ b/libs/spa/observability/tsconfig.lib.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "types": [] + }, + "exclude": [ + "src/**/*.spec.ts", + "src/test-setup.ts", + "jest.config.ts", + "src/**/*.test.ts" + ], + "include": ["src/**/*.ts", "../../../reset.d.ts"] +} diff --git a/libs/spa/observability/tsconfig.spec.json b/libs/spa/observability/tsconfig.spec.json new file mode 100644 index 00000000..fb725351 --- /dev/null +++ b/libs/spa/observability/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2016", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/package-lock.json b/package-lock.json index d8904462..3c019d70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,14 @@ "@nestjs/graphql": "^11.0.4", "@nestjs/mongoose": "^9.2.2", "@nestjs/platform-express": "9.4.0", + "@opentelemetry/instrumentation-graphql": "^0.34.1", + "@opentelemetry/instrumentation-mongoose": "^0.32.2", + "@opentelemetry/sdk-node": "^0.39.1", + "@opentelemetry/tracing": "^0.24.0", + "@sentry/angular-ivy": "^7.50.0", + "@sentry/node": "^7.52.1", + "@sentry/opentelemetry-node": "^7.52.1", + "@sentry/profiling-node": "^0.3.0", "angular-oauth2-oidc": "^15.0.1", "axios": "^1.0.0", "graphql": "^16.6.0", @@ -524,15 +532,15 @@ } }, "node_modules/@angular/cli": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.0.1.tgz", - "integrity": "sha512-0vIAcq/S+3NXXN4/gBQFVGaxLUQ0zhRxxHQQuiT7GGII73UySuhwvaFB1BEhYG5HVJjRrP1F0ZYbvsvrmFzfXQ==", + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.0.2.tgz", + "integrity": "sha512-D2LnNUSLFmfpOTIppGBxvA9kXvXUtoOtprQjwxE/LOtw9rmOZv0fNCbLG2m5GMxSsTs2qfGV04bTzme0Lp8HPQ==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1600.1", - "@angular-devkit/core": "16.0.1", - "@angular-devkit/schematics": "16.0.1", - "@schematics/angular": "16.0.1", + "@angular-devkit/architect": "0.1600.2", + "@angular-devkit/core": "16.0.2", + "@angular-devkit/schematics": "16.0.2", + "@schematics/angular": "16.0.2", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "4.0.0", @@ -558,12 +566,12 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1600.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.1.tgz", - "integrity": "sha512-7N3Dugrp3Fyyn3Q6RsxFNJJ2m1QuqcF3GHJcX7siINL37Hp6xI/q5gKffcd9rf20H1DYZE0VIbR1Sk31G6hMWg==", + "version": "0.1600.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1600.2.tgz", + "integrity": "sha512-2AOP3/dwLywcjkRr3ixR/lb0uBn1jzaMWwQR3o7ye3IuEA2sRtyWhUzsy6V7smKBKWPDIbXvX2TcqYZAJ87ccA==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.0.1", + "@angular-devkit/core": "16.0.2", "rxjs": "7.8.1" }, "engines": { @@ -573,9 +581,9 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.1.tgz", - "integrity": "sha512-2uz98IqkKJlgnHbWQ7VeL4pb+snGAZXIama2KXi+k9GsRntdcw+udX8rL3G9SdUGUF+m6+147Y1oRBMHsO/v4w==", + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.2.tgz", + "integrity": "sha512-V4+t0BHO+QML9O2IiG2mJi8DtjeMOm4LAuG6tNDeiHZGAPOflvSPsKBtVl2JlXX/JxdLmyF4B6kRoAXRMKcwTg==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -599,12 +607,12 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.0.1.tgz", - "integrity": "sha512-A9D0LTYmiqiBa90GKcSuWb7hUouGIbm/AHbJbjL85WLLRbQA2PwKl7P5Mpd6nS/ZC0kfG4VQY3VOaDvb3qpI9g==", + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.0.2.tgz", + "integrity": "sha512-z9GDVHhpEXvOQeekFuGghoFR/HikI66LoEifG+jT659N5ggFLJ88hDnXxeR21yUy3BjvnI+c3gRaOnccWAA7ug==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.0.1", + "@angular-devkit/core": "16.0.2", "jsonc-parser": "3.2.0", "magic-string": "0.30.0", "ora": "5.4.1", @@ -617,13 +625,13 @@ } }, "node_modules/@angular/cli/node_modules/@schematics/angular": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.0.1.tgz", - "integrity": "sha512-MNgH/iB3WWxMLFVHJjtXCHZ8YHtfx2e3mX2Ds5P43OTgSnTk6tHabqvwxJ4wzjoyoPUyXWLhHt0diCmVtDTNeQ==", + "version": "16.0.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.0.2.tgz", + "integrity": "sha512-uur0oSAKu9vkFJuXhSiMjkhgLb7RFtAkUpED7Mx5APXIgAvNylOVQXONmBHBY/2mBJDjt+7giLKweAqSK9PtTg==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.0.1", - "@angular-devkit/schematics": "16.0.1", + "@angular-devkit/core": "16.0.2", + "@angular-devkit/schematics": "16.0.2", "jsonc-parser": "3.2.0" }, "engines": { @@ -4949,8 +4957,7 @@ "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==" }, "node_modules/@golevelup/ts-jest": { "version": "0.3.7", @@ -5004,6 +5011,36 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.14.tgz", + "integrity": "sha512-w84maJ6CKl5aApCMzFll0hxtFNT6or9WwMslobKaqWUEf1K+zhlL43bSQhFreyYWIWR+Z0xnVFC1KtLm4ZpM/A==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", + "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -6164,7 +6201,6 @@ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -6177,7 +6213,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -7242,6 +7277,451 @@ "node": ">=10" } }, + "node_modules/@opentelemetry/api": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.4.1.tgz", + "integrity": "sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.39.1.tgz", + "integrity": "sha512-9BJ8lMcOzEN0lu+Qji801y707oFO4xT3db6cosPvl+k7ItUHKN5ofWqtSbM9gbt1H4JJ/4/2TVrqI9Rq7hNv6Q==", + "dependencies": { + "@opentelemetry/api": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.13.0.tgz", + "integrity": "sha512-pS5fU4lrRjOIPZQqA2V1SUM9QUFXbO+8flubAiy6ntLjnAjJJUdRFOUOxK6v86ZHI2p2S8A0vD0BTu95FZYvjA==", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.13.0.tgz", + "integrity": "sha512-2dBX3Sj99H96uwJKvc2w9NOiNgbvAO6mOFJFramNkKfS9O4Um+VWgpnlAazoYjT6kUJ1MP70KQ5ngD4ed+4NUw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/exporter-jaeger": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.13.0.tgz", + "integrity": "sha512-ke/STs/erRDqKmNv6Dv+5SetXsVD+Zm1/Wo8cLdAGrZn6kG6Fyp5EXVO/BJuzx6q+jHCdODm8jV4veXl4m71nQ==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0", + "jaeger-client": "^3.15.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.39.1.tgz", + "integrity": "sha512-l5RhLKx6U+yuLhMrtgavTDthX50E1mZM3/SSySC7OPZiArFHV/b/9x9jxAzrOgIQUDxyj4N0V9aLKSA2t7Qzxg==", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-grpc-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-http": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.39.1.tgz", + "integrity": "sha512-AEhnJfVmo1g+7NxszAuf3c6vddld2DGH2+IM4XrPxCklucCsIpuStuC5EVZbCXXXBMpAY+n3t04QMxIQqNrcSw==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-trace-otlp-proto": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.39.1.tgz", + "integrity": "sha512-oJQC7a67iwExRYynKqn/O9Fl5gUjDa43ZQsZu2iKAADs/6YJ+u5MJ/wcq3CpJsn2KU/8j8HWAKOcDkkQXPuJ9A==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "@opentelemetry/otlp-proto-exporter-base": "0.39.1", + "@opentelemetry/otlp-transformer": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/exporter-zipkin": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.13.0.tgz", + "integrity": "sha512-4IuUmYEhlHm8tAGtd6KKkktEO9Bt7dpdBdAPVAzhmXsPwGi0yExo7E5qfi9HtHQcdfP9SnrGRkeorVtrZkGlhg==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.39.1.tgz", + "integrity": "sha512-s7/9tPmM0l5KCd07VQizC4AO2/5UJdkXq5gMSHPdCeiMKSeBEdyDyQX7A+Cq+RYZM452qzFmrJ4ut628J5bnSg==", + "dependencies": { + "require-in-the-middle": "^7.1.0", + "semver": "^7.3.2", + "shimmer": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.34.2.tgz", + "integrity": "sha512-0DZmTNsUp0Wf6P+Q6rP02DlUzxdS0+YmxZXXrAiwvd0+vjPyPY8Vc+4EcZS/hoHJtlzZtgnChDzucCfu8sYY1Q==", + "dependencies": { + "@opentelemetry/instrumentation": "^0.39.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.32.3", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.32.3.tgz", + "integrity": "sha512-xWi9nLWc+U7myAI3gO+FrxRDEBGhZb5wnsaHhlhOXGqNARWQcuN1JF4uGR0XG5hyMSG4LWv6FgHDcDDPRzMEZQ==", + "dependencies": { + "@opentelemetry/core": "^1.8.0", + "@opentelemetry/instrumentation": "^0.39.1", + "@opentelemetry/semantic-conventions": "^1.0.0" + }, + "engines": { + "node": ">=14.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.39.1.tgz", + "integrity": "sha512-Pv5X8fbi6jD/RJBePyn7MnCSuE6MbPB6dl+7YYBWJ5RcMGYMwvLXjd4h2jWsPV2TSUg38H/RoSP0aXvQ06Y7iw==", + "dependencies": { + "@opentelemetry/core": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/otlp-grpc-exporter-base": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.39.1.tgz", + "integrity": "sha512-u3ErFRQqQFKjjIMuwLWxz/tLPYInfmiAmSy//fGSCzCh2ZdJgqQjMOAxBgqFtCF2xFL+OmMhyuC2ThMzceGRWA==", + "dependencies": { + "@grpc/grpc-js": "^1.7.1", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "protobufjs": "^7.2.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/otlp-proto-exporter-base": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-proto-exporter-base/-/otlp-proto-exporter-base-0.39.1.tgz", + "integrity": "sha512-VssdfGYu6LkSliQATdkvoP8lPSQuNLENRdHTUOV2veF4iqY/UpxBFFlkarY29W+MYjWXIBfYntgNjQvcn78A+w==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/otlp-exporter-base": "0.39.1", + "protobufjs": "^7.1.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.39.1.tgz", + "integrity": "sha512-0hgVnXXz5efI382B/24NxD4b6Zxlh7nxCdJkxkdmQMbn0yRiwoq/ZT+QG8eUL6JNzsBAV1WJlF5aJNsL8skHvw==", + "dependencies": { + "@opentelemetry/api-logs": "0.39.1", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-logs": "0.39.1", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/propagator-b3": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.13.0.tgz", + "integrity": "sha512-HOo91EI4UbuG8xQVLFziTzrcIn0MJQhy8m9jorh8aonb94jFVFi3CFNIiAnIGOabmnshJLOABxpYXsiPB8Xnzg==", + "dependencies": { + "@opentelemetry/core": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/propagator-jaeger": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.13.0.tgz", + "integrity": "sha512-IV9TO+u1Jzm9mUDAD3gyXf89eyvgEJUY1t+GB5QmS4wjVeWrSMUtD0JjH3yG9SNqkrQOqOGJq7YUSSetW+Lf5Q==", + "dependencies": { + "@opentelemetry/core": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.13.0.tgz", + "integrity": "sha512-euqjOkiN6xhjE//0vQYGvbStxoD/WWQRhDiO0OTLlnLBO9Yw2Gd/VoSx2H+svsebjzYk5OxLuREBmcdw6rbUNg==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.39.1.tgz", + "integrity": "sha512-/gmgKfZ1ZVFporKuwsewqIyvaUIGpv76JZ7lBpHQQPb37IMpaXO6pdqFI4ebHAWfNIm3akMyhmdtzivcgF3lgw==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.5.0", + "@opentelemetry/api-logs": ">=0.38.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.13.0.tgz", + "integrity": "sha512-MOjZX6AnSOqLliCcZUrb+DQKjAWXBiGeICGbHAGe5w0BB18PJIeIo995lO5JSaFfHpmUMgJButTPfJJD27W3Vg==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "lodash.merge": "4.6.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/sdk-node": { + "version": "0.39.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.39.1.tgz", + "integrity": "sha512-qODReBGNSdfRS5gvCFj1SdiIi/3ZFTZb0H1KvWE/OrTkklyL5RhIs7vDwvEGHmha+YpUu0Y2+R2+itSBSu/jCA==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/exporter-jaeger": "1.13.0", + "@opentelemetry/exporter-trace-otlp-grpc": "0.39.1", + "@opentelemetry/exporter-trace-otlp-http": "0.39.1", + "@opentelemetry/exporter-trace-otlp-proto": "0.39.1", + "@opentelemetry/exporter-zipkin": "1.13.0", + "@opentelemetry/instrumentation": "0.39.1", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/sdk-metrics": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "@opentelemetry/sdk-trace-node": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.13.0.tgz", + "integrity": "sha512-moTiQtc0uPR1hQLt6gLDJH9IIkeBhgRb71OKjNHZPE1VF45fHtD6nBDi5J/DkTHTwYP5X3kBJLa3xN7ub6J4eg==", + "dependencies": { + "@opentelemetry/core": "1.13.0", + "@opentelemetry/resources": "1.13.0", + "@opentelemetry/semantic-conventions": "1.13.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-node": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.13.0.tgz", + "integrity": "sha512-FXA85lXKTsnbOflA/TBuBf2pmhD3c8uDjNjG0YqK+ap8UayfALmfJhf+aG1yBOUHevCY0JXJ4/xtbXExxpsMog==", + "dependencies": { + "@opentelemetry/context-async-hooks": "1.13.0", + "@opentelemetry/core": "1.13.0", + "@opentelemetry/propagator-b3": "1.13.0", + "@opentelemetry/propagator-jaeger": "1.13.0", + "@opentelemetry/sdk-trace-base": "1.13.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.5.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.13.0.tgz", + "integrity": "sha512-LMGqfSZkaMQXqewO0o1wvWr/2fQdCh4a3Sqlxka/UsJCe0cfLulh6x2aqnKLnsrSGiCq5rSCwvINd152i0nCqw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/tracing": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/tracing/-/tracing-0.24.0.tgz", + "integrity": "sha512-sTLEs1SIon3xV8vLe53PzfbU0FahoxL9NPY/CYvA1mwGbMu4zHkHAjqy1Tc8JmqRrfa+XrHkmzeSM4hrvloBaA==", + "deprecated": "Package renamed to @opentelemetry/sdk-trace-base", + "dependencies": { + "@opentelemetry/core": "0.24.0", + "@opentelemetry/resources": "0.24.0", + "@opentelemetry/semantic-conventions": "0.24.0", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.1" + } + }, + "node_modules/@opentelemetry/tracing/node_modules/@opentelemetry/core": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-0.24.0.tgz", + "integrity": "sha512-KpsfxBbFTZT9zaB4Es/fFLbvSzVl9Io/8UUu/TYl4/HgqkmyVInNlWTgRiKyz9nsHzFpGP1kdZJj+YIut0IFsw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "0.24.0", + "semver": "^7.1.3" + }, + "engines": { + "node": ">=8.5.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.1" + } + }, + "node_modules/@opentelemetry/tracing/node_modules/@opentelemetry/resources": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-0.24.0.tgz", + "integrity": "sha512-uEr2m13IRkjQAjX6fsYqJ21aONCspRvuQunaCl8LbH1NS1Gj82TuRUHF6TM82ulBPK8pU+nrrqXKuky2cMcIzw==", + "dependencies": { + "@opentelemetry/core": "0.24.0", + "@opentelemetry/semantic-conventions": "0.24.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.1" + } + }, + "node_modules/@opentelemetry/tracing/node_modules/@opentelemetry/semantic-conventions": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.24.0.tgz", + "integrity": "sha512-a/szuMQV0Quy0/M7kKdglcbRSoorleyyOwbTNNJ32O+RBN766wbQlMTvdimImTmwYWGr+NJOni1EcC242WlRcA==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@parcel/watcher": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz", @@ -7345,32 +7825,240 @@ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@schematics/angular": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.0.0.tgz", + "integrity": "sha512-Ao1Y0hEDa30JjWDLnUfOsD+9nnfdBFclfKFzR+7pvvFYCpSUhH1u+8e+7noruIxlP26+SpqPn3AF5+IRTGza8w==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.0.0", + "@angular-devkit/schematics": "16.0.0", + "jsonc-parser": "3.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.52.1.tgz", + "integrity": "sha512-6N99rE+Ek0LgbqSzI/XpsKSLUyJjQ9nychViy+MP60p1x+hllukfTsDbNtUNrPlW0Bx+vqUrWKkAqmTFad94TQ==", + "dependencies": { + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/angular-ivy": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/angular-ivy/-/angular-ivy-7.52.1.tgz", + "integrity": "sha512-aCCyrbGb45Tx+cwi/86FuCmSK/F9CBGvL8L/86akgvpTcKzmZWG8OIF3SkVnSEB4vjlTpi9pPHIo9PdpO6I9XQ==", + "dependencies": { + "@sentry/browser": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@angular/common": ">= 12.x <= 16.x", + "@angular/core": ">= 12.x <= 16.x", + "@angular/router": ">= 12.x <= 16.x", + "rxjs": "^6.5.5 || ^7.x" + } + }, + "node_modules/@sentry/browser": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.52.1.tgz", + "integrity": "sha512-HrCOfieX68t+Wj42VIkraLYwx8kN5311SdBkHccevWs2Y2dZU7R9iLbI87+nb5kpOPQ7jVWW7d6QI/yZmliYgQ==", + "dependencies": { + "@sentry-internal/tracing": "7.52.1", + "@sentry/core": "7.52.1", + "@sentry/replay": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/core": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.52.1.tgz", + "integrity": "sha512-36clugQu5z/9jrit1gzI7KfKbAUimjRab39JeR0mJ6pMuKLTTK7PhbpUAD4AQBs9qVeXN2c7h9SVZiSA0UDvkg==", + "dependencies": { + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/hub": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-7.52.1.tgz", + "integrity": "sha512-iNHB33ATTxpTNNOc9cQwuSweU6dufDeHPpOC57uVgqZVd+hb4XW/8A/4MckdqeEEMfamvdERzG6Oovy3V0GvzQ==", + "dependencies": { + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/hub/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/node": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.52.1.tgz", + "integrity": "sha512-n3frjYbkY/+eZ5RTQMaipv6Hh9w3ia40GDeRK6KJQit7OLKLmXisD+FsdYzm8Jc784csSvb6HGGVgqLpO1p9Og==", + "dependencies": { + "@sentry-internal/tracing": "7.52.1", + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/opentelemetry-node": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry-node/-/opentelemetry-node-7.52.1.tgz", + "integrity": "sha512-Ur6y8fK2b3spGJ1EMxSfuiJQvLilxBZN9hzBoVwWPKFt+LPxVO5ksxJS1MwjZN7aPsWu0VwVLMOnpT2X71FLfg==", + "dependencies": { + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "@opentelemetry/api": "1.x", + "@opentelemetry/core": "1.x", + "@opentelemetry/sdk-trace-base": "1.x", + "@opentelemetry/semantic-conventions": "1.x" + } + }, + "node_modules/@sentry/profiling-node": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@sentry/profiling-node/-/profiling-node-0.3.0.tgz", + "integrity": "sha512-zodtr8JgI3m7GOoELEiE+zokEhyVoP6I4PvfPOTA6Etud4CUZ1B6uM7Ls8VgWAwlrPxJzaP+GJUJevZ0P2jfOQ==", + "hasInstallScript": true, + "dependencies": { + "@sentry/hub": "^7.44.1", + "@sentry/node": "^7.44.1", + "@sentry/tracing": "^7.44.1", + "@sentry/types": "^7.44.1", + "@sentry/utils": "^7.44.1", + "detect-libc": "^2.0.1", + "nan": "^2.17.0", + "node-abi": "^3.28.0", + "node-gyp": "^9.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@sentry/replay": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.52.1.tgz", + "integrity": "sha512-A+RaUmpU9/yBHnU3ATemc6wAvobGno0yf5R6fZYkAFoo2FCR2YG6AXxkTazymIf8v2DnLGaSDORYDPdhQClU9A==", + "dependencies": { + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sentry/tracing": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.52.1.tgz", + "integrity": "sha512-1afFeb0X6YcMK8mcsGXpO9rNFEF4Kd3mAUF22hXyFNWVoPNQsvdh/WxG2t3U+hLhehQ1ps3iJ0jxFRGF5zSboA==", + "dependencies": { + "@sentry-internal/tracing": "7.52.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + "node_modules/@sentry/types": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.52.1.tgz", + "integrity": "sha512-OMbGBPrJsw0iEXwZ2bJUYxewI1IEAU2e1aQGc0O6QW5+6hhCh+8HO8Xl4EymqwejjztuwStkl6G1qhK+Q0/Row==", + "engines": { + "node": ">=8" + } }, - "node_modules/@schematics/angular": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.0.0.tgz", - "integrity": "sha512-Ao1Y0hEDa30JjWDLnUfOsD+9nnfdBFclfKFzR+7pvvFYCpSUhH1u+8e+7noruIxlP26+SpqPn3AF5+IRTGza8w==", - "dev": true, + "node_modules/@sentry/utils": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.52.1.tgz", + "integrity": "sha512-MPt1Xu/jluulknW8CmZ2naJ53jEdtdwCBSo6fXJvOTI0SDqwIPbXDVrsnqLAhVJuIN7xbkj96nuY/VBR6S5sWg==", "dependencies": { - "@angular-devkit/core": "16.0.0", - "@angular-devkit/schematics": "16.0.0", - "jsonc-parser": "3.2.0" + "@sentry/types": "7.52.1", + "tslib": "^1.9.3" }, "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=8" } }, + "node_modules/@sentry/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", @@ -7448,7 +8136,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, "engines": { "node": ">= 10" } @@ -8809,8 +9496,7 @@ "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "node_modules/accepts": { "version": "1.3.8", @@ -8913,7 +9599,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, "dependencies": { "debug": "4" }, @@ -8925,7 +9610,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", - "dev": true, "dependencies": { "debug": "^4.1.0", "depd": "^2.0.0", @@ -8939,7 +9623,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -9005,6 +9688,14 @@ "@angular/core": ">=14.0.0" } }, + "node_modules/ansi-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz", + "integrity": "sha512-bF6xLaZBLpOQzgYUtYEhJx090nPSZk1BQ/q2oyBK9aMMcJHzx9uXGCjI2Y+LebsN4Jwoykr0V9whbPiogdyHoQ==", + "engines": { + "node": "*" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -9045,7 +9736,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -9090,14 +9780,12 @@ "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "node_modules/are-we-there-yet": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -9110,7 +9798,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -9514,8 +10201,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -9694,7 +10380,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9802,6 +10487,20 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/bufrw": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bufrw/-/bufrw-1.3.0.tgz", + "integrity": "sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ==", + "dependencies": { + "ansi-color": "^0.2.1", + "error": "^7.0.0", + "hexer": "^1.5.0", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.10.x" + } + }, "node_modules/builtins": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", @@ -9975,9 +10674,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001487", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz", - "integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==", + "version": "1.0.30001488", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", + "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", "dev": true, "funding": [ { @@ -10060,7 +10759,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, "engines": { "node": ">=10" } @@ -10099,7 +10797,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, "engines": { "node": ">=6" } @@ -10233,7 +10930,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, "bin": { "color-support": "bin.js" } @@ -10373,8 +11069,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -10423,8 +11118,7 @@ "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -10499,9 +11193,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", "engines": { "node": ">= 0.6" } @@ -11250,8 +11944,7 @@ "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "node_modules/depd": { "version": "2.0.0", @@ -11270,6 +11963,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -11535,8 +12236,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/emojis-list": { "version": "3.0.0", @@ -11623,7 +12323,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, "engines": { "node": ">=6" } @@ -11631,8 +12330,7 @@ "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" }, "node_modules/errno": { "version": "0.1.8", @@ -11647,6 +12345,15 @@ "errno": "cli.js" } }, + "node_modules/error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha512-UtVv4l5MhijsYUxPJo4390gzfZvAnTHreNnDjnTZaKIiZ/SemXxAhBkYSKtWa5RtBXbLP8tMgn/n0RUa/H7jXw==", + "dependencies": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -11735,7 +12442,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -12313,6 +13019,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -12979,8 +13693,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.2", @@ -13013,7 +13726,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", @@ -13041,7 +13753,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -13104,7 +13815,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13140,7 +13850,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -13149,7 +13858,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -13219,8 +13927,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -13384,8 +14091,7 @@ "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, "node_modules/hdr-histogram-js": { "version": "2.0.3", @@ -13413,6 +14119,23 @@ "he": "bin/he" } }, + "node_modules/hexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/hexer/-/hexer-1.5.0.tgz", + "integrity": "sha512-dyrPC8KzBzUJ19QTIo1gXNqIISRXQ0NwteW6OeQHRN4ZuZeHkdODfj0zHBdOlHbRY8GqbqK57C9oWSvQZizFsg==", + "dependencies": { + "ansi-color": "^0.2.1", + "minimist": "^1.1.0", + "process": "^0.10.0", + "xtend": "^4.0.0" + }, + "bin": { + "hexer": "cli.js" + }, + "engines": { + "node": ">= 0.10.x" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -13495,8 +14218,7 @@ "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, "node_modules/http-deceiver": { "version": "1.2.7", @@ -13543,7 +14265,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -13620,7 +14341,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -13642,7 +14362,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, "dependencies": { "ms": "^2.0.0" } @@ -13829,7 +14548,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, "engines": { "node": ">=0.8.19" } @@ -13838,7 +14556,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, "engines": { "node": ">=8" } @@ -13846,14 +14563,12 @@ "node_modules/infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -14017,7 +14732,6 @@ "version": "2.12.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -14067,7 +14781,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -14104,8 +14817,7 @@ "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" }, "node_modules/is-map": { "version": "2.0.2", @@ -14367,8 +15079,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", @@ -14494,6 +15205,29 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jaeger-client": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/jaeger-client/-/jaeger-client-3.19.0.tgz", + "integrity": "sha512-M0c7cKHmdyEUtjemnJyx/y9uX16XHocL46yQvyqDlPdvAcwPDbHrIbKjQdBqtiE4apQ/9dmr+ZLJYYPGnurgpw==", + "dependencies": { + "node-int64": "^0.4.0", + "opentracing": "^0.14.4", + "thriftrw": "^3.5.0", + "uuid": "^8.3.2", + "xorshift": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jaeger-client/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jake": { "version": "10.8.6", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.6.tgz", @@ -16085,8 +16819,7 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -16121,8 +16854,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/lodash.mergewith": { "version": "4.6.2", @@ -16197,6 +16929,11 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -16260,7 +16997,6 @@ "version": "10.2.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", @@ -16287,7 +17023,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" @@ -16300,7 +17035,6 @@ "version": "16.1.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", @@ -16329,7 +17063,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -16341,7 +17074,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -16353,7 +17085,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -16365,7 +17096,6 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, "dependencies": { "minipass": "^3.1.1" }, @@ -16377,7 +17107,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, "dependencies": { "unique-slug": "^3.0.0" }, @@ -16389,7 +17118,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, "dependencies": { "imurmurhash": "^0.1.4" }, @@ -16646,7 +17374,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -16655,7 +17382,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -16667,7 +17393,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -16679,7 +17404,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, "dependencies": { "minipass": "^3.1.6", "minipass-sized": "^1.0.3", @@ -16696,7 +17420,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -16708,7 +17431,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -16720,7 +17442,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -16754,7 +17475,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -16766,7 +17486,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -16778,7 +17497,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -16790,7 +17508,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -16802,7 +17519,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -16815,7 +17531,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -16834,6 +17549,11 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/module-details-from-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" + }, "node_modules/mongo-migrate-ts": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/mongo-migrate-ts/-/mongo-migrate-ts-1.1.4.tgz", @@ -17029,8 +17749,7 @@ "node_modules/nan": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", - "dev": true + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" }, "node_modules/nanoid": { "version": "3.3.6", @@ -17132,6 +17851,17 @@ "node-gyp-build": "^4.2.2" } }, + "node_modules/node-abi": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.40.0.tgz", + "integrity": "sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -17205,7 +17935,6 @@ "version": "9.3.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", - "dev": true, "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", @@ -17240,7 +17969,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -17260,7 +17988,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -17271,8 +17998,7 @@ "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { "version": "2.0.10", @@ -17284,7 +18010,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", - "dev": true, "dependencies": { "abbrev": "^1.0.0" }, @@ -17503,7 +18228,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", @@ -17786,7 +18510,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -17832,6 +18555,14 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/opentracing": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/opentracing/-/opentracing-0.14.7.tgz", + "integrity": "sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -17915,7 +18646,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -18147,7 +18877,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -18164,8 +18893,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { "version": "1.9.1", @@ -19170,6 +19898,14 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -19178,14 +19914,12 @@ "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -19198,7 +19932,6 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true, "engines": { "node": ">= 4" } @@ -19216,6 +19949,34 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", + "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", + "hasInstallScript": true, + "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" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -19849,7 +20610,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19863,6 +20623,19 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.1.0.tgz", + "integrity": "sha512-6f86Mh0vWCxqKKatRPwgY6VzYmcVay3WUTIpJ1ILBCNh+dTWabMR1swKGKz3XcEZ5mgjndzRu7fQ+44G2H9Gew==", + "dependencies": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -19882,7 +20655,6 @@ "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, "dependencies": { "is-core-module": "^2.11.0", "path-parse": "^1.0.7", @@ -20010,7 +20782,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -20025,7 +20796,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -20045,7 +20815,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -20054,9 +20823,9 @@ } }, "node_modules/rollup": { - "version": "3.21.8", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.8.tgz", - "integrity": "sha512-SSFV2T2fWtQ/vvBip85u2Nr0GNKireabH9d7nXswBg+XSH+jbVDSYptRAEbCEsquhs503rpPA9POYAp0/Jhasw==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.22.0.tgz", + "integrity": "sha512-imsigcWor5Y/dC0rz2q0bBt9PabcL3TORry2hAa6O6BuMvY71bqHyfReAz5qyAqiQATD1m70qdntqBfBQjVWpQ==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -20314,7 +21083,6 @@ "version": "7.4.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -20329,7 +21097,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -20482,8 +21249,7 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -20544,6 +21310,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -20565,8 +21336,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sigstore": { "version": "1.5.2", @@ -20689,7 +21459,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", @@ -20995,11 +21764,15 @@ "node": ">=10" } }, + "node_modules/string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==" + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -21028,7 +21801,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -21440,7 +22212,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -21570,10 +22341,9 @@ } }, "node_modules/tar": { - "version": "6.1.14", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz", - "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==", - "dev": true, + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -21620,7 +22390,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -21632,7 +22401,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -21644,7 +22412,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -21870,6 +22637,30 @@ "node": ">=0.8" } }, + "node_modules/thriftrw": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/thriftrw/-/thriftrw-3.12.0.tgz", + "integrity": "sha512-4YZvR4DPEI41n4Opwr4jmrLGG4hndxr7387kzRFIIzxHQjarPusH4lGXrugvgb7TtPrfZVTpZCVe44/xUxowEw==", + "dependencies": { + "bufrw": "^1.3.0", + "error": "7.0.2", + "long": "^2.4.0" + }, + "bin": { + "thrift2json": "thrift2json.js" + }, + "engines": { + "node": ">= 0.10.x" + } + }, + "node_modules/thriftrw/node_modules/long": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", + "integrity": "sha512-ijUtjmO/n2A5PaosNG9ZGDsQ3vxJg7ZW8vsY8Kp0f2yIZWhSJvjmegV7t+9RPQKxKrvj8yKGehhS+po14hPLGQ==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -23100,7 +23891,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -23166,7 +23956,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -23190,7 +23979,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -23224,8 +24012,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", @@ -23314,6 +24101,11 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xorshift": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/xorshift/-/xorshift-1.2.0.tgz", + "integrity": "sha512-iYgNnGyeeJ4t6U11NpA/QiKy+PXn5Aa3Azg5qkwIFz1tBLllQrjjsk9yzD7IAK0naNU4JxdeDgqW9ov4u/hc4g==" + }, "node_modules/xss": { "version": "1.0.14", "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.14.tgz", @@ -23346,7 +24138,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -23354,8 +24145,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "1.10.2", @@ -23370,7 +24160,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -23388,7 +24177,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -23397,7 +24185,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", diff --git a/package.json b/package.json index 79c12cba..423cd316 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,14 @@ "@nestjs/cqrs": "^9.0.3", "@nestjs/graphql": "^11.0.4", "@nestjs/mongoose": "^9.2.2", + "@opentelemetry/instrumentation-graphql": "^0.34.1", + "@opentelemetry/instrumentation-mongoose": "^0.32.2", + "@opentelemetry/sdk-node": "^0.39.1", + "@opentelemetry/tracing": "^0.24.0", + "@sentry/angular-ivy": "^7.50.0", + "@sentry/node": "^7.52.1", + "@sentry/opentelemetry-node": "^7.52.1", + "@sentry/profiling-node": "^0.3.0", "@nestjs/platform-express": "9.4.0", "angular-oauth2-oidc": "^15.0.1", "axios": "^1.0.0", diff --git a/tsconfig.base.json b/tsconfig.base.json index 9c14cd01..2f1fb89b 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,10 +16,12 @@ "baseUrl": ".", "paths": { "@kordis/api/auth": ["libs/api/auth/src/index.ts"], + "@kordis/api/observability": ["libs/api/observability/src/index.ts"], "@kordis/api/shared": ["libs/api/shared/src/index.ts"], "@kordis/api/test-helpers": ["libs/api/test-helpers/src/index.ts"], "@kordis/shared/auth": ["libs/shared/auth/src/index.ts"], - "@kordis/spa/auth": ["libs/spa/auth/src/index.ts"] + "@kordis/spa/auth": ["libs/spa/auth/src/index.ts"], + "@kordis/spa/observability": ["libs/spa/observability/src/index.ts"] } }, "exclude": ["node_modules", "tmp"]