diff --git a/client/browser/src/browser-extension/scripts/backgroundPage.main.ts b/client/browser/src/browser-extension/scripts/backgroundPage.main.ts index c37a379b52e..7f7fc83296d 100644 --- a/client/browser/src/browser-extension/scripts/backgroundPage.main.ts +++ b/client/browser/src/browser-extension/scripts/backgroundPage.main.ts @@ -5,7 +5,7 @@ import '../../config/background.entry' import '../../shared/polyfills' import type { Endpoint } from 'comlink' -import { combineLatest, merge, type Observable, of, Subject, Subscription, timer } from 'rxjs' +import { combineLatest, merge, type Observable, of, Subject, Subscription, timer, lastValueFrom } from 'rxjs' import { bufferCount, filter, @@ -238,7 +238,7 @@ async function main(): Promise { variables: V sourcegraphURL?: string }): Promise> { - return requestGraphQL({ request, variables, sourcegraphURL }).toPromise() + return lastValueFrom(requestGraphQL({ request, variables, sourcegraphURL })) }, async notifyRepoSyncError({ sourcegraphURL, hasRepoSyncError }, sender: browser.runtime.MessageSender) { diff --git a/client/browser/src/end-to-end/github.test.ts b/client/browser/src/end-to-end/github.test.ts index b09adac2e3f..5814cc7a0aa 100644 --- a/client/browser/src/end-to-end/github.test.ts +++ b/client/browser/src/end-to-end/github.test.ts @@ -3,8 +3,8 @@ import assert from 'assert' import { startCase } from 'lodash' import { describe, it } from 'mocha' import type { Target, Page } from 'puppeteer' -import { fromEvent } from 'rxjs' -import { first, filter, timeout, mergeMap } from 'rxjs/operators' +import { firstValueFrom, fromEvent } from 'rxjs' +import { filter, timeout, mergeMap } from 'rxjs/operators' import { isDefined } from '@sourcegraph/common' import { getConfig } from '@sourcegraph/shared/src/testing/config' @@ -141,14 +141,13 @@ describe('Sourcegraph browser extension on github.com', function () { let page: Page = driver.page if (new URL(goToDefinitionURL).hostname !== 'github.com') { ;[page] = await Promise.all([ - fromEvent(driver.browser, 'targetcreated') - .pipe( + firstValueFrom( + fromEvent(driver.browser, 'targetcreated').pipe( timeout(5000), mergeMap(target => target.page()), - filter(isDefined), - first() + filter(isDefined) ) - .toPromise(), + ), driver.page.click('.test-tooltip-go-to-definition'), ]) } else { diff --git a/client/browser/src/shared/cli/search.ts b/client/browser/src/shared/cli/search.ts index 065f5497d5d..ea1a5bad330 100644 --- a/client/browser/src/shared/cli/search.ts +++ b/client/browser/src/shared/cli/search.ts @@ -1,5 +1,4 @@ -import { from } from 'rxjs' -import { take } from 'rxjs/operators' +import { firstValueFrom } from 'rxjs' import { type ErrorLike, isErrorLike, isDefined, isNot } from '@sourcegraph/common' import type { Settings } from '@sourcegraph/shared/src/settings/settings' @@ -22,7 +21,7 @@ export class SearchCommand { private prev: { query: string; suggestions: browser.omnibox.SuggestResult[] } = { query: '', suggestions: [] } public getSuggestions = async (query: string): Promise => { - const sourcegraphURL = await observeSourcegraphURL(IS_EXTENSION).pipe(take(1)).toPromise() + const sourcegraphURL = await firstValueFrom(observeSourcegraphURL(IS_EXTENSION)) return new Promise(resolve => { if (this.prev.query === query) { resolve(this.prev.suggestions) @@ -54,7 +53,7 @@ export class SearchCommand { disposition?: 'newForegroundTab' | 'newBackgroundTab' | 'currentTab', currentTabId?: number ): Promise => { - const sourcegraphURL = await observeSourcegraphURL(IS_EXTENSION).pipe(take(1)).toPromise() + const sourcegraphURL = await firstValueFrom(observeSourcegraphURL(IS_EXTENSION)) const [patternType, caseSensitive] = await this.getDefaultSearchSettings(sourcegraphURL) @@ -121,7 +120,7 @@ export class SearchCommand { ) await platformContext.refreshSettings() - const settings = (await from(platformContext.settings).pipe(take(1)).toPromise()).final + const settings = (await firstValueFrom(platformContext.settings)).final if (isDefined(settings) && isNot(isErrorLike)(settings)) { this.defaultPatternType = diff --git a/client/browser/src/shared/code-hosts/gitlab/codeHost.ts b/client/browser/src/shared/code-hosts/gitlab/codeHost.ts index 4f0d70f314a..d044435be1d 100644 --- a/client/browser/src/shared/code-hosts/gitlab/codeHost.ts +++ b/client/browser/src/shared/code-hosts/gitlab/codeHost.ts @@ -1,6 +1,6 @@ import * as Sentry from '@sentry/browser' import classNames from 'classnames' -import { fromEvent } from 'rxjs' +import { fromEvent, lastValueFrom } from 'rxjs' import { filter, map, mapTo, tap } from 'rxjs/operators' import type { Omit } from 'utility-types' @@ -286,25 +286,25 @@ export const gitlabCodeHost = subtypeOf()({ ), prepareCodeHost: async requestGraphQL => - requestGraphQL({ - request: gql` - query ResolveRepoName($cloneURL: String!) { - repository(cloneURL: $cloneURL) { - name + lastValueFrom( + requestGraphQL({ + request: gql` + query ResolveRepoName($cloneURL: String!) { + repository(cloneURL: $cloneURL) { + name + } } - } - `, - variables: { - cloneURL: getGitlabRepoURL(), - }, - mightContainPrivateInfo: true, - }) - .pipe( + `, + variables: { + cloneURL: getGitlabRepoURL(), + }, + mightContainPrivateInfo: true, + }).pipe( map(dataOrThrowErrors), tap(({ repository }) => { repoNameOnSourcegraph.next(repository?.name ?? '') }), mapTo(true) ) - .toPromise(), + ), }) diff --git a/client/browser/src/shared/code-hosts/phabricator/backend.tsx b/client/browser/src/shared/code-hosts/phabricator/backend.tsx index 70a43f30440..140b9df957d 100644 --- a/client/browser/src/shared/code-hosts/phabricator/backend.tsx +++ b/client/browser/src/shared/code-hosts/phabricator/backend.tsx @@ -1,4 +1,4 @@ -import { from, type Observable, of, throwError } from 'rxjs' +import { from, type Observable, of, throwError, lastValueFrom } from 'rxjs' import { fromFetch } from 'rxjs/fetch' import { map, mapTo, switchMap, catchError } from 'rxjs/operators' @@ -275,9 +275,9 @@ export function getRepoDetailsFromCallsign( * case it fails we query the conduit API. */ export function getSourcegraphURLFromConduit(): Promise { - return queryConduitHelper<{ url: string }>('/api/sourcegraph.configuration', {}) - .pipe(map(({ url }) => url)) - .toPromise() + return lastValueFrom( + queryConduitHelper<{ url: string }>('/api/sourcegraph.configuration', {}).pipe(map(({ url }) => url)) + ) } const getRepoDetailsFromRepoPHID = memoizeObservable( diff --git a/client/browser/src/shared/code-hosts/phabricator/fileInfo.test.ts b/client/browser/src/shared/code-hosts/phabricator/fileInfo.test.ts index 0ce991d0f77..ae5b1ee1910 100644 --- a/client/browser/src/shared/code-hosts/phabricator/fileInfo.test.ts +++ b/client/browser/src/shared/code-hosts/phabricator/fileInfo.test.ts @@ -1,5 +1,5 @@ import { readFile } from 'mz/fs' -import { type Observable, throwError, of } from 'rxjs' +import { type Observable, throwError, of, lastValueFrom } from 'rxjs' import { beforeEach, describe, expect, test } from 'vitest' import { resetAllMemoizationCaches } from '@sourcegraph/common' @@ -145,15 +145,17 @@ const resolveFileInfoFromFixture = async ( if (!codeView) { throw new Error(`Code view matching selector ${codeViewSelector} not found`) } - return resolver( - codeView as HTMLElement, - mockRequestGraphQL({ - ...DEFAULT_GRAPHQL_RESPONSES, - ...graphQLResponseMap, - }), - mockQueryConduit(conduitResponseMap), - new URL(url) - ).toPromise() + return lastValueFrom( + resolver( + codeView as HTMLElement, + mockRequestGraphQL({ + ...DEFAULT_GRAPHQL_RESPONSES, + ...graphQLResponseMap, + }), + mockQueryConduit(conduitResponseMap), + new URL(url) + ) + ) } describe('Phabricator file info', () => { diff --git a/client/browser/src/shared/code-hosts/shared/codeHost.tsx b/client/browser/src/shared/code-hosts/shared/codeHost.tsx index 32f2a42198c..75809864a92 100644 --- a/client/browser/src/shared/code-hosts/shared/codeHost.tsx +++ b/client/browser/src/shared/code-hosts/shared/codeHost.tsx @@ -18,6 +18,7 @@ import { concat, BehaviorSubject, fromEvent, + lastValueFrom, } from 'rxjs' import { catchError, @@ -643,10 +644,12 @@ const isSafeToContinueCodeIntel = async ({ rawRepoName = context.rawRepoName - const isRepoCloned = await resolvePrivateRepo({ - rawRepoName, - requestGraphQL, - }).toPromise() + const isRepoCloned = await lastValueFrom( + resolvePrivateRepo({ + rawRepoName, + requestGraphQL, + }) + ) return isRepoCloned } catch (error) { diff --git a/client/browser/src/shared/code-hosts/shared/codeViews.test.ts b/client/browser/src/shared/code-hosts/shared/codeViews.test.ts index a66b8dfd844..7e5249e1df4 100644 --- a/client/browser/src/shared/code-hosts/shared/codeViews.test.ts +++ b/client/browser/src/shared/code-hosts/shared/codeViews.test.ts @@ -1,4 +1,4 @@ -import { of } from 'rxjs' +import { lastValueFrom, of } from 'rxjs' import { toArray } from 'rxjs/operators' import * as sinon from 'sinon' import type { Omit } from 'utility-types' @@ -33,14 +33,14 @@ describe('codeViews', () => { element.className = 'test-code-view' document.body.append(element) const selector = '.test-code-view' - const detected = await of([{ addedNodes: [document.body], removedNodes: [] }]) - .pipe( + const detected = await lastValueFrom( + of([{ addedNodes: [document.body], removedNodes: [] }]).pipe( trackCodeViews({ codeViewResolvers: [toCodeViewResolver(selector, codeViewSpec)], }), toArray() ) - .toPromise() + ) expect(detected.map(({ subscriptions, ...rest }) => rest)).toEqual([{ ...codeViewSpec, element }]) }) it('should detect added code views from resolver', async () => { @@ -49,14 +49,14 @@ describe('codeViews', () => { document.body.append(element) const selector = '.test-code-view' const resolveView = sinon.spy((element: HTMLElement) => ({ element, ...codeViewSpec })) - const detected = await of([{ addedNodes: [document.body], removedNodes: [] }]) - .pipe( + const detected = await lastValueFrom( + of([{ addedNodes: [document.body], removedNodes: [] }]).pipe( trackCodeViews({ codeViewResolvers: [{ selector, resolveView }], }), toArray() ) - .toPromise() + ) expect(detected.map(({ subscriptions, ...rest }) => rest)).toEqual([{ ...codeViewSpec, element }]) sinon.assert.calledOnce(resolveView) sinon.assert.calledWith(resolveView, element) diff --git a/client/browser/src/shared/code-hosts/shared/views.test.ts b/client/browser/src/shared/code-hosts/shared/views.test.ts index afa64df04e6..2bd7bf32b15 100644 --- a/client/browser/src/shared/code-hosts/shared/views.test.ts +++ b/client/browser/src/shared/code-hosts/shared/views.test.ts @@ -1,5 +1,5 @@ import { noop } from 'lodash' -import { from, type Observable, of, Subject, Subscription, NEVER } from 'rxjs' +import { from, type Observable, of, Subject, Subscription, NEVER, lastValueFrom } from 'rxjs' import { bufferCount, map, switchMap, toArray } from 'rxjs/operators' import * as sinon from 'sinon' import { afterAll, beforeEach, describe, expect, test } from 'vitest' @@ -40,9 +40,9 @@ describe('trackViews()', () => { test('detects all views on the page', async () => { const mutations: Observable = of([{ addedNodes: [document.body], removedNodes: [] }]) - const views = await mutations - .pipe(trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), toArray()) - .toPromise() + const views = await lastValueFrom( + mutations.pipe(trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), toArray()) + ) expect(views.map(({ element }) => element.id)).toEqual(['view1', 'view2', 'view3']) }) @@ -51,13 +51,13 @@ describe('trackViews()', () => { { addedNodes: [document.querySelector('#view1')!], removedNodes: [] }, ]) expect( - await mutations - .pipe( + await lastValueFrom( + mutations.pipe( trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), map(({ element }) => element.id), toArray() ) - .toPromise() + ) ).toEqual(['view1']) }) @@ -66,13 +66,13 @@ describe('trackViews()', () => { { addedNodes: [document.querySelector('#view1')!], removedNodes: [] }, ]) expect( - await mutations - .pipe( + await lastValueFrom( + mutations.pipe( trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), map(({ element }) => element.id), toArray() ) - .toPromise() + ) ).toEqual(['view1']) }) @@ -82,8 +82,8 @@ describe('trackViews()', () => { selectorTarget.className = 'selector-target' document.querySelector('#view1')!.append(selectorTarget) expect( - await mutations - .pipe( + await lastValueFrom( + mutations.pipe( trackViews([ { selector: '.selector-target', @@ -93,15 +93,15 @@ describe('trackViews()', () => { map(({ element }) => element.id), toArray() ) - .toPromise() + ) ).toEqual(['view1']) }) test("doesn't emit duplicate views", async () => { const mutations: Observable = of([{ addedNodes: [document.body], removedNodes: [] }]) expect( - await mutations - .pipe( + await lastValueFrom( + mutations.pipe( trackViews([ { selector: '.view', @@ -113,7 +113,7 @@ describe('trackViews()', () => { map(({ element }) => element.id), toArray() ) - .toPromise() + ) ).toEqual(['view1']) }) @@ -190,20 +190,20 @@ describe('trackViews()', () => { [{ addedNodes: [], removedNodes: [document.querySelector('#view1')!] }], [{ addedNodes: [], removedNodes: [document.querySelector('#view3')!] }], ]) - await mutations - .pipe( + await lastValueFrom( + mutations.pipe( trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), bufferCount(3), switchMap(async ([view1, view2, view3]) => { const v2Removed = sinon.spy(() => undefined) view2.subscriptions.add(v2Removed) - const v1Removed = new Promise(resolve => view1.subscriptions.add(resolve)) - const v3Removed = new Promise(resolve => view3.subscriptions.add(resolve)) + const v1Removed = new Promise(resolve => view1.subscriptions.add(resolve)) + const v3Removed = new Promise(resolve => view3.subscriptions.add(resolve)) await Promise.all([v1Removed, v3Removed]) sinon.assert.notCalled(v2Removed) }) ) - .toPromise() + ) }) test('removes all nested views', async () => { @@ -211,15 +211,15 @@ describe('trackViews()', () => { [{ addedNodes: [document.body], removedNodes: [] }], [{ addedNodes: [], removedNodes: [document.querySelector('#parent')!] }], ]) - await mutations - .pipe( + await lastValueFrom( + mutations.pipe( trackViews([{ selector: '.view', resolveView: element => ({ element }) }]), bufferCount(3), switchMap(views => - Promise.all(views.map(view => new Promise(resolve => view.subscriptions.add(resolve)))) + Promise.all(views.map(view => new Promise(resolve => view.subscriptions.add(resolve)))) ) ) - .toPromise() + ) }) test('removes a view without depending on its resolver', async () => { @@ -259,7 +259,7 @@ describe('trackViews()', () => { expect(resolver.resolveView(testElement)).toBe(null) // Verify that the code view still gets removed. - const unsubscribed = new Promise(resolve => view.subscriptions.add(resolve)) + const unsubscribed = new Promise(resolve => view.subscriptions.add(resolve)) mutations.next([{ addedNodes: [], removedNodes: [testElement] }]) await unsubscribed }) diff --git a/client/browser/src/shared/extensionHostWorker.ts b/client/browser/src/shared/extensionHostWorker.ts index 1c6862f9bad..35ccce4478b 100644 --- a/client/browser/src/shared/extensionHostWorker.ts +++ b/client/browser/src/shared/extensionHostWorker.ts @@ -1,7 +1,6 @@ import '@sourcegraph/shared/src/polyfills' -import { fromEvent } from 'rxjs' -import { take } from 'rxjs/operators' +import { firstValueFrom, fromEvent } from 'rxjs' import { hasProperty, logger } from '@sourcegraph/common' import { startExtensionHost } from '@sourcegraph/shared/src/api/extension/extensionHost' @@ -24,7 +23,7 @@ const isInitMessage = (value: unknown): value is InitMessage => */ async function extensionHostMain(): Promise { try { - const event = await fromEvent(self, 'message').pipe(take(1)).toPromise() + const event = await firstValueFrom(fromEvent(self, 'message')) if (!isInitMessage(event.data)) { throw new Error('First message event in extension host worker was not a well-formed InitMessage') } diff --git a/client/browser/src/shared/platform/context.ts b/client/browser/src/shared/platform/context.ts index 8a526d2b820..68bc573bcfa 100644 --- a/client/browser/src/shared/platform/context.ts +++ b/client/browser/src/shared/platform/context.ts @@ -1,4 +1,4 @@ -import { combineLatest, ReplaySubject } from 'rxjs' +import { combineLatest, lastValueFrom, ReplaySubject } from 'rxjs' import { map } from 'rxjs/operators' import { asError } from '@sourcegraph/common' @@ -77,7 +77,7 @@ export function createPlatformContext( ), refreshSettings: async () => { try { - const settings = await fetchViewerSettings(requestGraphQL).toPromise() + const settings = await lastValueFrom(fetchViewerSettings(requestGraphQL)) updatedViewerSettings.next(settings) } catch (error) { if (isHTTPAuthError(error)) { diff --git a/client/build-config/src/esbuild/packageResolutionPlugin.ts b/client/build-config/src/esbuild/packageResolutionPlugin.ts index 77041d4f03a..68c1207702e 100644 --- a/client/build-config/src/esbuild/packageResolutionPlugin.ts +++ b/client/build-config/src/esbuild/packageResolutionPlugin.ts @@ -51,14 +51,3 @@ export const packageResolutionPlugin = (resolutions: Resolutions): esbuild.Plugi build.onLoad({ filter: new RegExp(''), namespace: 'devnull' }, () => ({ contents: '' })) }, }) - -export const RXJS_RESOLUTIONS: Resolutions = { - // Needed because imports of rxjs/internal/... actually import a different variant of - // rxjs in the same package, which leads to observables from combineLatestOrDefault (and - // other places that use rxjs/internal/...) not being cross-compatible. See - // https://stackoverflow.com/questions/53758889/rxjs-subscribeto-js-observable-check-works-in-chrome-but-fails-in-chrome-incogn. - 'rxjs/internal/OuterSubscriber': require.resolve('rxjs/_esm5/internal/OuterSubscriber'), - 'rxjs/internal/util/subscribeToResult': require.resolve('rxjs/_esm5/internal/util/subscribeToResult'), - 'rxjs/internal/util/subscribeToArray': require.resolve('rxjs/_esm5/internal/util/subscribeToArray'), - 'rxjs/internal/Observable': require.resolve('rxjs/_esm5/internal/Observable'), -} diff --git a/client/build-config/src/esbuild/workerPlugin.ts b/client/build-config/src/esbuild/workerPlugin.ts index 99cb4e64a01..969ab813f9e 100644 --- a/client/build-config/src/esbuild/workerPlugin.ts +++ b/client/build-config/src/esbuild/workerPlugin.ts @@ -1,6 +1,6 @@ import type * as esbuild from 'esbuild' -import { packageResolutionPlugin, RXJS_RESOLUTIONS } from './packageResolutionPlugin' +import { packageResolutionPlugin } from './packageResolutionPlugin' /** * Starts a new esbuild build to create a bundle for a Web Worker. @@ -21,7 +21,6 @@ async function buildWorker( plugins: [ packageResolutionPlugin({ path: require.resolve('path-browserify'), - ...RXJS_RESOLUTIONS, }), ], // Use the minify option as an indicator for running in dev mode. diff --git a/client/codeintellify/src/helpers.ts b/client/codeintellify/src/helpers.ts index 03b2dfd7edd..d201f213ccf 100644 --- a/client/codeintellify/src/helpers.ts +++ b/client/codeintellify/src/helpers.ts @@ -1,5 +1,5 @@ import { isObject } from 'lodash' -import { type Subscribable, type Observable, from } from 'rxjs' +import { type Observable, from } from 'rxjs' import { map } from 'rxjs/operators' import { isDefined } from '@sourcegraph/common' @@ -17,9 +17,9 @@ const isPromiseLike = (value: unknown): value is PromiseLike => * single result, to the same type. */ export const toMaybeLoadingProviderResult = ( - value: Subscribable> | PromiseLike + value: Observable> | PromiseLike ): Observable> => - isPromiseLike(value) ? from(value).pipe(map(result => ({ isLoading: false, result }))) : from(value) + isPromiseLike(value) ? from(value).pipe(map(result => ({ isLoading: false, result }))) : value /** * Returns a function that returns `true` if the given `key` of the object is not `null` or `undefined`. diff --git a/client/codeintellify/src/hoverifier.test.ts b/client/codeintellify/src/hoverifier.test.ts index 938a839e5d1..57e7fa507f1 100644 --- a/client/codeintellify/src/hoverifier.test.ts +++ b/client/codeintellify/src/hoverifier.test.ts @@ -2,7 +2,7 @@ import { isEqual } from 'lodash' import { EMPTY, NEVER, of, Subject, Subscription } from 'rxjs' import { delay, distinctUntilChanged, filter, first, map, takeWhile } from 'rxjs/operators' import { TestScheduler } from 'rxjs/testing' -import { afterAll, afterEach, beforeAll, describe, it, expect } from 'vitest' +import { afterEach, beforeEach, describe, it, expect } from 'vitest' import { isDefined } from '@sourcegraph/common' import type { Range } from '@sourcegraph/extension-api-types' @@ -27,17 +27,18 @@ import { import { dispatchMouseEventAtPositionImpure } from './testutils/mouse' describe('Hoverifier', () => { - const dom = new DOM() - afterAll(dom.cleanup) - + let dom: DOM let testcases: CodeViewProps[] = [] - beforeAll(() => { + + beforeEach(() => { + dom = new DOM() testcases = dom.createCodeViews() }) let subscriptions = new Subscription() afterEach(() => { + dom.cleanup() subscriptions.unsubscribe() subscriptions = new Subscription() }) diff --git a/client/codeintellify/src/hoverifier.ts b/client/codeintellify/src/hoverifier.ts index 2f9c6a3aaae..2a2be5daa22 100644 --- a/client/codeintellify/src/hoverifier.ts +++ b/client/codeintellify/src/hoverifier.ts @@ -11,11 +11,10 @@ import { type Observable, of, Subject, - type Subscribable, - type SubscribableOrPromise, Subscription, race, type MonoTypeOperatorFunction, + ObservableInput, } from 'rxjs' import { catchError, @@ -75,7 +74,7 @@ export interface HoverifierOptions { /** * Emit the HoverOverlay element on this after it was rerendered when its content changed and it needs to be repositioned. */ - hoverOverlayRerenders: Subscribable<{ + hoverOverlayRerenders: ObservableInput<{ /** * The HoverOverlay element */ @@ -89,13 +88,13 @@ export interface HoverifierOptions { pinOptions?: { /** Emit on this Observable to pin the popover. */ - pins: Subscribable + pins: ObservableInput /** * Emit on this Observable when the close button in the HoverOverlay was clicked */ - closeButtonClicks: Subscribable + closeButtonClicks: ObservableInput } - hoverOverlayElements: Subscribable + hoverOverlayElements: ObservableInput /** * Called to get the data to display in the hover. @@ -199,7 +198,7 @@ export interface AdjustPositionProps { * * @template C Extra context for the hovered token. */ -export type PositionAdjuster = (props: AdjustPositionProps) => SubscribableOrPromise +export type PositionAdjuster = (props: AdjustPositionProps) => ObservableInput /** * HoverifyOptions that need to be included internally with every event @@ -235,13 +234,13 @@ export interface EventOptions { */ export interface HoverifyOptions extends Pick, Exclude, 'codeViewId'>> { - positionEvents: Subscribable + positionEvents: ObservableInput /** * Emit on this Observable to trigger the overlay on a position in this code view. * This Observable is intended to be used to trigger a Hover after a URL change with a position. */ - positionJumps?: Subscribable + positionJumps?: ObservableInput } /** @@ -388,7 +387,7 @@ export const MOUSEOVER_DELAY = 50 */ export type HoverProvider = ( position: HoveredToken & C -) => Subscribable> | PromiseLike<(HoverAttachment & D) | null> +) => Observable> | PromiseLike<(HoverAttachment & D) | null> /** * Function that returns a Subscribable or PromiseLike of the ranges to be highlighted in the document. @@ -399,13 +398,13 @@ export type HoverProvider = ( */ export type DocumentHighlightProvider = ( position: HoveredToken & C -) => Subscribable | PromiseLike +) => ObservableInput /** * @template C Extra context for the hovered token. * @template A The type of an action. */ -export type ActionsProvider = (position: HoveredToken & C) => SubscribableOrPromise +export type ActionsProvider = (position: HoveredToken & C) => ObservableInput /** * Function responsible for resolving the position of a hovered token @@ -1050,7 +1049,9 @@ export function createHoverifier({ } // Pin on request. - subscription.add(pinOptions?.pins.subscribe(() => container.update({ pinned: true }))) + if (pinOptions) { + subscription.add(from(pinOptions.pins).subscribe(() => container.update({ pinned: true }))) + } // Unpin on close, ESC, or click. subscription.add( diff --git a/client/codeintellify/src/positions.ts b/client/codeintellify/src/positions.ts index 4dd53a5cf4d..5f071787fc0 100644 --- a/client/codeintellify/src/positions.ts +++ b/client/codeintellify/src/positions.ts @@ -1,4 +1,4 @@ -import { from, fromEvent, merge, type Observable, type Subscribable } from 'rxjs' +import { from, fromEvent, merge, type Observable } from 'rxjs' import { filter, map, switchMap, tap } from 'rxjs/operators' import { convertCodeElementIdempotent, type DOMFunctions, type HoveredToken, locateTarget } from './tokenPosition' @@ -28,7 +28,7 @@ export interface PositionEvent { export const findPositionsFromEvents = ({ domFunctions, tokenize = true }: { domFunctions: DOMFunctions; tokenize?: boolean }) => - (elements: Subscribable): Observable => + (elements: Observable): Observable => merge( from(elements).pipe( switchMap(element => diff --git a/client/codeintellify/src/testutils/fixtures.ts b/client/codeintellify/src/testutils/fixtures.ts index 7cb08ad455c..6b24bc8a76c 100644 --- a/client/codeintellify/src/testutils/fixtures.ts +++ b/client/codeintellify/src/testutils/fixtures.ts @@ -2,7 +2,6 @@ import { of } from 'rxjs' import { delay } from 'rxjs/operators' import type { ActionsProvider, HoverProvider, DocumentHighlightProvider } from '../hoverifier' -import type { MaybeLoadingResult } from '../loading' import type { HoverAttachment, DocumentHighlight } from '../types' /** @@ -41,10 +40,7 @@ export function createStubHoverProvider( hover: Partial = {}, delayTime?: number ): HoverProvider<{}, {}> { - return () => - of>({ isLoading: false, result: createHoverAttachment(hover) }).pipe( - delay(delayTime ?? 0) - ) + return () => of({ isLoading: false, result: createHoverAttachment(hover) }).pipe(delay(delayTime ?? 0)) } /** @@ -57,7 +53,7 @@ export function createStubDocumentHighlightProvider( documentHighlights: Partial[] = [], delayTime?: number ): DocumentHighlightProvider<{}> { - return () => of(documentHighlights.map(createDocumentHighlight)).pipe(delay(delayTime ?? 0)) + return () => of(documentHighlights.map(createDocumentHighlight)).pipe(delay(delayTime ?? 0)) } /** diff --git a/client/common/BUILD.bazel b/client/common/BUILD.bazel index 903146a63d2..a471484396e 100644 --- a/client/common/BUILD.bazel +++ b/client/common/BUILD.bazel @@ -46,6 +46,7 @@ ts_project( "src/util/path.ts", "src/util/rxjs/asObservable.ts", "src/util/rxjs/combineLatestOrDefault.ts", + "src/util/rxjs/fromSubscribable.ts", "src/util/rxjs/index.ts", "src/util/rxjs/memoizeObservable.ts", "src/util/rxjs/repeatUntil.ts", @@ -71,6 +72,7 @@ ts_project( "//:node_modules/marked", "//:node_modules/react-router-dom", "//:node_modules/rxjs", + "//:node_modules/sourcegraph", "//:node_modules/utility-types", ], ) diff --git a/client/common/src/util/rxjs/combineLatestOrDefault.ts b/client/common/src/util/rxjs/combineLatestOrDefault.ts index 389182d071b..5ed68f70f74 100644 --- a/client/common/src/util/rxjs/combineLatestOrDefault.ts +++ b/client/common/src/util/rxjs/combineLatestOrDefault.ts @@ -1,18 +1,4 @@ -/* eslint rxjs/no-internal: warn */ -import { - asapScheduler, - type ObservableInput, - of, - type Operator, - type PartialObserver, - type Subscriber, - type TeardownLogic, - zip, -} from 'rxjs' -import { Observable } from 'rxjs/internal/Observable' -import { OuterSubscriber } from 'rxjs/internal/OuterSubscriber' -import { subscribeToArray } from 'rxjs/internal/util/subscribeToArray' -import { subscribeToResult } from 'rxjs/internal/util/subscribeToResult' +import { asapScheduler, type ObservableInput, Observable, of, zip, from, Subscription } from 'rxjs' /** * Like {@link combineLatest}, except that it does not wait for all Observables to emit before emitting an initial @@ -51,74 +37,68 @@ export function combineLatestOrDefault(observables: ObservableInput[], def return zip(...observables) } default: { - return new Observable(subscribeToArray(observables)).lift(new CombineLatestOperator(defaultValue)) - } - } -} + return new Observable(subscriber => { + // The array of the most recent values from each input Observable. + // If a source Observable has not yet emitted a value, it will be represented by the + // defaultValue (if provided) or not at all (if not provided). + const values: T[] = defaultValue !== undefined ? observables.map(() => defaultValue) : [] -class CombineLatestOperator implements Operator { - constructor(private defaultValue?: T) {} + // Whether the emission of the values array has been scheduled + let scheduled = false + let scheduledWork: Subscription | undefined + // The number of source Observables that have not yet completed + // (so that we know when to complete the output Observable) + let activeObservables = observables.length - public call(subscriber: Subscriber, source: any): TeardownLogic { - return source.subscribe(new CombineLatestSubscriber(subscriber, this.defaultValue)) - } -} + // When everything is done, clean up the values array + subscriber.add(() => { + values.length = 0 + }) -class CombineLatestSubscriber extends OuterSubscriber { - private activeObservables = 0 - private values: any[] = [] - private observables: Observable[] = [] - private scheduled = false - - constructor(observer: PartialObserver, private defaultValue?: T) { - super(observer) - } - - protected _next(observable: any): void { - if (this.defaultValue !== undefined) { - this.values.push(this.defaultValue) - } - this.observables.push(observable) - } - - protected _complete(): void { - this.activeObservables = this.observables.length - for (let index = 0; index < this.observables.length; index++) { - this.add(subscribeToResult(this, this.observables[index], this.observables[index], index)) - } - } - - public notifyComplete(): void { - this.activeObservables-- - if (this.activeObservables === 0 && this.destination.complete) { - this.destination.complete() - } - } - - public notifyNext(_outerValue: T, innerValue: T[], outerIndex: number): void { - const values = this.values - values[outerIndex] = innerValue - - if (this.activeObservables === 1) { - // Only 1 observable is active, so no need to buffer. - // - // This makes it possible to use RxJS's `of` in tests without specifying an explicit scheduler. - if (this.destination.next) { - this.destination.next(this.values.slice()) - } - return - } - - // Buffer all next values that are emitted at the same time into one emission. - // - // This makes tests (using expectObservable) easier to write. - if (!this.scheduled) { - this.scheduled = true - asapScheduler.schedule(() => { - if (this.scheduled && this.destination.next) { - this.destination.next(this.values.slice()) + // Subscribe to each source Observable. The index of the source Observable is used to + // keep track of the most recent value from that Observable in the values array. + for (let index = 0; index < observables.length; index++) { + subscriber.add( + from(observables[index]).subscribe({ + next: value => { + values[index] = value + if (activeObservables === 1) { + // If only one source Observable is active, emit the values array immediately + // Abort any scheduled emission + scheduledWork?.unsubscribe() + scheduled = false + subscriber.next(values.slice()) + } else if (!scheduled) { + scheduled = true + // Use asapScheduler to emit the values array, so that all + // next values that are emitted at the same time are emitted together. + // This makes tests (using expectObservable) easier to write. + scheduledWork = asapScheduler.schedule(() => { + if (!subscriber.closed) { + subscriber.next(values.slice()) + scheduled = false + if (activeObservables === 0) { + subscriber.complete() + } + } + }) + } + }, + error: error => subscriber.error(error), + complete: () => { + activeObservables-- + if (activeObservables === 0 && !scheduled) { + subscriber.complete() + } + }, + }) + ) + } + + // When everything is done, clean up the values array + return () => { + values.length = 0 } - this.scheduled = false }) } } diff --git a/client/common/src/util/rxjs/fromSubscribable.ts b/client/common/src/util/rxjs/fromSubscribable.ts new file mode 100644 index 00000000000..4e60bcb5c20 --- /dev/null +++ b/client/common/src/util/rxjs/fromSubscribable.ts @@ -0,0 +1,14 @@ +import { Observable, isObservable } from 'rxjs' +import { type Subscribable } from 'sourcegraph' + +/** + * Converts a Sourcegraph {@link Subscribable} to an {@link Observable}. + */ +export function fromSubscribable(value: Subscribable): Observable { + if (isObservable(value)) { + // type casting should be fine since we already know that + // value is at least a Subscribable + return value as Observable + } + return new Observable(subscriber => value.subscribe(subscriber)) +} diff --git a/client/common/src/util/rxjs/index.ts b/client/common/src/util/rxjs/index.ts index 89f4fcd4754..e33b08dc252 100644 --- a/client/common/src/util/rxjs/index.ts +++ b/client/common/src/util/rxjs/index.ts @@ -2,3 +2,4 @@ export * from './asObservable' export * from './combineLatestOrDefault' export * from './memoizeObservable' export * from './repeatUntil' +export * from './fromSubscribable' diff --git a/client/jetbrains/webview/src/sourcegraph-api-access/api-gateway.ts b/client/jetbrains/webview/src/sourcegraph-api-access/api-gateway.ts index 5c259992448..e6ffd245a0a 100644 --- a/client/jetbrains/webview/src/sourcegraph-api-access/api-gateway.ts +++ b/client/jetbrains/webview/src/sourcegraph-api-access/api-gateway.ts @@ -1,3 +1,5 @@ +import { lastValueFrom } from 'rxjs' + import { gql, requestGraphQLCommon } from '@sourcegraph/http-client' import type { AuthenticatedUser } from '@sourcegraph/shared/src/auth' import type { CurrentAuthStateResult, CurrentAuthStateVariables } from '@sourcegraph/shared/src/graphql-operations' @@ -57,18 +59,20 @@ export async function getSiteVersionAndAuthenticatedUser( return { site: null, currentUser: null } } - const result = await requestGraphQLCommon({ - request: siteVersionAndUserQuery, - variables: {}, - baseUrl: instanceURL, - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'X-Sourcegraph-Should-Trace': new URLSearchParams(window.location.search).get('trace') || 'false', - ...(accessToken && { Authorization: `token ${accessToken}` }), - ...customRequestHeaders, - }, - }).toPromise() + const result = await lastValueFrom( + requestGraphQLCommon({ + request: siteVersionAndUserQuery, + variables: {}, + baseUrl: instanceURL, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'X-Sourcegraph-Should-Trace': new URLSearchParams(window.location.search).get('trace') || 'false', + ...(accessToken && { Authorization: `token ${accessToken}` }), + ...customRequestHeaders, + }, + }) + ) return result.data ?? { site: null, currentUser: null } } diff --git a/client/shared/src/actions/ActionsNavItems.tsx b/client/shared/src/actions/ActionsNavItems.tsx index b2873749903..f1a644e07a8 100644 --- a/client/shared/src/actions/ActionsNavItems.tsx +++ b/client/shared/src/actions/ActionsNavItems.tsx @@ -82,12 +82,12 @@ export interface ActionsNavItemsProps export const ActionsNavItems: React.FunctionComponent> = props => { const { scope, extraContext, extensionsController, menu, wrapInList, transformContributions = identity } = props - const scopeChanges = useMemo(() => new ReplaySubject(1), []) + const scopeChanges = useMemo(() => new ReplaySubject(1), []) useDeepCompareEffectNoCheck(() => { scopeChanges.next(scope) }, [scope]) - const extraContextChanges = useMemo(() => new ReplaySubject>(1), []) + const extraContextChanges = useMemo(() => new ReplaySubject | undefined>(1), []) useDeepCompareEffectNoCheck(() => { extraContextChanges.next(extraContext) }, [extraContext]) diff --git a/client/shared/src/api/client/api/common.ts b/client/shared/src/api/client/api/common.ts index 2564dfe516d..6a3b4c91997 100644 --- a/client/shared/src/api/client/api/common.ts +++ b/client/shared/src/api/client/api/common.ts @@ -1,6 +1,5 @@ import { type Remote, proxyMarker, releaseProxy, type ProxyMethods, type ProxyOrClone } from 'comlink' -import { noop } from 'lodash' -import { from, type Observable, observable as symbolObservable, Subscription, type Subscribable } from 'rxjs' +import { from, Observable, Subscription } from 'rxjs' import { mergeMap, finalize } from 'rxjs/operators' import { asError, logger } from '@sourcegraph/common' @@ -59,37 +58,18 @@ export const wrapRemoteObservable = ( const observable = from( isPromiseLike(proxyOrProxyPromise) ? proxyOrProxyPromise : Promise.resolve(proxyOrProxyPromise) ).pipe( - mergeMap((proxySubscribable): Subscribable> => { + mergeMap((proxySubscribable): Observable> => { proxySubscription.add(new ProxySubscription(proxySubscribable)) - return { - // Needed for Rx type check - [symbolObservable](): Subscribable> { - return this - }, - subscribe(...args: any[]): Subscription { - // Always subscribe with an object because the other side - // is unable to tell if a Proxy is a function or an observer object - // (they always appear as functions) - let proxyObserver: Parameters[0] - if (typeof args[0] === 'function') { - proxyObserver = { - [proxyMarker]: true, - next: args[0] || noop, - error: args[1] ? error => args[1](asError(error)) : noop, - complete: args[2] || noop, - } - } else { - const partialObserver = args[0] || {} - proxyObserver = { - [proxyMarker]: true, - next: partialObserver.next ? value => partialObserver.next(value) : noop, - error: partialObserver.error ? error => partialObserver.error(asError(error)) : noop, - complete: partialObserver.complete ? () => partialObserver.complete() : noop, - } - } - return syncRemoteSubscription(proxySubscribable.subscribe(proxyObserver)) - }, - } + return new Observable(subscriber => { + const proxyObserver: Parameters[0] = { + [proxyMarker]: true, + // @ts-expect-error - this was previously typed as any + next: value => subscriber.next(value), + error: error => subscriber.error(asError(error)), + complete: () => subscriber.complete(), + } + return syncRemoteSubscription(proxySubscribable.subscribe(proxyObserver)) + }) }) ) return Object.assign(observable, { proxySubscription }) diff --git a/client/shared/src/api/client/connection.ts b/client/shared/src/api/client/connection.ts index c60c64edf03..cc119403a12 100644 --- a/client/shared/src/api/client/connection.ts +++ b/client/shared/src/api/client/connection.ts @@ -1,6 +1,5 @@ import * as comlink from 'comlink' -import { from, Subscription, type Unsubscribable } from 'rxjs' -import { first } from 'rxjs/operators' +import { firstValueFrom, Subscription, type Unsubscribable } from 'rxjs' import { logger } from '@sourcegraph/common' @@ -42,7 +41,7 @@ export async function createExtensionHostClientConnection( /** Proxy to the exposed extension host API */ const initializeExtensionHost = comlink.wrap(endpoints.proxy) - const initialSettings = await from(platformContext.settings).pipe(first()).toPromise() + const initialSettings = await firstValueFrom(platformContext.settings) const proxy = await initializeExtensionHost({ ...initData, // TODO what to do in error case? diff --git a/client/shared/src/api/client/mainthread-api.test.ts b/client/shared/src/api/client/mainthread-api.test.ts index 7eae423c44f..7d238751722 100644 --- a/client/shared/src/api/client/mainthread-api.test.ts +++ b/client/shared/src/api/client/mainthread-api.test.ts @@ -19,7 +19,7 @@ describe('MainThreadAPI', () => { describe('graphQL', () => { test('PlatformContext#requestGraphQL is called with the correct arguments', async () => { - const requestGraphQL = sinon.spy(_options => EMPTY) + const requestGraphQL = sinon.spy(_options => of({ data: null, errors: [] })) const platformContext: Pick< PlatformContext, diff --git a/client/shared/src/api/client/mainthread-api.ts b/client/shared/src/api/client/mainthread-api.ts index 5b7e08a84f6..7b22c17a87e 100644 --- a/client/shared/src/api/client/mainthread-api.ts +++ b/client/shared/src/api/client/mainthread-api.ts @@ -1,5 +1,5 @@ import { type Remote, proxy } from 'comlink' -import { type Unsubscribable, Subscription, from, of } from 'rxjs' +import { type Unsubscribable, Subscription, from, of, lastValueFrom } from 'rxjs' import { publishReplay, refCount, switchMap } from 'rxjs/operators' import { logger } from '@sourcegraph/common' @@ -101,13 +101,13 @@ export const initMainThreadAPI = ( const api: MainThreadAPI = { applySettingsEdit: edit => updateSettings(platformContext, edit), requestGraphQL: (request, variables) => - platformContext - .requestGraphQL({ + lastValueFrom( + platformContext.requestGraphQL({ request, variables, mightContainPrivateInfo: true, }) - .toPromise(), + ), // Commands executeCommand: (command, args) => executeCommand({ command, args }), registerCommand: (command, run) => { diff --git a/client/shared/src/api/client/services/settings.ts b/client/shared/src/api/client/services/settings.ts index b56a7c3803f..a252915cb06 100644 --- a/client/shared/src/api/client/services/settings.ts +++ b/client/shared/src/api/client/services/settings.ts @@ -1,5 +1,4 @@ -import { from } from 'rxjs' -import { first } from 'rxjs/operators' +import { firstValueFrom } from 'rxjs' import type { KeyPath } from '@sourcegraph/client-api' @@ -28,7 +27,7 @@ export async function updateSettings( edit: SettingsEdit ): Promise { const { settings: data, updateSettings: update } = platformContext - const settings = await from(data).pipe(first()).toPromise() + const settings = await firstValueFrom(data) if (!isSettingsValid(settings)) { throw new Error('invalid settings (internal error)') } diff --git a/client/shared/src/api/contract.ts b/client/shared/src/api/contract.ts index ef825b50f2a..e5d7ad1d668 100644 --- a/client/shared/src/api/contract.ts +++ b/client/shared/src/api/contract.ts @@ -1,6 +1,5 @@ import type { Remote, ProxyMarked } from 'comlink' import type { Unsubscribable } from 'rxjs' -import type { DocumentHighlight } from 'sourcegraph' import type { Contributions, @@ -13,7 +12,7 @@ import type { MaybeLoadingResult } from '@sourcegraph/codeintellify' import type * as clientType from '@sourcegraph/extension-api-types' import type { GraphQLResult } from '@sourcegraph/http-client' -import type { ReferenceContext } from '../codeintel/legacy-extensions/api' +import type { DocumentHighlight, ReferenceContext } from '../codeintel/legacy-extensions/api' import type { Occurrence } from '../codeintel/scip' import type { ConfiguredExtension } from '../extensions/extension' import type { SettingsCascade } from '../settings/settings' diff --git a/client/shared/src/api/extension/api/common.ts b/client/shared/src/api/extension/api/common.ts index 6f4c86a04e7..3518d5bdd35 100644 --- a/client/shared/src/api/extension/api/common.ts +++ b/client/shared/src/api/extension/api/common.ts @@ -1,8 +1,10 @@ import { type Remote, type ProxyMarked, proxy, proxyMarker, type UnproxyOrClone } from 'comlink' import { identity } from 'lodash' -import { from, isObservable, type Observable, type Observer, of, type Subscribable, type Unsubscribable } from 'rxjs' +import { from, isObservable, type Observable, type Observer, of, type Unsubscribable, ObservableInput } from 'rxjs' import { map } from 'rxjs/operators' -import type { ProviderResult } from 'sourcegraph' +import type { ProviderResult, Subscribable } from 'sourcegraph' + +import { fromSubscribable } from '@sourcegraph/common' import { isAsyncIterable, isPromiseLike, isSubscribable, observableFromAsyncIterable } from '../../util' @@ -50,8 +52,10 @@ export function providerResultToObservable( mapFunc: (value: T | undefined | null) => R = identity ): Observable { let observable: Observable - if (result && (isPromiseLike(result) || isObservable(result) || isSubscribable(result))) { - observable = from(result).pipe(map(mapFunc)) + if (result && (isPromiseLike(result) || isObservable(result))) { + observable = from(result as ObservableInput).pipe(map(mapFunc)) + } else if (isSubscribable(result)) { + observable = fromSubscribable(result as Subscribable).pipe(map(mapFunc)) } else if (isAsyncIterable(result)) { observable = observableFromAsyncIterable(result).pipe(map(mapFunc)) } else { diff --git a/client/shared/src/api/extension/extensionHostApi.ts b/client/shared/src/api/extension/extensionHostApi.ts index a0cde462607..6416b91cf08 100644 --- a/client/shared/src/api/extension/extensionHostApi.ts +++ b/client/shared/src/api/extension/extensionHostApi.ts @@ -1,6 +1,6 @@ import { proxy } from 'comlink' import { castArray, isEqual } from 'lodash' -import { combineLatest, concat, type Observable, of, type Subscribable } from 'rxjs' +import { combineLatest, concat, type Observable, of } from 'rxjs' import { catchError, defaultIfEmpty, distinctUntilChanged, map, switchMap } from 'rxjs/operators' import type { ProviderResult } from 'sourcegraph' @@ -319,7 +319,7 @@ export function createExtensionHostAPI(state: ExtensionHostState): FlatExtension }) ), state.settings, - state.context as Subscribable>, + state.context as Observable>, ]).pipe( map(([multiContributions, activeEditor, settings, context]) => { // Merge in extra context. @@ -431,7 +431,7 @@ export function callProviders(null), + defaultIfEmpty(null), catchError(error => { logError(error) return [null] @@ -443,7 +443,7 @@ export function callProviders([]), + defaultIfEmpty([]), map(results => ({ isLoading: results.some(hover => hover === LOADING), result: mergeResult(results), diff --git a/client/shared/src/api/extension/test/extensionHost.documentHighlights.test.ts b/client/shared/src/api/extension/test/extensionHost.documentHighlights.test.ts index fe722decf79..c63f3e79805 100644 --- a/client/shared/src/api/extension/test/extensionHost.documentHighlights.test.ts +++ b/client/shared/src/api/extension/test/extensionHost.documentHighlights.test.ts @@ -61,6 +61,7 @@ describe('getDocumentHighlights from ExtensionHost API, it aims to have more e2e position: { line: 1, character: 2 }, textDocument: { uri: typescriptFileUri }, }) + // @ts-expect-error - Unclear how to consolidate the different versions of the DocumentHighlight types .subscribe(observe(value => results.push(value))) // first provider results diff --git a/client/shared/src/api/integration-test/codeEditor.test.ts b/client/shared/src/api/integration-test/codeEditor.test.ts index 98837f570a2..19a16515ec4 100644 --- a/client/shared/src/api/integration-test/codeEditor.test.ts +++ b/client/shared/src/api/integration-test/codeEditor.test.ts @@ -1,7 +1,8 @@ -import { from } from 'rxjs' +import { lastValueFrom } from 'rxjs' import { distinctUntilChanged, switchMap, take, toArray } from 'rxjs/operators' import { describe, test } from 'vitest' +import { fromSubscribable } from '@sourcegraph/common' import { Selection } from '@sourcegraph/extension-api-classes' import { assertToJSON, integrationTestContext } from '../../testing/testHelpers' @@ -11,14 +12,16 @@ describe('CodeEditor (integration)', () => { test('observe changes', async () => { const { extensionAPI, extensionHostAPI } = await integrationTestContext() - const values = from(extensionAPI.app.activeWindow!.activeViewComponentChanges) - .pipe( - switchMap(viewer => (viewer && viewer.type === 'CodeEditor' ? viewer.selectionsChanges : [])), + const values = lastValueFrom( + fromSubscribable(extensionAPI.app.activeWindow!.activeViewComponentChanges).pipe( + switchMap(viewer => + viewer && viewer.type === 'CodeEditor' ? fromSubscribable(viewer.selectionsChanges) : [] + ), distinctUntilChanged(), take(3), toArray() ) - .toPromise() + ) await extensionHostAPI.setEditorSelections({ viewerId: 'viewer#0' }, [new Selection(1, 2, 3, 4)]) await extensionHostAPI.setEditorSelections({ viewerId: 'viewer#0' }, []) diff --git a/client/shared/src/api/integration-test/documents.test.ts b/client/shared/src/api/integration-test/documents.test.ts index 5d5861a938e..c872cc4dbde 100644 --- a/client/shared/src/api/integration-test/documents.test.ts +++ b/client/shared/src/api/integration-test/documents.test.ts @@ -1,5 +1,7 @@ import { describe, expect, test } from 'vitest' +import { fromSubscribable } from '@sourcegraph/common' + import type { TextDocument } from '../../codeintel/legacy-extensions/api' import { assertToJSON, collectSubscribableValues, integrationTestContext } from '../../testing/testHelpers' @@ -28,7 +30,7 @@ describe('Documents (integration)', () => { test('fires when a text document is opened', async () => { const { extensionAPI, extensionHostAPI } = await integrationTestContext() - const values = collectSubscribableValues(extensionAPI.workspace.openedTextDocuments) + const values = collectSubscribableValues(fromSubscribable(extensionAPI.workspace.openedTextDocuments)) expect(values).toEqual([] as TextDocument[]) await extensionHostAPI.addTextDocumentIfNotExists({ uri: 'file:///f2', languageId: 'l2', text: 't2' }) diff --git a/client/shared/src/api/integration-test/roots.test.ts b/client/shared/src/api/integration-test/roots.test.ts index b27259a4b96..f968014d375 100644 --- a/client/shared/src/api/integration-test/roots.test.ts +++ b/client/shared/src/api/integration-test/roots.test.ts @@ -1,5 +1,7 @@ import { describe, expect, test } from 'vitest' +import { fromSubscribable } from '@sourcegraph/common' + import type { WorkspaceRoot } from '../../codeintel/legacy-extensions/api' import { collectSubscribableValues, integrationTestContext } from '../../testing/testHelpers' @@ -34,7 +36,7 @@ describe('Workspace roots (integration)', () => { test('fires when a root is added or removed', async () => { const { extensionAPI, extensionHostAPI } = await integrationTestContext() - const values = collectSubscribableValues(extensionAPI.workspace.rootChanges) + const values = collectSubscribableValues(fromSubscribable(extensionAPI.workspace.rootChanges)) expect(values).toEqual([] as void[]) await extensionHostAPI.addWorkspaceRoot({ diff --git a/client/shared/src/api/integration-test/selections.test.ts b/client/shared/src/api/integration-test/selections.test.ts index 42f99142fe1..33d23ac0c44 100644 --- a/client/shared/src/api/integration-test/selections.test.ts +++ b/client/shared/src/api/integration-test/selections.test.ts @@ -1,8 +1,7 @@ -import { from } from 'rxjs' import { distinctUntilChanged, filter, switchMap } from 'rxjs/operators' import { describe, test } from 'vitest' -import { isDefined, isTaggedUnionMember } from '@sourcegraph/common' +import { fromSubscribable, isDefined, isTaggedUnionMember } from '@sourcegraph/common' import { assertToJSON, collectSubscribableValues, integrationTestContext } from '../../testing/testHelpers' @@ -10,13 +9,13 @@ describe('Selections (integration)', () => { describe('editor.selectionsChanged', () => { test('reflects changes to the current selections', async () => { const { extensionAPI, extensionHostAPI } = await integrationTestContext() - const selectionChanges = from(extensionAPI.app.activeWindowChanges).pipe( + const selectionChanges = fromSubscribable(extensionAPI.app.activeWindowChanges).pipe( filter(isDefined), - switchMap(window => window.activeViewComponentChanges), + switchMap(window => fromSubscribable(window.activeViewComponentChanges)), filter(isDefined), filter(isTaggedUnionMember('type', 'CodeEditor' as const)), distinctUntilChanged(), - switchMap(editor => editor.selectionsChanges) + switchMap(editor => fromSubscribable(editor.selectionsChanges)) ) const selectionValues = collectSubscribableValues(selectionChanges) const testValues = [ diff --git a/client/shared/src/api/integration-test/windows.test.ts b/client/shared/src/api/integration-test/windows.test.ts index d3d35e117c5..10d702aadae 100644 --- a/client/shared/src/api/integration-test/windows.test.ts +++ b/client/shared/src/api/integration-test/windows.test.ts @@ -1,9 +1,11 @@ import { pick } from 'lodash' -import { from, of } from 'rxjs' +import { lastValueFrom, of } from 'rxjs' import { switchMap, take, toArray } from 'rxjs/operators' import type { ViewComponent, Window } from 'sourcegraph' import { describe, expect, test } from 'vitest' +import { fromSubscribable } from '@sourcegraph/common' + import { assertToJSON, integrationTestContext } from '../../testing/testHelpers' import type { TextDocumentData } from '../viewerTypes' @@ -159,13 +161,15 @@ describe('Windows (integration)', () => { viewers: [], }) - const viewers = from(extensionAPI.app.activeWindowChanges) - .pipe( - switchMap(activeWindow => (activeWindow ? activeWindow.activeViewComponentChanges : of(null))), + const viewers = lastValueFrom( + fromSubscribable(extensionAPI.app.activeWindowChanges).pipe( + switchMap(activeWindow => + activeWindow ? fromSubscribable(activeWindow.activeViewComponentChanges) : of(null) + ), take(4), toArray() ) - .toPromise() + ) await extensionHostAPI.addTextDocumentIfNotExists({ uri: 'foo', languageId: 'l1', text: 't1' }) await extensionHostAPI.addTextDocumentIfNotExists({ uri: 'bar', languageId: 'l2', text: 't2' }) diff --git a/client/shared/src/api/util.ts b/client/shared/src/api/util.ts index 8689fbf3c15..8d06e0ce5e9 100644 --- a/client/shared/src/api/util.ts +++ b/client/shared/src/api/util.ts @@ -6,14 +6,8 @@ import { type Remote, proxyMarker, } from 'comlink' -import { - type Unsubscribable, - type Subscribable, - Observable, - type Observer, - type PartialObserver, - Subscription, -} from 'rxjs' +import { type Unsubscribable, Observable, type Observer, type PartialObserver, Subscription } from 'rxjs' +import { Subscribable } from 'sourcegraph' import { hasProperty, AbortError } from '@sourcegraph/common' diff --git a/client/shared/src/codeintel/api.ts b/client/shared/src/codeintel/api.ts index 1d67dbe9ae2..ac80f7bd757 100644 --- a/client/shared/src/codeintel/api.ts +++ b/client/shared/src/codeintel/api.ts @@ -1,5 +1,5 @@ import { castArray } from 'lodash' -import { from, type Observable, of } from 'rxjs' +import { from, of, lastValueFrom } from 'rxjs' import { defaultIfEmpty, map } from 'rxjs/operators' import { @@ -42,7 +42,7 @@ export interface CodeIntelAPI { scipParameters?: ScipParameters ): Promise getImplementations(parameters: TextDocumentPositionParameters): Promise - getHover(textParameters: TextDocumentPositionParameters): Promise + getHover(textParameters: TextDocumentPositionParameters): Promise getDocumentHighlights(textParameters: TextDocumentPositionParameters): Promise } @@ -57,9 +57,9 @@ export async function getOrCreateCodeIntelAPI(context: PlatformContext): Promise return codeIntelAPI } - return new Promise((resolve, reject) => { - context.settings.subscribe(settingsCascade => { - try { + return lastValueFrom( + context.settings.pipe( + map(settingsCascade => { if (!isSettingsValid(settingsCascade)) { throw new Error('Settings are not valid') } @@ -68,28 +68,27 @@ export async function getOrCreateCodeIntelAPI(context: PlatformContext): Promise telemetryService: context.telemetryService, settings: newSettingsGetter(settingsCascade), }) - resolve(codeIntelAPI) - } catch (error) { - reject(error) - } - }) - }) + return codeIntelAPI + }) + ) + ) } class DefaultCodeIntelAPI implements CodeIntelAPI { private locationResult( locations: sourcegraph.ProviderResult ): Promise { - return locations - .pipe( - defaultIfEmpty(), + return lastValueFrom( + locations.pipe( + defaultIfEmpty(undefined), map(result => castArray(result) .filter(isDefined) .map(location => ({ ...location, uri: location.uri.toString() })) ) - ) - .toPromise() + ), + { defaultValue: [] } + ) } public hasReferenceProvidersForDocument(textParameters: TextDocumentPositionParameters): Promise { @@ -128,26 +127,26 @@ class DefaultCodeIntelAPI implements CodeIntelAPI { request.providers.implementations.provideLocations(request.document, request.position) ) } - public getHover(textParameters: TextDocumentPositionParameters): Promise { + public getHover(textParameters: TextDocumentPositionParameters): Promise { const request = requestFor(textParameters) - return ( + return lastValueFrom( request.providers.hover .provideHover(request.document, request.position) // We intentionally don't use `defaultIfEmpty()` here because // that makes the popover load with an empty docstring. - .pipe(map(result => fromHoverMerged([result]))) - .toPromise() + .pipe(map(result => fromHoverMerged([result]))), + { defaultValue: null } ) } public getDocumentHighlights(textParameters: TextDocumentPositionParameters): Promise { const request = requestFor(textParameters) - return request.providers.documentHighlights - .provideDocumentHighlights(request.document, request.position) - .pipe( - defaultIfEmpty(), + return lastValueFrom( + request.providers.documentHighlights.provideDocumentHighlights(request.document, request.position).pipe( + defaultIfEmpty(undefined), map(result => result || []) - ) - .toPromise() + ), + { defaultValue: [] } + ) } } @@ -242,13 +241,8 @@ export function injectNewCodeintel( } export function newCodeIntelExtensionHostAPI(codeintel: CodeIntelAPI): CodeIntelExtensionHostAPI { - function thenMaybeLoadingResult(promise: Observable): Observable> { - return promise.pipe( - map(result => { - const maybeLoadingResult: MaybeLoadingResult = { isLoading: false, result } - return maybeLoadingResult - }) - ) + function thenMaybeLoadingResult(result: T): MaybeLoadingResult { + return { isLoading: false, result } } return { @@ -257,22 +251,22 @@ export function newCodeIntelExtensionHostAPI(codeintel: CodeIntelAPI): CodeIntel }, getLocations(id, parameters) { if (!id.startsWith('implementations_')) { - return proxySubscribable(thenMaybeLoadingResult(of([]))) + return proxySubscribable(of({ isLoading: false, result: [] })) } - return proxySubscribable(thenMaybeLoadingResult(from(codeintel.getImplementations(parameters)))) + return proxySubscribable(from(codeintel.getImplementations(parameters).then(thenMaybeLoadingResult))) }, getDefinition(parameters) { - return proxySubscribable(thenMaybeLoadingResult(from(codeintel.getDefinition(parameters)))) + return proxySubscribable(from(codeintel.getDefinition(parameters).then(thenMaybeLoadingResult))) }, getReferences(parameters, context, scipParameters) { return proxySubscribable( - thenMaybeLoadingResult(from(codeintel.getReferences(parameters, context, scipParameters))) + from(codeintel.getReferences(parameters, context, scipParameters).then(thenMaybeLoadingResult)) ) }, getDocumentHighlights: (textParameters: TextDocumentPositionParameters) => proxySubscribable(from(codeintel.getDocumentHighlights(textParameters))), getHover: (textParameters: TextDocumentPositionParameters) => - proxySubscribable(thenMaybeLoadingResult(from(codeintel.getHover(textParameters)))), + proxySubscribable(from(codeintel.getHover(textParameters).then(thenMaybeLoadingResult))), } } diff --git a/client/shared/src/codeintel/legacy-extensions/api.ts b/client/shared/src/codeintel/legacy-extensions/api.ts index 32b98d6ee6b..04ea5b2266c 100644 --- a/client/shared/src/codeintel/legacy-extensions/api.ts +++ b/client/shared/src/codeintel/legacy-extensions/api.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Observable, Unsubscribable } from 'rxjs' +import { lastValueFrom, type Observable, type Unsubscribable } from 'rxjs' import type { GraphQLResult } from '@sourcegraph/http-client' @@ -361,10 +361,9 @@ export function requestGraphQL(query: string, vars?: { [name: string]: unknow ) ) } - return context - - .requestGraphQL({ request: query, variables: vars as any, mightContainPrivateInfo: true }) - .toPromise() + return lastValueFrom( + context.requestGraphQL({ request: query, variables: vars as any, mightContainPrivateInfo: true }) + ) } export function getSetting(key: string): T | undefined { diff --git a/client/shared/src/hover/actions.test.ts b/client/shared/src/hover/actions.test.ts index 35e88eab42b..74973a39701 100644 --- a/client/shared/src/hover/actions.test.ts +++ b/client/shared/src/hover/actions.test.ts @@ -396,7 +396,7 @@ describe('getDefinitionURL', () => { Partial ) => '' ) - await of>({ + await of({ isLoading: false, result: [{ uri: 'git://r3?c3#f' }], }) @@ -424,7 +424,7 @@ describe('getDefinitionURL', () => { describe('when the result is inside the current root', () => { it('emits the definition URL the user input revision (not commit SHA) of the root', () => expect( - of>({ + of({ isLoading: false, result: [{ uri: 'git://r3?c3#f' }], }) @@ -445,7 +445,7 @@ describe('getDefinitionURL', () => { describe('when the result is not inside the current root (different repo and/or commit)', () => { it('emits the definition URL with range', () => expect( - of>({ + of({ isLoading: false, result: [FIXTURE_LOCATION_CLIENT], }) @@ -464,7 +464,7 @@ describe('getDefinitionURL', () => { it('emits the definition URL without range', () => expect( - of>({ + of({ isLoading: false, result: [{ ...FIXTURE_LOCATION_CLIENT, range: undefined }], }) @@ -485,7 +485,7 @@ describe('getDefinitionURL', () => { it('emits the definition panel URL if there is more than 1 location result', () => expect( - of>({ + of({ isLoading: false, result: [FIXTURE_LOCATION_CLIENT, { ...FIXTURE_LOCATION, uri: 'other' }], }) diff --git a/client/shared/src/hover/actions.ts b/client/shared/src/hover/actions.ts index 0ac00e3f780..04f00cac531 100644 --- a/client/shared/src/hover/actions.ts +++ b/client/shared/src/hover/actions.ts @@ -1,7 +1,18 @@ import type { Remote } from 'comlink' import * as H from 'history' import { isEqual, uniqWith } from 'lodash' -import { combineLatest, merge, type Observable, of, Subscription, type Unsubscribable, concat, from, EMPTY } from 'rxjs' +import { + combineLatest, + merge, + type Observable, + of, + Subscription, + type Unsubscribable, + concat, + from, + EMPTY, + lastValueFrom, +} from 'rxjs' import { catchError, delay, @@ -246,7 +257,7 @@ export const getDefinitionURL = Partial> > => { if (definitions.length === 0) { - return of>({ isLoading, result: null }) + return of({ isLoading, result: null }) } // Get unique definitions. @@ -258,7 +269,7 @@ export const getDefinitionURL = workspaceRoots || [], parseRepoURI(parameters.textDocument.uri) ) - return of>({ + return of({ isLoading, result: { url: urlToFile( @@ -409,8 +420,8 @@ export function registerHoverContributions({ const parameters: TextDocumentPositionParameters & URLToFileContext = JSON.parse(parametersString) - const { result } = await wrapRemoteObservable(extensionHostAPI.getDefinition(parameters)) - .pipe( + const { result } = await lastValueFrom( + wrapRemoteObservable(extensionHostAPI.getDefinition(parameters)).pipe( getDefinitionURL( { urlToFile, requestGraphQL }, { @@ -425,7 +436,7 @@ export function registerHoverContributions({ ), first(({ isLoading, result }) => !isLoading || result !== null) ) - .toPromise() + ) if (!result) { throw new Error('No definition found.') diff --git a/client/shared/src/platform/context.ts b/client/shared/src/platform/context.ts index 4d88ec3f2f3..2cd929ebb54 100644 --- a/client/shared/src/platform/context.ts +++ b/client/shared/src/platform/context.ts @@ -1,6 +1,6 @@ import type { Endpoint } from 'comlink' import { isObject } from 'lodash' -import type { Observable, Subscribable, Subscription } from 'rxjs' +import type { Observable, Subscription } from 'rxjs' import type { DiffPart } from '@sourcegraph/codeintellify' import { hasProperty } from '@sourcegraph/common' @@ -73,7 +73,7 @@ export interface PlatformContext { * * @deprecated Use useSettings instead */ - readonly settings: Subscribable> + readonly settings: Observable> /** * Update the settings for the subject, either by inserting/changing a specific value or by overwriting the diff --git a/client/shared/src/testing/integration/context.ts b/client/shared/src/testing/integration/context.ts index a4921064047..ff8e46a74a5 100644 --- a/client/shared/src/testing/integration/context.ts +++ b/client/shared/src/testing/integration/context.ts @@ -10,7 +10,7 @@ import type { Test } from 'mocha' import { readFile, mkdir } from 'mz/fs' import pTimeout from 'p-timeout' import * as prettier from 'prettier' -import { Subject, Subscription, throwError } from 'rxjs' +import { Subject, Subscription, lastValueFrom, throwError } from 'rxjs' import { first, timeoutWith } from 'rxjs/operators' import { STATIC_ASSETS_PATH } from '@sourcegraph/build-config' @@ -288,15 +288,15 @@ export const createSharedIntegrationTestContext = async < triggerRequest: () => Promise | void, operationName: O ): Promise[0]> => { - const requestPromise = graphQlRequests - .pipe( + const requestPromise = lastValueFrom( + graphQlRequests.pipe( first( (request: GraphQLRequestEvent): request is GraphQLRequestEvent => request.operationName === operationName ), timeoutWith(4000, throwError(new Error(`Timeout waiting for GraphQL request "${operationName}"`))) ) - .toPromise() + ) await triggerRequest() const { variables } = await requestPromise return variables diff --git a/client/shared/src/testing/testHelpers.ts b/client/shared/src/testing/testHelpers.ts index efe5424c8d6..ec105ad1b15 100644 --- a/client/shared/src/testing/testHelpers.ts +++ b/client/shared/src/testing/testHelpers.ts @@ -1,5 +1,5 @@ import type { Remote } from 'comlink' -import { throwError, of, Subscription, type Unsubscribable, type Subscribable } from 'rxjs' +import { throwError, of, Subscription, type Unsubscribable, type Observable } from 'rxjs' import type * as sourcegraph from 'sourcegraph' import { expect } from 'vitest' @@ -107,8 +107,9 @@ export async function integrationTestContext( } } -export function collectSubscribableValues(subscribable: Subscribable): T[] { +export function collectSubscribableValues(observable: Observable): T[] { const values: T[] = [] - subscribable.subscribe(value => values.push(value)) + // eslint-disable-next-line rxjs/no-ignored-subscription + observable.subscribe(value => values.push(value)) return values } diff --git a/client/shared/src/util/useInputValidation.ts b/client/shared/src/util/useInputValidation.ts index 00445169762..46cdb3e27c7 100644 --- a/client/shared/src/util/useInputValidation.ts +++ b/client/shared/src/util/useInputValidation.ts @@ -180,7 +180,7 @@ export function createValidationPipeline( combineLatest([ // Validate immediately if the user has provided an initial input value concat( - initialValue !== undefined ? of({ value: initialValue, validate: true }) : EMPTY, + initialValue !== undefined ? of({ value: initialValue, validate: true }) : EMPTY, inputValidationEvents ), inputReferences, diff --git a/client/web-sveltekit/src/lib/graphql/urql.ts b/client/web-sveltekit/src/lib/graphql/urql.ts index 78842e6d5bb..5c29cf85ee7 100644 --- a/client/web-sveltekit/src/lib/graphql/urql.ts +++ b/client/web-sveltekit/src/lib/graphql/urql.ts @@ -206,7 +206,7 @@ export function infinityQuery>>( + return concat( of({ fetching: true, stale: false, restoring: false }), from(args.client.executeRequestOperation(operation).toPromise()).pipe( map(({ data, stale, operation, error, extensions }) => ({ diff --git a/client/web-sveltekit/vite.config.ts b/client/web-sveltekit/vite.config.ts index 32c657b941a..895a668fe29 100644 --- a/client/web-sveltekit/vite.config.ts +++ b/client/web-sveltekit/vite.config.ts @@ -73,33 +73,6 @@ export default defineConfig(({ mode }) => { find: /^(.*)\.gql$/, replacement: '$1.gql.ts', }, - // In rxjs v6 these are directories and cannot be imported from directly in the production build. - // The following error occurs: - // Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '[...]/node_modules/rxjs/operators' is not supported resolving ES modules - { - find: /^rxjs\/(operators|fetch)$/, - replacement: 'rxjs/$1/index.js', - customResolver(source, importer, options) { - // This is an hacky way to make the dev build work. @sourcegraph/telemetry uses a newer - // version of rxjs (v7) where `rjx/operators` and `rxjs/fetch` are properly mapped - // to their respective files in package.json. - // Applying the same replacement to this version results in an error. - // I tried various ways to prevent having the alias be applied to `@sourcegraph/telemetry` - // without success: - // - Removing this alias causes the production build to fail do the issue mentioned at the - // top of this alias. - // - Adding something like `{ ssr: { external: '@sourcegraph/telemetry' } }` to the config - // does not prevent the alias from being applied. Maybe I don't understand how `external` - // is supposed to work. - // - Using a custom plugin that implements a custom resolveId function is somehow not being - // run in the production build for `rxjs` imports. Maybe it has something to do with the - // interop between vite and sveltekit. - if (importer?.includes('@sourcegraph/telemetry')) { - source = source.replace('/index.js', '') - } - return this.resolve(source, importer, options) - }, - }, // Without aliasing lodash to lodash-es we get the following error: // SyntaxError: Named export 'castArray' not found. The requested module 'lodash' is a CommonJS module, which may not support all module.exports as named exports. { diff --git a/client/web/dev/BUILD.bazel b/client/web/dev/BUILD.bazel index e4717929267..6629934ea84 100644 --- a/client/web/dev/BUILD.bazel +++ b/client/web/dev/BUILD.bazel @@ -89,10 +89,6 @@ esbuild( "path-browserify", "monaco-yaml/lib/esm/monaco.contribution", "monaco-yaml/lib/esm/yaml.worker", - "rxjs/_esm5/internal/OuterSubscriber", - "rxjs/_esm5/internal/util/subscribeToResult", - "rxjs/_esm5/internal/util/subscribeToArray", - "rxjs/_esm5/internal/Observable", ], format = "cjs", platform = "node", diff --git a/client/web/dev/esbuild/config.ts b/client/web/dev/esbuild/config.ts index 596dc586911..ffe0e90d368 100644 --- a/client/web/dev/esbuild/config.ts +++ b/client/web/dev/esbuild/config.ts @@ -8,7 +8,6 @@ import { stylePlugin, packageResolutionPlugin, monacoPlugin, - RXJS_RESOLUTIONS, buildTimerPlugin, workerPlugin, } from '@sourcegraph/build-config/src/esbuild/plugins' @@ -51,7 +50,6 @@ export function esbuildBuildOptions(ENVIRONMENT_CONFIG: EnvironmentConfig): esbu workerPlugin, packageResolutionPlugin({ path: require.resolve('path-browserify'), - ...RXJS_RESOLUTIONS, ...(ENVIRONMENT_CONFIG.DEV_WEB_BUILDER_OMIT_SLOW_DEPS ? { // Monaco diff --git a/client/web/src/SearchQueryStateObserver.tsx b/client/web/src/SearchQueryStateObserver.tsx index d6710ddb0a1..4610a1a3d30 100644 --- a/client/web/src/SearchQueryStateObserver.tsx +++ b/client/web/src/SearchQueryStateObserver.tsx @@ -1,8 +1,7 @@ import { type FC, useLayoutEffect, useRef, useState } from 'react' import { type Location, useLocation } from 'react-router-dom' -import { BehaviorSubject } from 'rxjs' -import { first } from 'rxjs/operators' +import { BehaviorSubject, firstValueFrom } from 'rxjs' import type { PlatformContext } from '@sourcegraph/shared/src/platform/context' import { isSearchContextSpecAvailable } from '@sourcegraph/shared/src/search' @@ -45,12 +44,13 @@ export const SearchQueryStateObserver: FC = props location: locationSubject, isSearchContextAvailable: (searchContext: string) => searchContextsEnabled - ? isSearchContextSpecAvailable({ - spec: searchContext, - platformContext, - }) - .pipe(first()) - .toPromise() + ? firstValueFrom( + isSearchContextSpecAvailable({ + spec: searchContext, + platformContext, + }), + { defaultValue: false } + ) : Promise.resolve(false), }).subscribe(parsedSearchURLAndContext => { if (parsedSearchURLAndContext.query) { diff --git a/client/web/src/components/externalServices/backend.ts b/client/web/src/components/externalServices/backend.ts index 5ab2a4fbd0d..b46038b96fa 100644 --- a/client/web/src/components/externalServices/backend.ts +++ b/client/web/src/components/externalServices/backend.ts @@ -2,7 +2,7 @@ import type { Dispatch, SetStateAction } from 'react' import type { QueryTuple, MutationTuple, QueryResult } from '@apollo/client' import { parse } from 'jsonc-parser' -import type { Observable } from 'rxjs' +import { type Observable, lastValueFrom } from 'rxjs' import { map } from 'rxjs/operators' import { createAggregateError } from '@sourcegraph/common' @@ -111,28 +111,30 @@ export const useUpdateExternalService = ( export function updateExternalService( variables: UpdateExternalServiceVariables ): Promise { - return requestGraphQL( - UPDATE_EXTERNAL_SERVICE, - variables - ) - .pipe( + return lastValueFrom( + requestGraphQL( + UPDATE_EXTERNAL_SERVICE, + variables + ).pipe( map(dataOrThrowErrors), map(data => data.updateExternalService) ) - .toPromise() + ) } export async function deleteExternalService(externalService: Scalars['ID']): Promise { - const result = await requestGraphQL( - gql` - mutation DeleteExternalService($externalService: ID!) { - deleteExternalService(externalService: $externalService) { - alwaysNil + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation DeleteExternalService($externalService: ID!) { + deleteExternalService(externalService: $externalService) { + alwaysNil + } } - } - `, - { externalService } - ).toPromise() + `, + { externalService } + ) + ) dataOrThrowErrors(result) } diff --git a/client/web/src/enterprise/batches/close/backend.ts b/client/web/src/enterprise/batches/close/backend.ts index 5730b884c65..bfbb716595e 100644 --- a/client/web/src/enterprise/batches/close/backend.ts +++ b/client/web/src/enterprise/batches/close/backend.ts @@ -1,18 +1,22 @@ +import { lastValueFrom } from 'rxjs' + import { gql, dataOrThrowErrors } from '@sourcegraph/http-client' import { requestGraphQL } from '../../../backend/graphql' import type { CloseBatchChangeResult, CloseBatchChangeVariables } from '../../../graphql-operations' export async function closeBatchChange({ batchChange, closeChangesets }: CloseBatchChangeVariables): Promise { - const result = await requestGraphQL( - gql` - mutation CloseBatchChange($batchChange: ID!, $closeChangesets: Boolean) { - closeBatchChange(batchChange: $batchChange, closeChangesets: $closeChangesets) { - id + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation CloseBatchChange($batchChange: ID!, $closeChangesets: Boolean) { + closeBatchChange(batchChange: $batchChange, closeChangesets: $closeChangesets) { + id + } } - } - `, - { batchChange, closeChangesets } - ).toPromise() + `, + { batchChange, closeChangesets } + ) + ) dataOrThrowErrors(result) } diff --git a/client/web/src/enterprise/batches/detail/backend.ts b/client/web/src/enterprise/batches/detail/backend.ts index 3229d8ad5d0..3ae3ae73beb 100644 --- a/client/web/src/enterprise/batches/detail/backend.ts +++ b/client/web/src/enterprise/batches/detail/backend.ts @@ -1,5 +1,5 @@ import type { QueryResult, QueryTuple } from '@apollo/client' -import { EMPTY, type Observable } from 'rxjs' +import { EMPTY, lastValueFrom, type Observable } from 'rxjs' import { expand, map, reduce } from 'rxjs/operators' import { dataOrThrowErrors, gql, useLazyQuery, useQuery } from '@sourcegraph/http-client' @@ -459,37 +459,39 @@ export const queryChangesets = ({ ) export async function syncChangeset(changeset: Scalars['ID']): Promise { - const result = await requestGraphQL( - gql` - mutation SyncChangeset($changeset: ID!) { - syncChangeset(changeset: $changeset) { - alwaysNil + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation SyncChangeset($changeset: ID!) { + syncChangeset(changeset: $changeset) { + alwaysNil + } } - } - `, - { changeset } - ).toPromise() + `, + { changeset } + ) + ) dataOrThrowErrors(result) } export async function reenqueueChangeset(changeset: Scalars['ID']): Promise { - return requestGraphQL( - gql` - mutation ReenqueueChangeset($changeset: ID!) { - reenqueueChangeset(changeset: $changeset) { - ...ChangesetFields + return lastValueFrom( + requestGraphQL( + gql` + mutation ReenqueueChangeset($changeset: ID!) { + reenqueueChangeset(changeset: $changeset) { + ...ChangesetFields + } } - } - ${changesetFieldsFragment} - `, - { changeset } - ) - .pipe( + ${changesetFieldsFragment} + `, + { changeset } + ).pipe( map(dataOrThrowErrors), map(data => data.reenqueueChangeset) ) - .toPromise() + ) } // Because thats the name in the API: @@ -628,16 +630,18 @@ export const useChangesetCountsOverTime = ( }) export async function deleteBatchChange(batchChange: Scalars['ID']): Promise { - const result = await requestGraphQL( - gql` - mutation DeleteBatchChange($batchChange: ID!) { - deleteBatchChange(batchChange: $batchChange) { - alwaysNil + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation DeleteBatchChange($batchChange: ID!) { + deleteBatchChange(batchChange: $batchChange) { + alwaysNil + } } - } - `, - { batchChange } - ).toPromise() + `, + { batchChange } + ) + ) dataOrThrowErrors(result) } @@ -657,20 +661,20 @@ const changesetDiffFragment = gql` ` export async function getChangesetDiff(changeset: Scalars['ID']): Promise { - return requestGraphQL( - gql` - query ChangesetDiff($changeset: ID!) { - node(id: $changeset) { - __typename - ...ChangesetDiffFields + return lastValueFrom( + requestGraphQL( + gql` + query ChangesetDiff($changeset: ID!) { + node(id: $changeset) { + __typename + ...ChangesetDiffFields + } } - } - ${changesetDiffFragment} - `, - { changeset } - ) - .pipe( + ${changesetDiffFragment} + `, + { changeset } + ).pipe( map(dataOrThrowErrors), map(({ node }) => { if (!node) { @@ -693,7 +697,7 @@ export async function getChangesetDiff(changeset: Scalars['ID']): Promise { - return requestGraphQL( - gql` - query ChangesetScheduleEstimate($changeset: ID!) { - node(id: $changeset) { - __typename - ...ChangesetScheduleEstimateFields + return lastValueFrom( + requestGraphQL( + gql` + query ChangesetScheduleEstimate($changeset: ID!) { + node(id: $changeset) { + __typename + ...ChangesetScheduleEstimateFields + } } - } - ${changesetScheduleEstimateFragment} - `, - { changeset } - ) - .pipe( + ${changesetScheduleEstimateFragment} + `, + { changeset } + ).pipe( map(dataOrThrowErrors), map(({ node }) => { if (!node) { @@ -730,20 +734,22 @@ export async function getChangesetScheduleEstimate(changeset: Scalars['ID']): Pr return node.scheduleEstimateAt }) ) - .toPromise() + ) } export async function detachChangesets(batchChange: Scalars['ID'], changesets: Scalars['ID'][]): Promise { - const result = await requestGraphQL( - gql` - mutation DetachChangesets($batchChange: ID!, $changesets: [ID!]!) { - detachChangesets(batchChange: $batchChange, changesets: $changesets) { - id + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation DetachChangesets($batchChange: ID!, $changesets: [ID!]!) { + detachChangesets(batchChange: $batchChange, changesets: $changesets) { + id + } } - } - `, - { batchChange, changesets } - ).toPromise() + `, + { batchChange, changesets } + ) + ) dataOrThrowErrors(result) } @@ -752,30 +758,34 @@ export async function createChangesetComments( changesets: Scalars['ID'][], body: string ): Promise { - const result = await requestGraphQL( - gql` - mutation CreateChangesetComments($batchChange: ID!, $changesets: [ID!]!, $body: String!) { - createChangesetComments(batchChange: $batchChange, changesets: $changesets, body: $body) { - id + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation CreateChangesetComments($batchChange: ID!, $changesets: [ID!]!, $body: String!) { + createChangesetComments(batchChange: $batchChange, changesets: $changesets, body: $body) { + id + } } - } - `, - { batchChange, changesets, body } - ).toPromise() + `, + { batchChange, changesets, body } + ) + ) dataOrThrowErrors(result) } export async function reenqueueChangesets(batchChange: Scalars['ID'], changesets: Scalars['ID'][]): Promise { - const result = await requestGraphQL( - gql` - mutation ReenqueueChangesets($batchChange: ID!, $changesets: [ID!]!) { - reenqueueChangesets(batchChange: $batchChange, changesets: $changesets) { - id + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation ReenqueueChangesets($batchChange: ID!, $changesets: [ID!]!) { + reenqueueChangesets(batchChange: $batchChange, changesets: $changesets) { + id + } } - } - `, - { batchChange, changesets } - ).toPromise() + `, + { batchChange, changesets } + ) + ) dataOrThrowErrors(result) } @@ -784,30 +794,34 @@ export async function mergeChangesets( changesets: Scalars['ID'][], squash: boolean ): Promise { - const result = await requestGraphQL( - gql` - mutation MergeChangesets($batchChange: ID!, $changesets: [ID!]!, $squash: Boolean!) { - mergeChangesets(batchChange: $batchChange, changesets: $changesets, squash: $squash) { - id + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation MergeChangesets($batchChange: ID!, $changesets: [ID!]!, $squash: Boolean!) { + mergeChangesets(batchChange: $batchChange, changesets: $changesets, squash: $squash) { + id + } } - } - `, - { batchChange, changesets, squash } - ).toPromise() + `, + { batchChange, changesets, squash } + ) + ) dataOrThrowErrors(result) } export async function closeChangesets(batchChange: Scalars['ID'], changesets: Scalars['ID'][]): Promise { - const result = await requestGraphQL( - gql` - mutation CloseChangesets($batchChange: ID!, $changesets: [ID!]!) { - closeChangesets(batchChange: $batchChange, changesets: $changesets) { - id + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation CloseChangesets($batchChange: ID!, $changesets: [ID!]!) { + closeChangesets(batchChange: $batchChange, changesets: $changesets) { + id + } } - } - `, - { batchChange, changesets } - ).toPromise() + `, + { batchChange, changesets } + ) + ) dataOrThrowErrors(result) } @@ -816,16 +830,18 @@ export async function publishChangesets( changesets: Scalars['ID'][], draft: boolean ): Promise { - const result = await requestGraphQL( - gql` - mutation PublishChangesets($batchChange: ID!, $changesets: [ID!]!, $draft: Boolean!) { - publishChangesets(batchChange: $batchChange, changesets: $changesets, draft: $draft) { - id + const result = await lastValueFrom( + requestGraphQL( + gql` + mutation PublishChangesets($batchChange: ID!, $changesets: [ID!]!, $draft: Boolean!) { + publishChangesets(batchChange: $batchChange, changesets: $changesets, draft: $draft) { + id + } } - } - `, - { batchChange, changesets, draft } - ).toPromise() + `, + { batchChange, changesets, draft } + ) + ) dataOrThrowErrors(result) } diff --git a/client/web/src/enterprise/batches/preview/backend.ts b/client/web/src/enterprise/batches/preview/backend.ts index 1c56fea8dce..cadd6b49c04 100644 --- a/client/web/src/enterprise/batches/preview/backend.ts +++ b/client/web/src/enterprise/batches/preview/backend.ts @@ -1,4 +1,4 @@ -import type { Observable } from 'rxjs' +import { lastValueFrom, type Observable } from 'rxjs' import { map } from 'rxjs/operators' import { gql, dataOrThrowErrors } from '@sourcegraph/http-client' @@ -139,49 +139,49 @@ export const createBatchChange = ({ batchSpec, publicationStates, }: CreateBatchChangeVariables): Promise => - requestGraphQL( - gql` - mutation CreateBatchChange($batchSpec: ID!, $publicationStates: [ChangesetSpecPublicationStateInput!]) { - createBatchChange(batchSpec: $batchSpec, publicationStates: $publicationStates) { - id - url + lastValueFrom( + requestGraphQL( + gql` + mutation CreateBatchChange($batchSpec: ID!, $publicationStates: [ChangesetSpecPublicationStateInput!]) { + createBatchChange(batchSpec: $batchSpec, publicationStates: $publicationStates) { + id + url + } } - } - `, - { batchSpec, publicationStates } - ) - .pipe( + `, + { batchSpec, publicationStates } + ).pipe( map(dataOrThrowErrors), map(data => data.createBatchChange) ) - .toPromise() + ) export const applyBatchChange = ({ batchSpec, batchChange, publicationStates, }: ApplyBatchChangeVariables): Promise => - requestGraphQL( - gql` - mutation ApplyBatchChange( - $batchSpec: ID! - $batchChange: ID! - $publicationStates: [ChangesetSpecPublicationStateInput!] - ) { - applyBatchChange( - batchSpec: $batchSpec - ensureBatchChange: $batchChange - publicationStates: $publicationStates + lastValueFrom( + requestGraphQL( + gql` + mutation ApplyBatchChange( + $batchSpec: ID! + $batchChange: ID! + $publicationStates: [ChangesetSpecPublicationStateInput!] ) { - id - url + applyBatchChange( + batchSpec: $batchSpec + ensureBatchChange: $batchChange + publicationStates: $publicationStates + ) { + id + url + } } - } - `, - { batchSpec, batchChange, publicationStates } - ) - .pipe( + `, + { batchSpec, batchChange, publicationStates } + ).pipe( map(dataOrThrowErrors), map(data => data.applyBatchChange) ) - .toPromise() + ) diff --git a/client/web/src/enterprise/codeintel/indexes/components/EnqueueForm.tsx b/client/web/src/enterprise/codeintel/indexes/components/EnqueueForm.tsx index 7362d418cec..ad4a430ab81 100644 --- a/client/web/src/enterprise/codeintel/indexes/components/EnqueueForm.tsx +++ b/client/web/src/enterprise/codeintel/indexes/components/EnqueueForm.tsx @@ -41,8 +41,8 @@ export const EnqueueForm: FunctionComponent = ({ const queueResultLength = indexes?.queueAutoIndexJobsForRepo.length || 0 setQueueResult(queueResultLength) - if (queueResultLength > 0) { - querySubject.next(indexes?.queueAutoIndexJobsForRepo[0].inputCommit) + if (queueResultLength > 0 && indexes?.queueAutoIndexJobsForRepo[0].inputCommit !== undefined) { + querySubject.next(indexes.queueAutoIndexJobsForRepo[0].inputCommit) } } catch (error) { setEnqueueError(error) diff --git a/client/web/src/enterprise/codeintel/indexes/pages/CodeIntelPreciseIndexesPage.tsx b/client/web/src/enterprise/codeintel/indexes/pages/CodeIntelPreciseIndexesPage.tsx index 197dcba5916..eae35f829e4 100644 --- a/client/web/src/enterprise/codeintel/indexes/pages/CodeIntelPreciseIndexesPage.tsx +++ b/client/web/src/enterprise/codeintel/indexes/pages/CodeIntelPreciseIndexesPage.tsx @@ -170,7 +170,7 @@ export const CodeIntelPreciseIndexesPage: FunctionComponent new Subject(), []) + const refresh = useMemo(() => new Subject(), []) const querySubject = useMemo(() => new Subject(), []) // State used to control bulk index selection diff --git a/client/web/src/enterprise/insights/core/hooks/live-preview-insight/use-live-preview-lang-stats-insight.ts b/client/web/src/enterprise/insights/core/hooks/live-preview-insight/use-live-preview-lang-stats-insight.ts index 1031ffbcdb6..708a11e474b 100644 --- a/client/web/src/enterprise/insights/core/hooks/live-preview-insight/use-live-preview-lang-stats-insight.ts +++ b/client/web/src/enterprise/insights/core/hooks/live-preview-insight/use-live-preview-lang-stats-insight.ts @@ -122,7 +122,7 @@ async function getLangStats(inputs: GetInsightContentInputs): Promise { throw new Error('You have to specify a dashboard visibility') } - const updatedDashboard = await updateDashboard({ - id: dashboard.id, - nextDashboardInput: { - name, - owners: [owner], - }, - }).toPromise() + const updatedDashboard = await lastValueFrom( + updateDashboard({ + id: dashboard.id, + nextDashboardInput: { + name, + owners: [owner], + }, + }) + ) navigate(`/insights/dashboards/${updatedDashboard.id}`) } diff --git a/client/web/src/enterprise/searchContexts/backend.ts b/client/web/src/enterprise/searchContexts/backend.ts index a700f202c84..1e6f9439b78 100644 --- a/client/web/src/enterprise/searchContexts/backend.ts +++ b/client/web/src/enterprise/searchContexts/backend.ts @@ -1,5 +1,6 @@ +import { lastValueFrom } from 'rxjs' + import { dataOrThrowErrors, gql } from '@sourcegraph/http-client' -import type { GraphQLResult } from '@sourcegraph/http-client' import { requestGraphQL } from '../../backend/graphql' import type { InputMaybe, RepositoriesByNamesResult, RepositoriesByNamesVariables } from '../../graphql-operations' @@ -27,14 +28,13 @@ export async function fetchRepositoriesByNames( let after: InputMaybe = null while (true) { - const result: GraphQLResult = await requestGraphQL< - RepositoriesByNamesResult, - RepositoriesByNamesVariables - >(query, { - names, - first, - after, - }).toPromise() + const result = await lastValueFrom( + requestGraphQL(query, { + names, + first, + after, + }) + ) const data: RepositoriesByNamesResult = dataOrThrowErrors(result) diff --git a/client/web/src/org/backend.ts b/client/web/src/org/backend.ts index f9099a25a92..8cf14bb64b4 100644 --- a/client/web/src/org/backend.ts +++ b/client/web/src/org/backend.ts @@ -1,4 +1,4 @@ -import { concat, type Observable } from 'rxjs' +import { concat, lastValueFrom, type Observable } from 'rxjs' import { map, mergeMap } from 'rxjs/operators' import { createAggregateError } from '@sourcegraph/common' @@ -65,19 +65,19 @@ export function createOrganization(args: { /** The new organization's display name (e.g. full name) in the organization profile. */ displayName?: string }): Promise { - return requestGraphQL( - gql` - mutation CreateOrganization($name: String!, $displayName: String) { - createOrganization(name: $name, displayName: $displayName) { - id - name - settingsURL + return lastValueFrom( + requestGraphQL( + gql` + mutation CreateOrganization($name: String!, $displayName: String) { + createOrganization(name: $name, displayName: $displayName) { + id + name + settingsURL + } } - } - `, - { name: args.name, displayName: args.displayName ?? null } - ) - .pipe( + `, + { name: args.name, displayName: args.displayName ?? null } + ).pipe( mergeMap(({ data, errors }) => { if (!data?.createOrganization) { eventLogger.log('NewOrgFailed') @@ -87,7 +87,7 @@ export function createOrganization(args: { return concat(refreshAuthenticatedUser(), [data.createOrganization]) }) ) - .toPromise() + ) } export const REMOVE_USER_FROM_ORGANIZATION_QUERY = gql` diff --git a/client/web/src/org/settings/members/InviteForm.tsx b/client/web/src/org/settings/members/InviteForm.tsx index c4e2aa08ae2..4384442c644 100644 --- a/client/web/src/org/settings/members/InviteForm.tsx +++ b/client/web/src/org/settings/members/InviteForm.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react' import { mdiPlus, mdiEmailOpenOutline, mdiClose } from '@mdi/js' import classNames from 'classnames' +import { lastValueFrom } from 'rxjs' import { map } from 'rxjs/operators' import { asError, createAggregateError, isErrorLike } from '@sourcegraph/common' @@ -204,25 +205,25 @@ function inviteUserToOrganization( username: string, organization: Scalars['ID'] ): Promise { - return requestGraphQL( - gql` - mutation InviteUserToOrganization($organization: ID!, $username: String!) { - inviteUserToOrganization(organization: $organization, username: $username) { - ...InviteUserToOrganizationFields + return lastValueFrom( + requestGraphQL( + gql` + mutation InviteUserToOrganization($organization: ID!, $username: String!) { + inviteUserToOrganization(organization: $organization, username: $username) { + ...InviteUserToOrganizationFields + } } - } - fragment InviteUserToOrganizationFields on InviteUserToOrganizationResult { - sentInvitationEmail - invitationURL + fragment InviteUserToOrganizationFields on InviteUserToOrganizationResult { + sentInvitationEmail + invitationURL + } + `, + { + username, + organization, } - `, - { - username, - organization, - } - ) - .pipe( + ).pipe( map(({ data, errors }) => { if (!data?.inviteUserToOrganization || (errors && errors.length > 0)) { eventLogger.log('InviteOrgMemberFailed') @@ -232,7 +233,7 @@ function inviteUserToOrganization( return data.inviteUserToOrganization }) ) - .toPromise() + ) } function addUserToOrganization(username: string, organization: Scalars['ID']): Promise { diff --git a/client/web/src/regression/util/api.ts b/client/web/src/regression/util/api.ts index 781a5569501..1b71e0f4e7b 100644 --- a/client/web/src/regression/util/api.ts +++ b/client/web/src/regression/util/api.ts @@ -2,7 +2,7 @@ * Provides convenience functions for interacting with the Sourcegraph API from tests. */ -import { zip, timer, concat, throwError, defer, type Observable } from 'rxjs' +import { zip, timer, concat, throwError, defer, type Observable, lastValueFrom } from 'rxjs' import { map, tap, retryWhen, delayWhen, take, mergeMap } from 'rxjs/operators' import { isErrorLike, createAggregateError, logger } from '@sourcegraph/common' @@ -261,40 +261,41 @@ export function getExternalServices( uniqueDisplayName?: string } = {} ): Promise { - return gqlClient - .queryGraphQL( - gql` - query ExternalServicesRegression($first: Int) { - externalServices(first: $first) { - nodes { - ...ExternalServiceNodeFields + return lastValueFrom( + gqlClient + .queryGraphQL( + gql` + query ExternalServicesRegression($first: Int) { + externalServices(first: $first) { + nodes { + ...ExternalServiceNodeFields + } } } - } - fragment ExternalServiceNodeFields on ExternalService { - id - kind - displayName - config - createdAt - updatedAt - warning - } - `, - { first: 100 } - ) - .pipe( - map(dataOrThrowErrors), - map(({ externalServices }) => - externalServices.nodes.filter( - ({ displayName, kind }) => - (options.uniqueDisplayName === undefined || options.uniqueDisplayName === displayName) && - (options.kind === undefined || options.kind === kind) + fragment ExternalServiceNodeFields on ExternalService { + id + kind + displayName + config + createdAt + updatedAt + warning + } + `, + { first: 100 } + ) + .pipe( + map(dataOrThrowErrors), + map(({ externalServices }) => + externalServices.nodes.filter( + ({ displayName, kind }) => + (options.uniqueDisplayName === undefined || options.uniqueDisplayName === displayName) && + (options.kind === undefined || options.kind === kind) + ) ) ) - ) - .toPromise() + ) } export async function updateExternalService( @@ -448,22 +449,23 @@ export async function setTosAccepted(gqlClient: GraphQLClient, userID: Scalars[' * dependency-injected `requestGraphQL`. */ export function currentProductVersion(gqlClient: GraphQLClient): Promise { - return gqlClient - .queryGraphQL( - gql` - query SiteProductVersion { - site { - productVersion + return lastValueFrom( + gqlClient + .queryGraphQL( + gql` + query SiteProductVersion { + site { + productVersion + } } - } - `, - {} - ) - .pipe( - map(dataOrThrowErrors), - map(({ site }) => site.productVersion) - ) - .toPromise() + `, + {} + ) + .pipe( + map(dataOrThrowErrors), + map(({ site }) => site.productVersion) + ) + ) } /** @@ -473,16 +475,16 @@ export function currentProductVersion(gqlClient: GraphQLClient): Promise export function getViewerSettings({ requestGraphQL, }: Pick): Promise { - return requestGraphQL({ - request: viewerSettingsQuery, - variables: {}, - mightContainPrivateInfo: true, - }) - .pipe( + return lastValueFrom( + requestGraphQL({ + request: viewerSettingsQuery, + variables: {}, + mightContainPrivateInfo: true, + }).pipe( map(dataOrThrowErrors), map(data => data.viewerSettings) ) - .toPromise() + ) } /** @@ -631,55 +633,54 @@ export function createUser( * TODO(beyang): remove this after the corresponding API in the main code has been updated to use a * dependency-injected `requestGraphQL`. */ -export async function getUser( +export function getUser( { requestGraphQL }: Pick, username: string ): Promise { - const user = await requestGraphQL({ - request: gql` - query User($username: String!) { - user(username: $username) { - __typename - id - username - displayName - url - settingsURL - avatarURL - viewerCanAdminister - siteAdmin - createdAt - emails { - email - verified - } - organizations { - nodes { - id - displayName - name + return lastValueFrom( + requestGraphQL({ + request: gql` + query User($username: String!) { + user(username: $username) { + __typename + id + username + displayName + url + settingsURL + avatarURL + viewerCanAdminister + siteAdmin + createdAt + emails { + email + verified } - } - settingsCascade { - subjects { - latestSettings { + organizations { + nodes { id - contents + displayName + name + } + } + settingsCascade { + subjects { + latestSettings { + id + contents + } } } } } - } - `, - variables: { username }, - mightContainPrivateInfo: true, - }) - .pipe( + `, + variables: { username }, + mightContainPrivateInfo: true, + }).pipe( map(dataOrThrowErrors), map(({ user }) => user) ) - .toPromise() - return user + ) } /** @@ -748,86 +749,86 @@ export function search( version: SearchVersion, patternType: SearchPatternType ): Promise { - return requestGraphQL({ - request: gql` - query Search($query: String!, $version: SearchVersion!, $patternType: SearchPatternType!) { - search(query: $query, version: $version, patternType: $patternType) { - results { - __typename - limitHit - matchCount - approximateResultCount - missing { - name - } - cloning { - name - } - timedout { - name - } - indexUnavailable - dynamicFilters { - value - label - count - limitHit - kind - } + return lastValueFrom( + requestGraphQL({ + request: gql` + query Search($query: String!, $version: SearchVersion!, $patternType: SearchPatternType!) { + search(query: $query, version: $version, patternType: $patternType) { results { __typename - ... on Repository { - id + limitHit + matchCount + approximateResultCount + missing { name - ...GenericSearchResultFields } - ... on FileMatch { - file { - path - url - commit { - oid + cloning { + name + } + timedout { + name + } + indexUnavailable + dynamicFilters { + value + label + count + limitHit + kind + } + results { + __typename + ... on Repository { + id + name + ...GenericSearchResultFields + } + ... on FileMatch { + file { + path + url + commit { + oid + } + } + repository { + name + url + } + limitHit + symbols { + name + containerName + url + kind + } + lineMatches { + preview + lineNumber + offsetAndLengths } } - repository { - name - url - } - limitHit - symbols { - name - containerName - url - kind - } - lineMatches { - preview - lineNumber - offsetAndLengths + ... on CommitSearchResult { + ...GenericSearchResultFields } } - ... on CommitSearchResult { - ...GenericSearchResultFields - } - } - alert { - title - description - proposedQueries { + alert { + title description - query + proposedQueries { + description + query + } } + elapsedMilliseconds } - elapsedMilliseconds } } - } - ${GenericSearchResultInterfaceFragment} - `, - variables: { query, version, patternType }, - mightContainPrivateInfo: false, - }) - .pipe( + ${GenericSearchResultInterfaceFragment} + `, + variables: { query, version, patternType }, + mightContainPrivateInfo: false, + }).pipe( map(dataOrThrowErrors), map(data => { if (!data.search) { @@ -836,7 +837,7 @@ export function search( return data.search }) ) - .toPromise() + ) } /** diff --git a/client/web/src/regression/util/helpers.ts b/client/web/src/regression/util/helpers.ts index d14d853f2f1..a8aafe08162 100644 --- a/client/web/src/regression/util/helpers.ts +++ b/client/web/src/regression/util/helpers.ts @@ -1,6 +1,6 @@ import * as jsonc from 'jsonc-parser' import { first } from 'lodash' -import { throwError } from 'rxjs' +import { lastValueFrom, throwError } from 'rxjs' import { catchError, map } from 'rxjs/operators' import { Key } from 'ts-key-enum' @@ -131,7 +131,7 @@ export async function createAuthProvider( gqlClient: GraphQLClient, authProvider: GitHubAuthProvider | GitLabAuthProvider | OpenIDConnectAuthProvider | SAMLAuthProvider ): Promise { - const siteConfig = await fetchSiteConfiguration(gqlClient).toPromise() + const siteConfig = await lastValueFrom(fetchSiteConfiguration(gqlClient)) const siteConfigParsed: SiteConfiguration = jsonc.parse(siteConfig.configuration.effectiveContents) const authProviders = siteConfigParsed['auth.providers'] if ( @@ -185,7 +185,7 @@ export async function ensureNewOrganization( { requestGraphQL }: Pick, variables: CreateOrganizationVariables ): Promise<{ destroy: ResourceDestructor; result: CreateOrganizationResult['createOrganization'] }> { - const matchingOrgs = (await fetchAllOrganizations({ requestGraphQL }, { first: 1000 }).toPromise()).nodes.filter( + const matchingOrgs = (await lastValueFrom(fetchAllOrganizations({ requestGraphQL }, { first: 1000 }))).nodes.filter( org => org.name === variables.name ) if (matchingOrgs.length > 1) { @@ -194,7 +194,7 @@ export async function ensureNewOrganization( if (matchingOrgs.length === 1) { await deleteOrganization({ requestGraphQL }, matchingOrgs[0].id).toPromise() } - const createdOrg = await createOrganization({ requestGraphQL }, variables).toPromise() + const createdOrg = await lastValueFrom(createOrganization({ requestGraphQL }, variables)) return { destroy: () => deleteOrganization({ requestGraphQL }, createdOrg.id).toPromise(), result: createdOrg, @@ -239,15 +239,15 @@ export async function editSiteConfig( gqlClient: GraphQLClient, ...edits: ((contents: string) => jsonc.Edit[])[] ): Promise<{ destroy: ResourceDestructor; result: boolean }> { - const origConfig = await fetchSiteConfiguration(gqlClient).toPromise() + const origConfig = await lastValueFrom(fetchSiteConfiguration(gqlClient)) let newContents = origConfig.configuration.effectiveContents for (const editFunc of edits) { newContents = jsonc.applyEdits(newContents, editFunc(newContents)) } return { - result: await updateSiteConfiguration(gqlClient, origConfig.configuration.id, newContents).toPromise(), + result: await lastValueFrom(updateSiteConfiguration(gqlClient, origConfig.configuration.id, newContents)), destroy: async () => { - const site = await fetchSiteConfiguration(gqlClient).toPromise() + const site = await lastValueFrom(fetchSiteConfiguration(gqlClient)) await updateSiteConfiguration( gqlClient, site.configuration.id, diff --git a/client/web/src/repo/RepoContainer.tsx b/client/web/src/repo/RepoContainer.tsx index 65ab0edec1d..42129a3b05e 100644 --- a/client/web/src/repo/RepoContainer.tsx +++ b/client/web/src/repo/RepoContainer.tsx @@ -175,7 +175,7 @@ export const RepoContainer: FC = props => { } if (isCloneInProgressErrorLike(error)) { - return of(asError(error)) + return of(asError(error)) } throw error @@ -185,7 +185,7 @@ export const RepoContainer: FC = props => { ) .pipe( repeatUntil(value => !isCloneInProgressErrorLike(value), { delay: 1000 }), - catchError(error => of(asError(error))) + catchError(error => of(asError(error))) ), [repoName, revision] ) diff --git a/client/web/src/repo/blame/useBlameHunks.ts b/client/web/src/repo/blame/useBlameHunks.ts index 9cabd8abcbb..c7260b67fdb 100644 --- a/client/web/src/repo/blame/useBlameHunks.ts +++ b/client/web/src/repo/blame/useBlameHunks.ts @@ -3,7 +3,7 @@ import { useMemo } from 'react' import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source' import { formatDistanceStrict } from 'date-fns' import { truncate } from 'lodash' -import { Observable, of } from 'rxjs' +import { Observable, lastValueFrom, of } from 'rxjs' import { catchError, map, throttleTime } from 'rxjs/operators' import { type ErrorLike, memoizeObservable } from '@sourcegraph/common' @@ -192,25 +192,25 @@ const fetchBlameViaStreaming = memoizeObservable( ) async function fetchRepositoryData(repoName: string): Promise> { - return requestGraphQL( - gql` - query FirstCommitDate($repo: String!) { - repository(name: $repo) { - firstEverCommit { - author { - date + return lastValueFrom( + requestGraphQL( + gql` + query FirstCommitDate($repo: String!) { + repository(name: $repo) { + firstEverCommit { + author { + date + } + } + externalURLs { + url + serviceKind } } - externalURLs { - url - serviceKind - } } - } - `, - { repo: repoName } - ) - .pipe( + `, + { repo: repoName } + ).pipe( map(dataOrThrowErrors), map(({ repository }) => { const firstCommitDate = repository?.firstEverCommit?.author?.date @@ -220,7 +220,7 @@ async function fetchRepositoryData(repoName: string): Promise { - private toggles = new Subject() + private toggles = new Subject() private subscriptions = new Subscription() /** diff --git a/client/web/src/repo/settings/RepoSettingsArea.tsx b/client/web/src/repo/settings/RepoSettingsArea.tsx index 84aff5f45c7..8823b18474b 100644 --- a/client/web/src/repo/settings/RepoSettingsArea.tsx +++ b/client/web/src/repo/settings/RepoSettingsArea.tsx @@ -7,7 +7,7 @@ import { Routes, Route } from 'react-router-dom' import { of } from 'rxjs' import { catchError } from 'rxjs/operators' -import { asError, type ErrorLike, isErrorLike } from '@sourcegraph/common' +import { asError, isErrorLike } from '@sourcegraph/common' import type { TelemetryV2Props } from '@sourcegraph/shared/src/telemetry' import type { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService' import { useObservable, ErrorMessage } from '@sourcegraph/wildcard' @@ -46,10 +46,7 @@ export const RepoSettingsArea: React.FunctionComponent { const repoName = props.repoName const repoOrError = useObservable( - useMemo( - () => fetchSettingsAreaRepository(repoName).pipe(catchError(error => of(asError(error)))), - [repoName] - ) + useMemo(() => fetchSettingsAreaRepository(repoName).pipe(catchError(error => of(asError(error)))), [repoName]) ) useBreadcrumb(useMemo(() => ({ key: 'settings', element: 'Settings' }), [])) diff --git a/client/web/src/site-admin/SiteAdminConfigurationPage.tsx b/client/web/src/site-admin/SiteAdminConfigurationPage.tsx index 769c5f32956..cf77d12001e 100644 --- a/client/web/src/site-admin/SiteAdminConfigurationPage.tsx +++ b/client/web/src/site-admin/SiteAdminConfigurationPage.tsx @@ -4,7 +4,7 @@ import type { FC } from 'react' import { type ApolloClient, useApolloClient } from '@apollo/client' import classNames from 'classnames' import * as jsonc from 'jsonc-parser' -import { Subject, Subscription } from 'rxjs' +import { lastValueFrom, Subject, Subscription } from 'rxjs' import { delay, mergeMap, retryWhen, tap, timeout } from 'rxjs/operators' import { logger } from '@sourcegraph/common' @@ -468,7 +468,7 @@ class SiteAdminConfigurationContent extends React.Component { let restartToApply = false try { - restartToApply = await updateSiteConfiguration(lastConfigurationID, newContents).toPromise() + restartToApply = await lastValueFrom(updateSiteConfiguration(lastConfigurationID, newContents)) } catch (error) { logger.error(error) this.setState({ @@ -512,7 +512,7 @@ class SiteAdminConfigurationContent extends React.Component { this.setState({ restartToApply }) try { - const site = await fetchSite().toPromise() + const site = await lastValueFrom(fetchSite()) this.setState({ site, diff --git a/client/web/src/site-admin/UserManagement/components/useUserListActions.tsx b/client/web/src/site-admin/UserManagement/components/useUserListActions.tsx index 7478f4d1947..be38856c2b0 100644 --- a/client/web/src/site-admin/UserManagement/components/useUserListActions.tsx +++ b/client/web/src/site-admin/UserManagement/components/useUserListActions.tsx @@ -1,5 +1,7 @@ import React, { useState, useCallback } from 'react' +import { lastValueFrom } from 'rxjs' + import { logger } from '@sourcegraph/common' import { useMutation } from '@sourcegraph/http-client' import { Text } from '@sourcegraph/wildcard' @@ -202,8 +204,7 @@ export function useUserListActions(onEnd: (error?: any) => void): UseUserListAct const handleResetUserPassword = useCallback( ([user]: SiteUser[]) => { if (confirm('Are you sure you want to reset the selected user password?')) { - randomizeUserPassword(user.id) - .toPromise() + lastValueFrom(randomizeUserPassword(user.id)) .then(({ resetPasswordURL, emailSent }) => { if (resetPasswordURL === null || emailSent) { createOnSuccess( diff --git a/client/web/src/tour/components/Tour/utils.tsx b/client/web/src/tour/components/Tour/utils.tsx index 4b729c83711..e2a207535d7 100644 --- a/client/web/src/tour/components/Tour/utils.tsx +++ b/client/web/src/tour/components/Tour/utils.tsx @@ -1,6 +1,6 @@ import isAbsoluteURL from 'is-absolute-url' import { memoize, noop } from 'lodash' -import { type Subscriber, type Subscription, fromEvent, of } from 'rxjs' +import { type Subscriber, type Subscription, fromEvent, of, lastValueFrom } from 'rxjs' import { map } from 'rxjs/operators' import { @@ -77,22 +77,22 @@ const firstMatchMessageHandlers: MessageHandlers = { */ const fetchStreamSuggestions = memoize( (query: string, sourcegraphURL?: string): Promise => - search( - of(query), - { - version: LATEST_VERSION, - patternType: SearchPatternType.standard, - caseSensitive: false, - trace: undefined, - sourcegraphURL, - }, - firstMatchMessageHandlers - ) - .pipe( + lastValueFrom( + search( + of(query), + { + version: LATEST_VERSION, + patternType: SearchPatternType.standard, + caseSensitive: false, + trace: undefined, + sourcegraphURL, + }, + firstMatchMessageHandlers + ).pipe( switchAggregateSearchResults, map(suggestions => suggestions.results) ) - .toPromise(), + ), (query, sourcegraphURL) => `${query}|${sourcegraphURL}` ) diff --git a/client/web/src/user/settings/auth/RemoveExternalAccountModal.tsx b/client/web/src/user/settings/auth/RemoveExternalAccountModal.tsx index 3318f5d300b..4988c5d5bdc 100644 --- a/client/web/src/user/settings/auth/RemoveExternalAccountModal.tsx +++ b/client/web/src/user/settings/auth/RemoveExternalAccountModal.tsx @@ -1,5 +1,7 @@ import React, { useCallback, useState } from 'react' +import { lastValueFrom } from 'rxjs' + import { asError, type ErrorLike } from '@sourcegraph/common' import { gql, dataOrThrowErrors } from '@sourcegraph/http-client' import { Button, Modal, H3, Form } from '@sourcegraph/wildcard' @@ -9,16 +11,18 @@ import type { Scalars, DeleteExternalAccountResult, DeleteExternalAccountVariabl const deleteUserExternalAccount = async (externalAccount: Scalars['ID']): Promise => { dataOrThrowErrors( - await requestGraphQL( - gql` - mutation DeleteExternalAccount($externalAccount: ID!) { - deleteExternalAccount(externalAccount: $externalAccount) { - alwaysNil + await lastValueFrom( + requestGraphQL( + gql` + mutation DeleteExternalAccount($externalAccount: ID!) { + deleteExternalAccount(externalAccount: $externalAccount) { + alwaysNil + } } - } - `, - { externalAccount } - ).toPromise() + `, + { externalAccount } + ) + ) ) } diff --git a/client/web/src/user/settings/backend.tsx b/client/web/src/user/settings/backend.tsx index bfcda808962..1b935541d9d 100644 --- a/client/web/src/user/settings/backend.tsx +++ b/client/web/src/user/settings/backend.tsx @@ -1,4 +1,4 @@ -import { EMPTY, type Observable, Subject } from 'rxjs' +import { EMPTY, type Observable, Subject, lastValueFrom } from 'rxjs' import { bufferTime, catchError, concatMap, map } from 'rxjs/operators' import { createAggregateError } from '@sourcegraph/common' @@ -126,10 +126,11 @@ export const logEventsMutation = gql` ` function sendEvents(events: Event[]): Promise { - return requestGraphQL(logEventsMutation, { - events, - }) - .toPromise() + return lastValueFrom( + requestGraphQL(logEventsMutation, { + events, + }) + ) .then(dataOrThrowErrors) .then(() => {}) } diff --git a/client/web/src/user/settings/emails/AddUserEmailForm.tsx b/client/web/src/user/settings/emails/AddUserEmailForm.tsx index b6a0627e6ac..55629a6580e 100644 --- a/client/web/src/user/settings/emails/AddUserEmailForm.tsx +++ b/client/web/src/user/settings/emails/AddUserEmailForm.tsx @@ -1,6 +1,7 @@ import React, { type FunctionComponent, useMemo, useState } from 'react' import classNames from 'classnames' +import { lastValueFrom } from 'rxjs' import { asError, isErrorLike, type ErrorLike } from '@sourcegraph/common' import { gql, dataOrThrowErrors } from '@sourcegraph/http-client' @@ -55,16 +56,18 @@ export const AddUserEmailForm: FunctionComponent> try { dataOrThrowErrors( - await requestGraphQL( - gql` - mutation AddUserEmail($user: ID!, $email: String!) { - addUserEmail(user: $user, email: $email) { - alwaysNil + await lastValueFrom( + requestGraphQL( + gql` + mutation AddUserEmail($user: ID!, $email: String!) { + addUserEmail(user: $user, email: $email) { + alwaysNil + } } - } - `, - { user: user.id, email: emailState.value } - ).toPromise() + `, + { user: user.id, email: emailState.value } + ) + ) ) eventLogger.log('NewUserEmailAddressAdded') diff --git a/client/web/src/user/settings/emails/SetUserPrimaryEmailForm.tsx b/client/web/src/user/settings/emails/SetUserPrimaryEmailForm.tsx index f71d501c654..384b1227325 100644 --- a/client/web/src/user/settings/emails/SetUserPrimaryEmailForm.tsx +++ b/client/web/src/user/settings/emails/SetUserPrimaryEmailForm.tsx @@ -1,6 +1,7 @@ import React, { useState, type FunctionComponent, useCallback } from 'react' import classNames from 'classnames' +import { lastValueFrom } from 'rxjs' import { asError, type ErrorLike, isErrorLike } from '@sourcegraph/common' import { gql, dataOrThrowErrors } from '@sourcegraph/http-client' @@ -58,16 +59,18 @@ export const SetUserPrimaryEmailForm: FunctionComponent( - gql` - mutation SetUserEmailPrimary($user: ID!, $email: String!) { - setUserEmailPrimary(user: $user, email: $email) { - alwaysNil + await lastValueFrom( + requestGraphQL( + gql` + mutation SetUserEmailPrimary($user: ID!, $email: String!) { + setUserEmailPrimary(user: $user, email: $email) { + alwaysNil + } } - } - `, - { user: user.id, email: primaryEmail } - ).toPromise() + `, + { user: user.id, email: primaryEmail } + ) + ) ) eventLogger.log('UserEmailAddressSetAsPrimary') diff --git a/client/web/src/user/settings/emails/UserEmail.tsx b/client/web/src/user/settings/emails/UserEmail.tsx index 83693122be8..c4ba19161e9 100644 --- a/client/web/src/user/settings/emails/UserEmail.tsx +++ b/client/web/src/user/settings/emails/UserEmail.tsx @@ -1,5 +1,7 @@ import { type FunctionComponent, useState, useCallback } from 'react' +import { lastValueFrom } from 'rxjs' + import { asError, type ErrorLike } from '@sourcegraph/common' import { dataOrThrowErrors, gql } from '@sourcegraph/http-client' import { Badge, Button, screenReaderAnnounce } from '@sourcegraph/wildcard' @@ -35,16 +37,18 @@ export const resendVerificationEmail = async ( ): Promise => { try { dataOrThrowErrors( - await requestGraphQL( - gql` - mutation ResendVerificationEmail($userID: ID!, $email: String!) { - resendVerificationEmail(user: $userID, email: $email) { - alwaysNil + await lastValueFrom( + requestGraphQL( + gql` + mutation ResendVerificationEmail($userID: ID!, $email: String!) { + resendVerificationEmail(user: $userID, email: $email) { + alwaysNil + } } - } - `, - { userID, email } - ).toPromise() + `, + { userID, email } + ) + ) ) eventLogger.log('UserEmailAddressVerificationResent') @@ -79,16 +83,18 @@ export const UserEmail: FunctionComponent> = ({ try { dataOrThrowErrors( - await requestGraphQL( - gql` - mutation RemoveUserEmail($user: ID!, $email: String!) { - removeUserEmail(user: $user, email: $email) { - alwaysNil + await lastValueFrom( + requestGraphQL( + gql` + mutation RemoveUserEmail($user: ID!, $email: String!) { + removeUserEmail(user: $user, email: $email) { + alwaysNil + } } - } - `, - { user, email } - ).toPromise() + `, + { user, email } + ) + ) ) setIsLoading(false) @@ -108,16 +114,18 @@ export const UserEmail: FunctionComponent> = ({ try { dataOrThrowErrors( - await requestGraphQL( - gql` - mutation SetUserEmailVerified($user: ID!, $email: String!, $verified: Boolean!) { - setUserEmailVerified(user: $user, email: $email, verified: $verified) { - alwaysNil + await lastValueFrom( + requestGraphQL( + gql` + mutation SetUserEmailVerified($user: ID!, $email: String!, $verified: Boolean!) { + setUserEmailVerified(user: $user, email: $email, verified: $verified) { + alwaysNil + } } - } - `, - { user, email, verified } - ).toPromise() + `, + { user, email, verified } + ) + ) ) setIsLoading(false) diff --git a/client/web/src/user/settings/emails/UserSettingsEmailsPage.tsx b/client/web/src/user/settings/emails/UserSettingsEmailsPage.tsx index 2655be2fc5c..879ff7943be 100644 --- a/client/web/src/user/settings/emails/UserSettingsEmailsPage.tsx +++ b/client/web/src/user/settings/emails/UserSettingsEmailsPage.tsx @@ -1,6 +1,7 @@ import React, { type FunctionComponent, useEffect, useState, useCallback } from 'react' import classNames from 'classnames' +import { lastValueFrom } from 'rxjs' import { asError, type ErrorLike, isErrorLike } from '@sourcegraph/common' import { gql, dataOrThrowErrors, useQuery } from '@sourcegraph/http-client' @@ -142,27 +143,29 @@ export const UserSettingsEmailsPage: FunctionComponent { return dataOrThrowErrors( - await requestGraphQL( - gql` - fragment UserEmail on UserEmail { - email - isPrimary - verified - verificationPending - viewerCanManuallyVerify - } - query UserEmails($user: ID!) { - node(id: $user) { - ... on User { - __typename - emails { - ...UserEmail + await lastValueFrom( + requestGraphQL( + gql` + fragment UserEmail on UserEmail { + email + isPrimary + verified + verificationPending + viewerCanManuallyVerify + } + query UserEmails($user: ID!) { + node(id: $user) { + ... on User { + __typename + emails { + ...UserEmail + } } } } - } - `, - { user: userID } - ).toPromise() + `, + { user: userID } + ) + ) ) } diff --git a/package.json b/package.json index 07d26f7b317..ba3039986b9 100644 --- a/package.json +++ b/package.json @@ -396,7 +396,7 @@ "recharts": "^1.8.5", "regexpp": "^3.1.0", "resize-observer-polyfill": "^1.5.1", - "rxjs": "^6.6.3", + "rxjs": "^7.8.1", "semver": "^7.3.2", "stream-http": "^3.2.0", "stream-json": "^1.7.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87cf53d056b..465f4d980c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -407,8 +407,8 @@ importers: specifier: ^1.5.1 version: 1.5.1 rxjs: - specifier: ^6.6.3 - version: 6.6.7 + specifier: ^7.8.1 + version: 7.8.1 semver: specifier: ^7.3.2 version: 7.6.0 @@ -22898,6 +22898,7 @@ packages: engines: {npm: '>=2.0.0'} dependencies: tslib: 2.1.0 + dev: true /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}