checkpoint on new objectservice

This commit is contained in:
sawka 2024-05-26 23:05:11 -07:00
parent b1aaba2a37
commit 95ce1cc86d
9 changed files with 387 additions and 47 deletions

View File

@ -0,0 +1,26 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package main
import (
"fmt"
"reflect"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
func main() {
tsTypesMap := make(map[reflect.Type]string)
var waveObj waveobj.WaveObj
waveobj.GenerateTSType(reflect.TypeOf(waveobj.ORef{}), tsTypesMap)
waveobj.GenerateTSType(reflect.TypeOf(&waveObj).Elem(), tsTypesMap)
for _, rtype := range wstore.AllWaveObjTypes() {
waveobj.GenerateTSType(rtype, tsTypesMap)
}
for _, ts := range tsTypesMap {
fmt.Print(ts)
fmt.Print("\n")
}
}

View File

@ -1,15 +1,18 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import * as React from "react";
import * as jotai from "jotai";
import { atomFamily } from "jotai/utils";
import * as jotaiUtils from "jotai/utils";
import { v4 as uuidv4 } from "uuid";
import * as rxjs from "rxjs";
import type { WailsEvent } from "@wailsio/runtime/types/events";
import { Events } from "@wailsio/runtime";
import { produce } from "immer";
import { BlockService } from "@/bindings/blockservice";
import { ObjectService } from "@/bindings/objectservice";
import * as wstore from "@/gopkg/wstore";
import { Call as $Call } from "@wailsio/runtime";
const globalStore = jotai.createStore();
@ -105,4 +108,93 @@ function removeBlockFromTab(tabId: string, blockId: string) {
BlockService.CloseBlock(blockId);
}
export { globalStore, atoms, getBlockSubject, addBlockIdToTab, blockDataMap, useBlockAtom, removeBlockFromTab };
function GetObject(oref: string): Promise<any> {
let prtn = $Call.ByName(
"github.com/wavetermdev/thenextwave/pkg/service/objectservice.ObjectService.GetObject",
oref
);
return prtn;
}
type WaveObjectHookData = {
oref: string;
};
type WaveObjectValue<T> = {
pendingPromise: Promise<any>;
value: T;
loading: boolean;
};
const waveObjectValueCache = new Map<string, WaveObjectValue<any>>();
let waveObjectAtomCache = new WeakMap<WaveObjectHookData, jotai.Atom<any>>();
function clearWaveObjectCache() {
waveObjectValueCache.clear();
waveObjectAtomCache = new WeakMap<WaveObjectHookData, jotai.Atom<any>>();
}
function createWaveObjectAtom<T>(oref: string): jotai.Atom<[T, boolean]> {
let cacheVal: WaveObjectValue<T> = waveObjectValueCache.get(oref);
if (cacheVal == null) {
cacheVal = { pendingPromise: null, value: null, loading: true };
cacheVal.pendingPromise = GetObject(oref).then((val) => {
cacheVal.value = val;
cacheVal.loading = false;
cacheVal.pendingPromise = null;
});
waveObjectValueCache.set(oref, cacheVal);
}
return jotai.atom(
(get) => {
return [cacheVal.value, cacheVal.loading];
},
(get, set, newVal: T) => {
cacheVal.value = newVal;
}
);
}
function useWaveObjectValue<T>(oref: string): [T, boolean] {
const objRef = React.useRef<WaveObjectHookData>(null);
if (objRef.current == null) {
objRef.current = { oref: oref };
}
const objHookData = objRef.current;
let objAtom = waveObjectAtomCache.get(objHookData);
if (objAtom == null) {
objAtom = createWaveObjectAtom(oref);
waveObjectAtomCache.set(objHookData, objAtom);
}
const atomVal = jotai.useAtomValue(objAtom);
return [atomVal[0], atomVal[1]];
}
function useWaveObject<T>(oref: string): [T, boolean, (T) => void] {
const objRef = React.useRef<WaveObjectHookData>(null);
if (objRef.current == null) {
objRef.current = { oref: oref };
}
const objHookData = objRef.current;
let objAtom = waveObjectAtomCache.get(objHookData);
if (objAtom == null) {
objAtom = createWaveObjectAtom(oref);
waveObjectAtomCache.set(objHookData, objAtom);
}
const [atomVal, setAtomVal] = jotai.useAtom(objAtom);
return [atomVal[0], atomVal[1], setAtomVal];
}
export {
globalStore,
atoms,
getBlockSubject,
addBlockIdToTab,
blockDataMap,
useBlockAtom,
removeBlockFromTab,
GetObject,
useWaveObject,
useWaveObjectValue,
clearWaveObjectCache,
};

View File

@ -1,6 +1,97 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
declare global {}
declare global {
type ORef = {
otype: string;
oid: string;
};
type Block = {
otype: string;
oid: string;
version: number;
blockdef: BlockDef;
controller: string;
view: string;
meta?: { [key: string]: any };
runtimeopts?: RuntimeOpts;
};
type BlockDef = {
controller: string;
view?: string;
files?: { [key: string]: FileDef };
meta?: { [key: string]: any };
};
type FileDef = {
filetype?: string;
path?: string;
url?: string;
content?: string;
meta?: { [key: string]: any };
};
type TermSize = {
rows: number;
cols: number;
};
type Client = {
otype: string;
oid: string;
version: number;
mainwindowid: string;
};
type Tab = {
otype: string;
oid: string;
version: number;
name: string;
blockids: string[];
};
type Point = {
x: number;
y: number;
};
type WinSize = {
width: number;
height: number;
};
type Workspace = {
otype: string;
oid: string;
version: number;
name: string;
tabids: string[];
};
type RuntimeOpts = {
termsize?: TermSize;
winsize?: WinSize;
};
type WaveObj = {
otype: string;
oid: string;
};
type Window = {
otype: string;
oid: string;
version: number;
workspaceid: string;
activetabid: string;
activeblockmap: { [key: string]: string };
pos: Point;
winsize: WinSize;
lastfocusts: number;
};
}
export {};

View File

@ -19,6 +19,7 @@ import (
"github.com/wavetermdev/thenextwave/pkg/service/blockservice"
"github.com/wavetermdev/thenextwave/pkg/service/clientservice"
"github.com/wavetermdev/thenextwave/pkg/service/fileservice"
"github.com/wavetermdev/thenextwave/pkg/service/objectservice"
"github.com/wavetermdev/thenextwave/pkg/wavebase"
"github.com/wavetermdev/thenextwave/pkg/wstore"
@ -131,6 +132,7 @@ func main() {
application.NewService(&fileservice.FileService{}),
application.NewService(&blockservice.BlockService{}),
application.NewService(&clientservice.ClientService{}),
application.NewService(&objectservice.ObjectService{}),
},
Icon: appIcon,
Assets: application.AssetOptions{

View File

@ -0,0 +1,55 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package objectservice
import (
"context"
"fmt"
"strings"
"time"
"github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
type ObjectService struct{}
const DefaultTimeout = 2 * time.Second
func parseORef(oref string) (*waveobj.ORef, error) {
fields := strings.Split(oref, ":")
if len(fields) != 2 {
return nil, fmt.Errorf("invalid object reference: %q", oref)
}
return &waveobj.ORef{OType: fields[0], OID: fields[1]}, nil
}
func (svc *ObjectService) GetObject(orefStr string) (any, error) {
oref, err := parseORef(orefStr)
if err != nil {
return nil, err
}
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
obj, err := wstore.DBGetORef(ctx, *oref)
if err != nil {
return nil, fmt.Errorf("error getting object: %w", err)
}
return obj, nil
}
func (svc *ObjectService) GetObjects(orefStrArr []string) (any, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
var orefArr []waveobj.ORef
for _, orefStr := range orefStrArr {
orefObj, err := parseORef(orefStr)
if err != nil {
return nil, err
}
orefArr = append(orefArr, *orefObj)
}
return wstore.DBSelectORefs(ctx, orefArr)
}

View File

@ -23,6 +23,11 @@ const (
VersionGoFieldName = "Version"
)
type ORef struct {
OType string `json:"otype"`
OID string `json:"oid"`
}
type WaveObj interface {
GetOType() string // should not depend on object state (should work with nil value)
}
@ -36,35 +41,37 @@ type waveObjDesc struct {
var waveObjMap = sync.Map{}
var waveObjRType = reflect.TypeOf((*WaveObj)(nil)).Elem()
func RegisterType[T WaveObj]() {
var waveObj T
func RegisterType(rtype reflect.Type) {
if rtype.Kind() != reflect.Ptr {
panic(fmt.Sprintf("wave object must be a pointer for %v", rtype))
}
if !rtype.Implements(waveObjRType) {
panic(fmt.Sprintf("wave object must implement WaveObj for %v", rtype))
}
waveObj := reflect.Zero(rtype).Interface().(WaveObj)
otype := waveObj.GetOType()
if otype == "" {
panic(fmt.Sprintf("otype is empty for %T", waveObj))
}
rtype := reflect.TypeOf(waveObj)
if rtype.Kind() != reflect.Ptr {
panic(fmt.Sprintf("wave object must be a pointer for %T", waveObj))
panic(fmt.Sprintf("otype is empty for %v", rtype))
}
oidField, found := rtype.Elem().FieldByName(OIDGoFieldName)
if !found {
panic(fmt.Sprintf("missing OID field for %T", waveObj))
panic(fmt.Sprintf("missing OID field for %v", rtype))
}
if oidField.Type.Kind() != reflect.String {
panic(fmt.Sprintf("OID field must be string for %T", waveObj))
panic(fmt.Sprintf("OID field must be string for %v", rtype))
}
if oidField.Tag.Get("json") != OIDKeyName {
panic(fmt.Sprintf("OID field json tag must be %q for %T", OIDKeyName, waveObj))
panic(fmt.Sprintf("OID field json tag must be %q for %v", OIDKeyName, rtype))
}
versionField, found := rtype.Elem().FieldByName(VersionGoFieldName)
if !found {
panic(fmt.Sprintf("missing Version field for %T", waveObj))
panic(fmt.Sprintf("missing Version field for %v", rtype))
}
if versionField.Type.Kind() != reflect.Int {
panic(fmt.Sprintf("Version field must be int for %T", waveObj))
panic(fmt.Sprintf("Version field must be int for %v", rtype))
}
if versionField.Tag.Get("json") != VersionKeyName {
panic(fmt.Sprintf("Version field json tag must be %q for %T", VersionKeyName, waveObj))
panic(fmt.Sprintf("Version field json tag must be %q for %v", VersionKeyName, rtype))
}
_, found = waveObjMap.Load(otype)
if found {
@ -286,16 +293,16 @@ func generateTSTypeInternal(rtype reflect.Type) (string, []reflect.Type) {
subTypes = append(subTypes, fieldSubTypes...)
buf.WriteString(fmt.Sprintf(" %s%s: %s;\n", fieldName, optMarker, tsType))
}
buf.WriteString("}\n")
buf.WriteString("};\n")
return buf.String(), subTypes
}
func GenerateWaveObjTSType() string {
var buf bytes.Buffer
buf.WriteString("type WaveObj {\n")
buf.WriteString("type WaveObj = {\n")
buf.WriteString(" otype: string;\n")
buf.WriteString(" oid: string;\n")
buf.WriteString("}\n")
buf.WriteString("};\n")
return buf.String()
}

View File

@ -6,6 +6,7 @@ package wstore
import (
"context"
"fmt"
"reflect"
"time"
"github.com/google/uuid"
@ -19,11 +20,9 @@ var TabMap = ds.NewSyncMap[*Tab]()
var BlockMap = ds.NewSyncMap[*Block]()
func init() {
waveobj.RegisterType[*Client]()
waveobj.RegisterType[*Window]()
waveobj.RegisterType[*Workspace]()
waveobj.RegisterType[*Tab]()
waveobj.RegisterType[*Block]()
for _, rtype := range AllWaveObjTypes() {
waveobj.RegisterType(rtype)
}
}
type Client struct {
@ -36,6 +35,16 @@ func (*Client) GetOType() string {
return "client"
}
func AllWaveObjTypes() []reflect.Type {
return []reflect.Type{
reflect.TypeOf(&Client{}),
reflect.TypeOf(&Window{}),
reflect.TypeOf(&Workspace{}),
reflect.TypeOf(&Tab{}),
reflect.TypeOf(&Block{}),
}
}
// stores the ui-context of the window
// workspaceid, active tab, active block within each tab, window size, etc.
type Window struct {
@ -147,6 +156,10 @@ func CreateWorkspace() (*Workspace, error) {
return ws, nil
}
func GetObject(otype string, oid string) (waveobj.WaveObj, error) {
return nil, nil
}
func EnsureInitialData() error {
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()

View File

@ -14,9 +14,18 @@ func waveObjTableName(w waveobj.WaveObj) string {
return "db_" + w.GetOType()
}
func tableNameFromOType(otype string) string {
return "db_" + otype
}
func tableNameGen[T waveobj.WaveObj]() string {
var zeroObj T
return "db_" + zeroObj.GetOType()
return tableNameFromOType(zeroObj.GetOType())
}
func getOTypeGen[T waveobj.WaveObj]() string {
var zeroObj T
return zeroObj.GetOType()
}
func DBGetCount[T waveobj.WaveObj](ctx context.Context) (int, error) {
@ -33,13 +42,26 @@ type idDataType struct {
Data []byte
}
func genericCastWithErr[T any](v any, err error) (T, error) {
if err != nil {
var zeroVal T
return zeroVal, err
}
return v.(T), err
}
func DBGetSingleton[T waveobj.WaveObj](ctx context.Context) (T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (T, error) {
table := tableNameGen[T]()
rtn, err := DBGetSingletonByType(ctx, getOTypeGen[T]())
return genericCastWithErr[T](rtn, err)
}
func DBGetSingletonByType(ctx context.Context, otype string) (waveobj.WaveObj, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (waveobj.WaveObj, error) {
table := tableNameFromOType(otype)
query := fmt.Sprintf("SELECT oid, version, data FROM %s LIMIT 1", table)
var row idDataType
tx.Get(&row, query)
rtn, err := waveobj.FromJsonGen[T](row.Data)
rtn, err := waveobj.FromJson(row.Data)
if err != nil {
return rtn, err
}
@ -49,12 +71,17 @@ func DBGetSingleton[T waveobj.WaveObj](ctx context.Context) (T, error) {
}
func DBGet[T waveobj.WaveObj](ctx context.Context, id string) (T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (T, error) {
table := tableNameGen[T]()
rtn, err := DBGetORef(ctx, waveobj.ORef{OType: getOTypeGen[T](), OID: id})
return genericCastWithErr[T](rtn, err)
}
func DBGetORef(ctx context.Context, oref waveobj.ORef) (waveobj.WaveObj, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (waveobj.WaveObj, error) {
table := tableNameFromOType(oref.OType)
query := fmt.Sprintf("SELECT oid, version, data FROM %s WHERE oid = ?", table)
var row idDataType
tx.Get(&row, query, id)
rtn, err := waveobj.FromJsonGen[T](row.Data)
tx.Get(&row, query, oref.OID)
rtn, err := waveobj.FromJson(row.Data)
if err != nil {
return rtn, err
}
@ -63,31 +90,58 @@ func DBGet[T waveobj.WaveObj](ctx context.Context, id string) (T, error) {
})
}
func DBSelectMap[T waveobj.WaveObj](ctx context.Context, ids []string) (map[string]T, error) {
return WithTxRtn(ctx, func(tx *TxWrap) (map[string]T, error) {
table := tableNameGen[T]()
var rows []idDataType
func dbSelectOIDs(ctx context.Context, otype string, oids []string) ([]waveobj.WaveObj, error) {
return WithTxRtn(ctx, func(tx *TxWrap) ([]waveobj.WaveObj, error) {
table := tableNameFromOType(otype)
query := fmt.Sprintf("SELECT oid, version, data FROM %s WHERE oid IN (SELECT value FROM json_each(?))", table)
tx.Select(&rows, query, ids)
rtnMap := make(map[string]T)
var rows []idDataType
tx.Select(&rows, query, oids)
rtn := make([]waveobj.WaveObj, 0, len(rows))
for _, row := range rows {
if row.OId == "" || len(row.Data) == 0 {
continue
}
waveObj, err := waveobj.FromJsonGen[T](row.Data)
waveObj, err := waveobj.FromJson(row.Data)
if err != nil {
return nil, err
}
waveobj.SetVersion(waveObj, row.Version)
rtnMap[row.OId] = waveObj
rtn = append(rtn, waveObj)
}
return rtnMap, nil
return rtn, nil
})
}
func DBDelete[T waveobj.WaveObj](ctx context.Context, id string) error {
func DBSelectORefs(ctx context.Context, orefs []waveobj.ORef) ([]waveobj.WaveObj, error) {
oidsByType := make(map[string][]string)
for _, oref := range orefs {
oidsByType[oref.OType] = append(oidsByType[oref.OType], oref.OID)
}
return WithTxRtn(ctx, func(tx *TxWrap) ([]waveobj.WaveObj, error) {
rtn := make([]waveobj.WaveObj, 0, len(orefs))
for otype, oids := range oidsByType {
rtnArr, err := dbSelectOIDs(tx.Context(), otype, oids)
if err != nil {
return nil, err
}
rtn = append(rtn, rtnArr...)
}
return rtn, nil
})
}
func DBSelectMap[T waveobj.WaveObj](ctx context.Context, ids []string) (map[string]T, error) {
rtnArr, err := dbSelectOIDs(ctx, getOTypeGen[T](), ids)
if err != nil {
return nil, err
}
rtnMap := make(map[string]T)
for _, obj := range rtnArr {
rtnMap[waveobj.GetOID(obj)] = obj.(T)
}
return rtnMap, nil
}
func DBDelete(ctx context.Context, otype string, id string) error {
return WithTx(ctx, func(tx *TxWrap) error {
table := tableNameGen[T]()
table := tableNameFromOType(otype)
query := fmt.Sprintf("DELETE FROM %s WHERE oid = ?", table)
tx.Exec(query, id)
return nil
@ -111,7 +165,7 @@ func DBUpdate(ctx context.Context, val waveobj.WaveObj) error {
})
}
func DBInsert[T waveobj.WaveObj](ctx context.Context, val T) error {
func DBInsert(ctx context.Context, val waveobj.WaveObj) error {
oid := waveobj.GetOID(val)
if oid == "" {
return fmt.Errorf("cannot insert %T value with empty id", val)