1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/packages/technical-features/messaging/agnostic/src/features/actual/computed-channel/computed-channel.injectable.ts
Janne Savolainen e716836a4e
Extract messaging to NPM package
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com>

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
2023-03-21 09:28:16 +02:00

111 lines
3.3 KiB
TypeScript

import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import {
_getGlobalState,
computed,
IComputedValue,
observable,
onBecomeObserved,
onBecomeUnobserved,
runInAction,
} from "mobx";
import type { MessageChannel } from "../message/message-channel-listener-injection-token";
import { getMessageChannelListenerInjectable } from "../message/message-channel-listener-injection-token";
import { sendMessageToChannelInjectionToken } from "../message/message-to-channel-injection-token";
import type { JsonPrimitive } from "type-fest";
import { computedChannelAdministrationChannel } from "./computed-channel-administration-channel.injectable";
export type JsonifiableObject = { [Key in string]?: Jsonifiable } | { toJSON: () => Jsonifiable };
export type JsonifiableArray = readonly Jsonifiable[];
export type Jsonifiable = JsonPrimitive | JsonifiableObject | JsonifiableArray;
export type ComputedChannelFactory = <T>(
channel: MessageChannel<T>,
pendingValue: T,
) => IComputedValue<T>;
export const computedChannelInjectionToken = getInjectionToken<ComputedChannelFactory>({
id: "computed-channel-injection-token",
});
export type ChannelObserver<T extends Jsonifiable> = {
channel: MessageChannel<T>;
observer: IComputedValue<T>;
};
export const computedChannelObserverInjectionToken = getInjectionToken<
ChannelObserver<Jsonifiable>
>({
id: "computed-channel-observer",
});
const computedChannelInjectable = getInjectable({
id: "computed-channel",
instantiate: (di) => {
const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken);
return ((channel, pendingValue) => {
const observableValue = observable.box(pendingValue);
const computedValue = computed(() => {
const { trackingDerivation } = _getGlobalState();
const contextIsReactive = !!trackingDerivation;
if (!contextIsReactive) {
throw new Error(
`Tried to access value of computed channel "${channel.id}" outside of reactive context. This is not possible, as the value is acquired asynchronously sometime *after* being observed. Not respecting that, the value could be stale.`,
);
}
return observableValue.get();
});
const valueReceiverInjectable = getMessageChannelListenerInjectable({
id: `computed-channel-value-receiver-for-${channel.id}`,
channel,
getHandler: () => (message) => {
runInAction(() => {
observableValue.set(message);
});
},
});
runInAction(() => {
di.register(valueReceiverInjectable);
});
onBecomeObserved(computedValue, () => {
runInAction(() => {
observableValue.set(pendingValue);
});
sendMessageToChannel(computedChannelAdministrationChannel, {
channelId: channel.id,
status: "became-observed",
});
});
onBecomeUnobserved(computedValue, () => {
runInAction(() => {
observableValue.set(pendingValue);
});
sendMessageToChannel(computedChannelAdministrationChannel, {
channelId: channel.id,
status: "became-unobserved",
});
});
return computedValue;
}) as ComputedChannelFactory;
},
injectionToken: computedChannelInjectionToken,
});
export default computedChannelInjectable;