2024-05-27 22:59:58 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
// WaveObjectStore
|
|
|
|
|
2024-05-28 21:12:28 +02:00
|
|
|
import { Call as $Call, Events } from "@wailsio/runtime";
|
2024-05-27 22:59:58 +02:00
|
|
|
import * as jotai from "jotai";
|
2024-05-28 21:12:28 +02:00
|
|
|
import * as React from "react";
|
|
|
|
import { atoms, globalStore } from "./global";
|
2024-05-27 22:59:58 +02:00
|
|
|
|
|
|
|
type WaveObjectDataItemType<T extends WaveObj> = {
|
|
|
|
value: T;
|
|
|
|
loading: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
type WaveObjectValue<T extends WaveObj> = {
|
|
|
|
pendingPromise: Promise<T>;
|
|
|
|
dataAtom: jotai.PrimitiveAtom<WaveObjectDataItemType<T>>;
|
|
|
|
refCount: number;
|
|
|
|
holdTime: number;
|
|
|
|
};
|
|
|
|
|
|
|
|
function splitORef(oref: string): [string, string] {
|
|
|
|
let parts = oref.split(":");
|
|
|
|
if (parts.length != 2) {
|
|
|
|
throw new Error("invalid oref");
|
|
|
|
}
|
|
|
|
return [parts[0], parts[1]];
|
|
|
|
}
|
|
|
|
|
|
|
|
function isBlank(str: string): boolean {
|
|
|
|
return str == null || str == "";
|
|
|
|
}
|
|
|
|
|
|
|
|
function isBlankNum(num: number): boolean {
|
|
|
|
return num == null || isNaN(num) || num == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isValidWaveObj(val: WaveObj): boolean {
|
|
|
|
if (val == null) {
|
|
|
|
return false;
|
|
|
|
}
|
2024-05-27 23:31:12 +02:00
|
|
|
if (isBlank(val.otype) || isBlank(val.oid) || isBlankNum(val.version)) {
|
2024-05-27 22:59:58 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function makeORef(otype: string, oid: string): string {
|
|
|
|
if (isBlank(otype) || isBlank(oid)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return `${otype}:${oid}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function GetObject<T>(oref: string): Promise<T> {
|
|
|
|
let prtn = $Call.ByName(
|
|
|
|
"github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService.GetObject",
|
|
|
|
oref
|
|
|
|
);
|
|
|
|
return prtn;
|
|
|
|
}
|
|
|
|
|
|
|
|
const waveObjectValueCache = new Map<string, WaveObjectValue<any>>();
|
|
|
|
|
|
|
|
function clearWaveObjectCache() {
|
|
|
|
waveObjectValueCache.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaultHoldTime = 5000; // 5-seconds
|
|
|
|
|
|
|
|
function createWaveValueObject<T extends WaveObj>(oref: string, shouldFetch: boolean): WaveObjectValue<T> {
|
|
|
|
const wov = { pendingPromise: null, dataAtom: null, refCount: 0, holdTime: Date.now() + 5000 };
|
|
|
|
wov.dataAtom = jotai.atom({ value: null, loading: true });
|
|
|
|
if (!shouldFetch) {
|
|
|
|
return wov;
|
|
|
|
}
|
|
|
|
let startTs = Date.now();
|
|
|
|
let localPromise = GetObject<T>(oref);
|
|
|
|
wov.pendingPromise = localPromise;
|
|
|
|
localPromise.then((val) => {
|
|
|
|
if (wov.pendingPromise != localPromise) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const [otype, oid] = splitORef(oref);
|
|
|
|
if (val != null) {
|
|
|
|
if (val["otype"] != otype) {
|
|
|
|
throw new Error("GetObject returned wrong type");
|
|
|
|
}
|
|
|
|
if (val["oid"] != oid) {
|
|
|
|
throw new Error("GetObject returned wrong id");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wov.pendingPromise = null;
|
|
|
|
globalStore.set(wov.dataAtom, { value: val, loading: false });
|
2024-05-28 00:44:57 +02:00
|
|
|
console.log("WaveObj resolved", oref, Date.now() - startTs + "ms");
|
2024-05-27 22:59:58 +02:00
|
|
|
});
|
|
|
|
return wov;
|
|
|
|
}
|
|
|
|
|
|
|
|
function loadAndPinWaveObject<T>(oref: string): Promise<T> {
|
|
|
|
let wov = waveObjectValueCache.get(oref);
|
|
|
|
if (wov == null) {
|
|
|
|
wov = createWaveValueObject(oref, true);
|
|
|
|
waveObjectValueCache.set(oref, wov);
|
|
|
|
}
|
|
|
|
wov.refCount++;
|
|
|
|
if (wov.pendingPromise == null) {
|
|
|
|
const dataValue = globalStore.get(wov.dataAtom);
|
|
|
|
return Promise.resolve(dataValue.value);
|
|
|
|
}
|
|
|
|
return wov.pendingPromise;
|
|
|
|
}
|
|
|
|
|
2024-05-28 00:44:57 +02:00
|
|
|
function useWaveObjectValueWithSuspense<T>(oref: string): T {
|
|
|
|
let wov = waveObjectValueCache.get(oref);
|
|
|
|
if (wov == null) {
|
|
|
|
wov = createWaveValueObject(oref, true);
|
|
|
|
waveObjectValueCache.set(oref, wov);
|
|
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
|
|
wov.refCount++;
|
|
|
|
return () => {
|
|
|
|
wov.refCount--;
|
|
|
|
};
|
|
|
|
}, [oref]);
|
|
|
|
const dataValue = jotai.useAtomValue(wov.dataAtom);
|
|
|
|
if (dataValue.loading) {
|
|
|
|
throw wov.pendingPromise;
|
|
|
|
}
|
|
|
|
return dataValue.value;
|
|
|
|
}
|
|
|
|
|
2024-05-27 22:59:58 +02:00
|
|
|
function useWaveObjectValue<T>(oref: string): [T, boolean] {
|
|
|
|
let wov = waveObjectValueCache.get(oref);
|
|
|
|
if (wov == null) {
|
|
|
|
wov = createWaveValueObject(oref, true);
|
|
|
|
waveObjectValueCache.set(oref, wov);
|
|
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
|
|
wov.refCount++;
|
|
|
|
return () => {
|
|
|
|
wov.refCount--;
|
|
|
|
};
|
|
|
|
}, [oref]);
|
|
|
|
const atomVal = jotai.useAtomValue(wov.dataAtom);
|
|
|
|
return [atomVal.value, atomVal.loading];
|
|
|
|
}
|
|
|
|
|
2024-05-29 00:41:03 +02:00
|
|
|
function useWaveObject<T extends WaveObj>(oref: string): [T, boolean, (T) => void] {
|
2024-05-27 22:59:58 +02:00
|
|
|
let wov = waveObjectValueCache.get(oref);
|
|
|
|
if (wov == null) {
|
|
|
|
wov = createWaveValueObject(oref, true);
|
|
|
|
waveObjectValueCache.set(oref, wov);
|
|
|
|
}
|
|
|
|
React.useEffect(() => {
|
|
|
|
wov.refCount++;
|
|
|
|
return () => {
|
|
|
|
wov.refCount--;
|
|
|
|
};
|
|
|
|
}, [oref]);
|
|
|
|
const [atomVal, setAtomVal] = jotai.useAtom(wov.dataAtom);
|
|
|
|
const simpleSet = (val: T) => {
|
|
|
|
setAtomVal({ value: val, loading: false });
|
2024-05-29 00:41:03 +02:00
|
|
|
UpdateObject(val, false);
|
2024-05-27 22:59:58 +02:00
|
|
|
};
|
|
|
|
return [atomVal.value, atomVal.loading, simpleSet];
|
|
|
|
}
|
|
|
|
|
2024-05-27 23:31:12 +02:00
|
|
|
function updateWaveObject(update: WaveObjUpdate) {
|
|
|
|
if (update == null) {
|
2024-05-27 22:59:58 +02:00
|
|
|
return;
|
|
|
|
}
|
2024-05-27 23:31:12 +02:00
|
|
|
let oref = makeORef(update.otype, update.oid);
|
2024-05-27 22:59:58 +02:00
|
|
|
let wov = waveObjectValueCache.get(oref);
|
|
|
|
if (wov == null) {
|
|
|
|
wov = createWaveValueObject(oref, false);
|
|
|
|
waveObjectValueCache.set(oref, wov);
|
|
|
|
}
|
2024-05-27 23:31:12 +02:00
|
|
|
if (update.updatetype == "delete") {
|
2024-05-28 01:33:31 +02:00
|
|
|
console.log("WaveObj deleted", oref);
|
2024-05-27 22:59:58 +02:00
|
|
|
globalStore.set(wov.dataAtom, { value: null, loading: false });
|
|
|
|
} else {
|
2024-05-27 23:31:12 +02:00
|
|
|
if (!isValidWaveObj(update.obj)) {
|
|
|
|
console.log("invalid wave object update", update);
|
|
|
|
return;
|
|
|
|
}
|
2024-05-27 22:59:58 +02:00
|
|
|
let curValue: WaveObjectDataItemType<WaveObj> = globalStore.get(wov.dataAtom);
|
2024-05-27 23:31:12 +02:00
|
|
|
if (curValue.value != null && curValue.value.version >= update.obj.version) {
|
2024-05-27 22:59:58 +02:00
|
|
|
return;
|
|
|
|
}
|
2024-05-28 01:33:31 +02:00
|
|
|
console.log("WaveObj updated", oref);
|
2024-05-27 23:31:12 +02:00
|
|
|
globalStore.set(wov.dataAtom, { value: update.obj, loading: false });
|
2024-05-27 22:59:58 +02:00
|
|
|
}
|
|
|
|
wov.holdTime = Date.now() + defaultHoldTime;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-27 23:31:12 +02:00
|
|
|
function updateWaveObjects(vals: WaveObjUpdate[]) {
|
2024-05-27 22:59:58 +02:00
|
|
|
for (let val of vals) {
|
|
|
|
updateWaveObject(val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanWaveObjectCache() {
|
|
|
|
let now = Date.now();
|
|
|
|
for (let [oref, wov] of waveObjectValueCache) {
|
|
|
|
if (wov.refCount == 0 && wov.holdTime < now) {
|
|
|
|
waveObjectValueCache.delete(oref);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Events.On("waveobj:update", (event: any) => {
|
2024-05-27 23:31:12 +02:00
|
|
|
const data: WaveObjUpdate[] = event?.data;
|
2024-05-27 22:59:58 +02:00
|
|
|
if (data == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!Array.isArray(data)) {
|
|
|
|
console.log("invalid waveobj:update, not an array", data);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (data.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
updateWaveObjects(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
function wrapObjectServiceCall<T>(fnName: string, ...args: any[]): Promise<T> {
|
|
|
|
const uiContext = globalStore.get(atoms.uiContext);
|
2024-05-28 01:33:31 +02:00
|
|
|
const startTs = Date.now();
|
2024-05-27 22:59:58 +02:00
|
|
|
let prtn = $Call.ByName(
|
|
|
|
"github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService." + fnName,
|
|
|
|
uiContext,
|
|
|
|
...args
|
|
|
|
);
|
|
|
|
prtn = prtn.then((val) => {
|
2024-05-28 01:33:31 +02:00
|
|
|
console.log("Call", fnName, Date.now() - startTs + "ms");
|
2024-05-27 22:59:58 +02:00
|
|
|
if (val.updates) {
|
|
|
|
updateWaveObjects(val.updates);
|
|
|
|
}
|
|
|
|
return val;
|
|
|
|
});
|
|
|
|
return prtn;
|
|
|
|
}
|
|
|
|
|
2024-05-29 00:41:03 +02:00
|
|
|
// gets the value of a WaveObject from the cache.
|
|
|
|
// should provide getFn if it is available (e.g. inside of a jotai atom)
|
|
|
|
// otherwise it will use the globalStore.get function
|
|
|
|
function getObjectValue<T>(oref: string, getFn?: jotai.Getter): T {
|
2024-05-28 00:44:57 +02:00
|
|
|
let wov = waveObjectValueCache.get(oref);
|
|
|
|
if (wov == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2024-05-29 00:41:03 +02:00
|
|
|
if (getFn == null) {
|
|
|
|
getFn = globalStore.get;
|
|
|
|
}
|
2024-05-28 00:44:57 +02:00
|
|
|
const atomVal = getFn(wov.dataAtom);
|
|
|
|
return atomVal.value;
|
|
|
|
}
|
|
|
|
|
2024-05-29 00:41:03 +02:00
|
|
|
// sets the value of a WaveObject in the cache.
|
|
|
|
// should provide setFn if it is available (e.g. inside of a jotai atom)
|
|
|
|
// otherwise it will use the globalStore.set function
|
|
|
|
function setObjectValue<T>(value: WaveObj, setFn?: jotai.Setter, pushToServer?: boolean) {
|
|
|
|
const oref = makeORef(value.otype, value.oid);
|
|
|
|
let wov = waveObjectValueCache.get(oref);
|
|
|
|
if (wov == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (setFn == null) {
|
|
|
|
setFn = globalStore.set;
|
|
|
|
}
|
|
|
|
setFn(wov.dataAtom, { value: value, loading: false });
|
|
|
|
if (pushToServer) {
|
|
|
|
UpdateObject(value, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-28 01:33:31 +02:00
|
|
|
export function AddTabToWorkspace(tabName: string, activateTab: boolean): Promise<{ tabId: string }> {
|
2024-05-27 22:59:58 +02:00
|
|
|
return wrapObjectServiceCall("AddTabToWorkspace", tabName, activateTab);
|
|
|
|
}
|
|
|
|
|
2024-05-28 01:33:31 +02:00
|
|
|
export function SetActiveTab(tabId: string): Promise<void> {
|
2024-05-27 23:31:12 +02:00
|
|
|
return wrapObjectServiceCall("SetActiveTab", tabId);
|
|
|
|
}
|
|
|
|
|
2024-05-28 01:33:31 +02:00
|
|
|
export function CreateBlock(blockDef: BlockDef, rtOpts: RuntimeOpts): Promise<{ blockId: string }> {
|
2024-05-28 00:44:57 +02:00
|
|
|
return wrapObjectServiceCall("CreateBlock", blockDef, rtOpts);
|
2024-05-27 22:59:58 +02:00
|
|
|
}
|
|
|
|
|
2024-05-28 01:33:31 +02:00
|
|
|
export function DeleteBlock(blockId: string): Promise<void> {
|
|
|
|
return wrapObjectServiceCall("DeleteBlock", blockId);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function CloseTab(tabId: string): Promise<void> {
|
|
|
|
return wrapObjectServiceCall("CloseTab", tabId);
|
|
|
|
}
|
|
|
|
|
2024-05-29 00:41:03 +02:00
|
|
|
export function UpdateObjectMeta(blockId: string, meta: MetadataType): Promise<void> {
|
|
|
|
return wrapObjectServiceCall("UpdateObjectMeta", blockId, meta);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function UpdateObject(waveObj: WaveObj, returnUpdates: boolean): Promise<WaveObjUpdate[]> {
|
|
|
|
return wrapObjectServiceCall("UpdateObject", waveObj, returnUpdates);
|
2024-05-28 21:18:26 +02:00
|
|
|
}
|
|
|
|
|
2024-05-27 22:59:58 +02:00
|
|
|
export {
|
2024-05-28 21:12:28 +02:00
|
|
|
cleanWaveObjectCache,
|
|
|
|
clearWaveObjectCache,
|
2024-05-29 00:41:03 +02:00
|
|
|
getObjectValue,
|
2024-05-28 21:12:28 +02:00
|
|
|
loadAndPinWaveObject,
|
2024-05-27 22:59:58 +02:00
|
|
|
makeORef,
|
2024-05-29 00:41:03 +02:00
|
|
|
setObjectValue,
|
2024-05-28 21:12:28 +02:00
|
|
|
updateWaveObject,
|
|
|
|
updateWaveObjects,
|
2024-05-27 22:59:58 +02:00
|
|
|
useWaveObject,
|
|
|
|
useWaveObjectValue,
|
2024-05-28 00:44:57 +02:00
|
|
|
useWaveObjectValueWithSuspense,
|
2024-05-27 22:59:58 +02:00
|
|
|
};
|