diff --git a/src/common/k8s-api/kube-object.store.ts b/src/common/k8s-api/kube-object.store.ts index ca4e1d9000..855e1a1315 100644 --- a/src/common/k8s-api/kube-object.store.ts +++ b/src/common/k8s-api/kube-object.store.ts @@ -7,7 +7,7 @@ import type { ClusterContext } from "./cluster-context"; import { action, computed, makeObservable, observable, reaction, when } from "mobx"; import type { Disposer } from "../utils"; -import { autoBind, includes, isRequestError, noop, rejectPromiseBy } from "../utils"; +import { waitUntilDefined, autoBind, includes, isRequestError, noop, rejectPromiseBy } from "../utils"; import type { KubeJsonApiDataFor, KubeObject } from "./kube-object"; import { KubeStatus } from "./kube-object"; import type { IKubeWatchEvent } from "./kube-watch-event"; @@ -114,7 +114,7 @@ export abstract class KubeObjectStore< this.bindWatchEventsUpdater(); } - get context(): ClusterContext { + get context(): ClusterContext | undefined { return KubeObjectStore.defaultContext.get(); } @@ -259,8 +259,9 @@ export abstract class KubeObjectStore< @action async loadAll({ namespaces, merge = true, reqInit, onLoadFailure }: KubeObjectStoreLoadAllParams = {}): Promise { - await this.contextReady; - namespaces ??= this.context.contextNamespaces; + const context = await waitUntilDefined(() => this.context); + + namespaces ??= context.contextNamespaces; this.isLoading = true; try { @@ -427,11 +428,17 @@ export abstract class KubeObjectStore< subscribe({ onLoadFailure, abortController = new AbortController() }: KubeObjectStoreSubscribeParams = {}): Disposer { if (this.api.isNamespaced) { - Promise.race([rejectPromiseBy(abortController.signal), Promise.all([this.contextReady, this.namespacesReady])]) - .then(() => { + Promise.race([ + rejectPromiseBy(abortController.signal), + Promise.all([ + waitUntilDefined(() => this.context), + this.namespacesReady, + ] as const), + ]) + .then(([context]) => { assert(this.loadedNamespaces); - if (this.context.cluster?.isGlobalWatchEnabled && this.loadedNamespaces.length === 0) { + if (context.cluster?.isGlobalWatchEnabled && this.loadedNamespaces.length === 0) { return this.watchNamespace("", abortController, { onLoadFailure }); } diff --git a/src/common/utils/reject-promise.ts b/src/common/utils/reject-promise.ts index 2f6c31ca2a..0faa639e8b 100644 --- a/src/common/utils/reject-promise.ts +++ b/src/common/utils/reject-promise.ts @@ -11,7 +11,7 @@ import "abort-controller/polyfill"; * Useful for `Promise.race()` applications. * @param signal The AbortController's signal to reject with */ -export function rejectPromiseBy(signal: AbortSignal): Promise { +export function rejectPromiseBy(signal: AbortSignal): Promise { return new Promise((_, reject) => { signal.addEventListener("abort", reject); }); diff --git a/src/common/utils/sync-box/create-sync-box.injectable.ts b/src/common/utils/sync-box/create-sync-box.injectable.ts index 2cf3de6a69..4b15fdb79f 100644 --- a/src/common/utils/sync-box/create-sync-box.injectable.ts +++ b/src/common/utils/sync-box/create-sync-box.injectable.ts @@ -25,7 +25,11 @@ const createSyncBoxInjectable = getInjectable({ return { id, - value: computed(() => state.get()), + /** + * SAFETY: we unconditionally set the value above and only allow `TData` with the `.set` + * function so this is always `TData`. + */ + value: computed(() => state.get() as TData), set: (value) => { state.set(value); diff --git a/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame.injectable.ts b/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame.injectable.ts index 2749b23253..e364b8cb0e 100644 --- a/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame.injectable.ts +++ b/src/main/start-main-application/lens-window/current-cluster-frame/current-cluster-frame.injectable.ts @@ -17,6 +17,10 @@ const currentClusterFrameInjectable = getInjectable({ return computed(() => { const clusterId = currentClusterFrameState.get(); + if (!clusterId) { + return undefined; + } + return clusterFrames.get(clusterId); }); }, diff --git a/src/renderer/components/+namespaces/store.ts b/src/renderer/components/+namespaces/store.ts index 1a242839fa..1b2342facf 100644 --- a/src/renderer/components/+namespaces/store.ts +++ b/src/renderer/components/+namespaces/store.ts @@ -114,7 +114,7 @@ export class NamespaceStore extends KubeObjectStore { * if user has given static list of namespaces let's not start watches * because watch adds stuff that's not wanted or will just fail */ - if (this.context.cluster.accessibleNamespaces.length > 0) { + if ((this.context?.cluster.accessibleNamespaces.length ?? 0) > 0) { return noop; } diff --git a/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts b/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts index 39d63e5d46..d49ea9dd9c 100644 --- a/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts +++ b/src/renderer/utils/sync-box/provide-initial-values-for-sync-boxes.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { beforeFrameStartsInjectionToken } from "../../before-frame-starts/before-frame-starts-injection-token"; import syncBoxInitialValueChannelInjectable from "../../../common/utils/sync-box/sync-box-initial-value-channel.injectable"; -import syncBoxStateInjectable from "../../../common/utils/sync-box/sync-box-state.injectable"; +import createSyncBoxStateInjectable from "../../../common/utils/sync-box/sync-box-state.injectable"; import { requestFromChannelInjectionToken } from "../../../common/utils/channel/request-from-channel-injection-token"; const provideInitialValuesForSyncBoxesInjectable = getInjectable({ @@ -14,7 +14,7 @@ const provideInitialValuesForSyncBoxesInjectable = getInjectable({ instantiate: (di) => { const requestFromChannel = di.inject(requestFromChannelInjectionToken); const syncBoxInitialValueChannel = di.inject(syncBoxInitialValueChannelInjectable); - const setSyncBoxState = (id: string, state: any) => di.inject(syncBoxStateInjectable, id).set(state); + const setSyncBoxState = (id: string, state: any) => di.inject(createSyncBoxStateInjectable, id).set(state); return { run: async () => {