reimpl of some missing features from before the deletions of everything

This commit is contained in:
Emmaline Autumn 2023-12-27 18:43:56 -07:00
parent e9a3f8ca09
commit 67c326ad3b
16 changed files with 272 additions and 176 deletions

BIN
project-warstone/bun.lockb Executable file

Binary file not shown.

View File

@ -17,7 +17,7 @@
"postcss": "^8.4.24",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.12.0",
"react-router-dom": "^6.21.0",
"recoil": "^0.7.7",
"recoilize": "^3.2.0",
"tailwindcss": "^3.3.2",

View File

@ -1,9 +1,11 @@
Start query: ?
- by default, queries search the publication
- by default, queries search the current publication
- you can specify a publication by following this with the name, by default it will use the latest release of a publication
- you can specify the version by including it in a pair of square brackets
Query current object: ^
- refers specifically to the object based off of this specific type
Query relative object: $
- refers to the last relative object in the heirarchy
- refers to the last relative object in the hierarchy
Access child: .
- this also maps over all items in a list and returns a list of the relevant children
- if the parent is a list, it will return a list of just the specified child
@ -45,6 +47,8 @@ separator: ::;;
Examples:
?core.weapon_abilities[name=Rapid Fire].body
- this searches the publication 'core' object for weapon_abilities that have the name
?core[v1].weapon_abilities[name=Rapid Fire].body
- this searches version 'v1' of the publication 'core' object for weapon_abilities that have the name
Sustained Hits {{?^sustained_hits}}
- This formats the string with the queried values, which start the query within the same object that the field exists in. This would result in the string 'Sustained Hits 1'
Anti-{{_.keyword}} {{_.value}}<<?^anti_abilities

View File

@ -2,13 +2,22 @@ import { RecoilRoot } from 'recoil'
import { SchemaBuilder } from './components/SchemaBuilder'
import { GameSystemEditor } from './components/GameSystemEditor'
import RecoilizeDebugger from 'recoilize';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { SchemaList } from './components/schemalist';
function App() {
return (
<RecoilRoot>
<BrowserRouter>
<Routes>
<Route Component={SchemaList} path='/schemas' />
<Route Component={SchemaBuilder} path='/schema/:id' />
{/* <Route Component={SchemaBuilder} path='/schema/new' /> */}
<Route Component={GameSystemEditor} path='/game-system/:id' />
{/* <Route Component={GameSystemEditor} path='/game-system/new' /> */}
</Routes>
</BrowserRouter>
<RecoilizeDebugger />
<SchemaBuilder />
{/* <GameSystemEditor /> */}
</RecoilRoot>
)
}

View File

@ -16,19 +16,20 @@ export const GameSystemEditor: FC = () => {
name: 'Asshammer 40x a day',
accolades: [],
});
const [schemas, setSchemas] = useState<{ name: string, id: string }[]>([]);
const [schemas, setSchemas] = useState<[string,string][]>([]);
const [lastSaved, setLastSaved] = useState(gameSystem);
const fetchSchema = useCallback(async (id: string) => {
const res = await GameSystemsService.getSchema(id);
if (res.status !== 200) return;
const schema = await res.json()
try {
const schema = await GameSystemsService.getSchema(id);
updateGameSystem({
schema
});
} catch (e) {
console.log('failed to fetch schema:', e)
}
}, [updateGameSystem])
useEffect(() => {
@ -38,7 +39,6 @@ export const GameSystemEditor: FC = () => {
useEffect(() => {
GameSystemsService.getSchemaList()
.then(res => res.json())
.then(schemas => setSchemas(schemas));
}, []);
@ -48,8 +48,7 @@ export const GameSystemEditor: FC = () => {
}, [gameSystem])
const fetchGameSystem = useCallback(async () => {
const res = await GameSystemsService.getGameSystem('');
const gs = await res.json();
const gs = await GameSystemsService.getGameSystem('');
setGameSystem(gs);
setLastSaved(gs);
}, [setGameSystem]);
@ -66,7 +65,7 @@ export const GameSystemEditor: FC = () => {
<select className="no-default text-white bg-transparent header border-b-2 border-falcon" {...bindGameSystemProperty('schemaId')}>
<option value=""></option>
{schemas.map(s => (
<option value={s.id}>{s.name}</option>
<option value={s[0]}>{s[1]}</option>
))}
</select>
<button onClick={saveGameSystem} disabled={lastSaved === gameSystem}>Save Game System</button>

View File

@ -24,9 +24,9 @@ export const FieldEditor: FC<IProps> = ({ update, field, fieldName, deleteField
setReserved(RESERVED_FIELDS[fieldName]);
}, [fieldName])
useEffect(() => {
console.log(field.value);
}, [field])
// useEffect(() => {
// console.log(field.value);
// }, [field])
return (
<li className="odd:bg-black/50">

View File

@ -4,17 +4,23 @@ import { TypeEditor } from './type-editor';
import { useObjectState, useObjectStateWrapper } from '../../hooks/useObjectState';
import { FieldTypes, Schema, Template, TypeType } from '../../types/schema';
import { useInput } from '../../hooks/useInput';
import { useRecoilState } from 'recoil';
import { useRecoilState, useResetRecoilState } from 'recoil';
import { SchemaEditAtom } from '../../recoil/atoms/schema';
import { GameSystemsService } from '../../services/game-systems';
import { SchemaViewer } from './schema-viewer';
import { TemplateEditor } from './template-editor';
import { Icon } from '../Icon';
import { useNavigate, useParams } from 'react-router-dom';
export const SchemaBuilder: FC = () => {
const [schema, setSchema] = useRecoilState(SchemaEditAtom);
const { update: updateSchema } = useObjectStateWrapper<Schema>(schema, setSchema);
const resetSchema = useResetRecoilState(SchemaEditAtom);
const { update: updateSchema, bindProperty:bindSchemaProperty } = useObjectStateWrapper<Schema>(schema, setSchema);
const navigate = useNavigate();
const {id} = useParams<{id: string}>()
const { value: typeName, bind: bindTypeName, reset: resetTypeName } = useInput('');
@ -23,19 +29,18 @@ export const SchemaBuilder: FC = () => {
const [lastSaved, setLastSaved] = useState(schema);
const fetchSchema = useCallback(async () => {
const result = await GameSystemsService.getSchema('286f4c18-d280-444b-8d7e-9a3dd09f64ef')
if (result.status !== 200) return;
const fetchedSchema = await result.json();
if (!id) return;
if (id === 'new') return resetSchema();
const fetchedSchema = await GameSystemsService.getSchema(id)
// if (fetchedSchema.name === schema.name) return;
if (!fetchedSchema.templates) fetchedSchema.templates = {}
setSchema(fetchedSchema);
setLastSaved(fetchedSchema);
}, [setSchema])
}, [])
useEffect(() => {
fetchSchema();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
}, [fetchSchema])
const [selectedType, setSelectedType] = useState('');
@ -51,9 +56,10 @@ export const SchemaBuilder: FC = () => {
setSelectedType('');
}, [resetTypeName, updateSchema]);
const saveSchema = useCallback(() => {
GameSystemsService.saveSchema(schema);
const saveSchema = useCallback(async () => {
setLastSaved(schema);
const sid = await GameSystemsService.saveSchema(schema);
if (id === 'new') navigate('/schema/'+sid)
}, [schema])
const selectTypeForEdit = useCallback((typeKey: string) => {
@ -95,6 +101,9 @@ export const SchemaBuilder: FC = () => {
return (
<div className="container flex gap-4 p-8">
<div className="panel w-2/3 h-full flex flex-col gap-4">
<div>
<input type="text" {...bindSchemaProperty('name')} placeholder="Schema Name" />
</div>
<div>
<p className="subheader mb-2">Add a template</p>
<div className="mb-2">
@ -103,7 +112,7 @@ export const SchemaBuilder: FC = () => {
</div>
<ul className="rounded-lg overflow-hidden">
{Object.entries(schema.templates).map(([templateKey, template]) => (
<TemplateEditor templateKey={templateKey} template={template} update={updateTemplate} />
<TemplateEditor key={templateKey} templateKey={templateKey} template={template} update={updateTemplate} />
))}
</ul>
</div>
@ -119,7 +128,7 @@ export const SchemaBuilder: FC = () => {
</AnimatedPageContainer>
<ul className="mt-3 w-96">
{Object.keys(schema.types).map(t => (
<li className="odd:bg-black/50 flex justify-between p-2">
<li key={'type' + t} className="odd:bg-black/50 flex justify-between p-2">
{t}
<div className="flex gap-3">
<button title="Edit" className="no-default" onClick={() => selectTypeForEdit(t)}>

View File

@ -40,10 +40,10 @@ export const TemplateEditor: FC<IProps> = ({ templateKey, update, template }) =>
<input type="text" {...bindProperty('type', { disallowSpaces: true })} list="type-editor-type-list" />
<datalist id="type-editor-type-list">
{Object.keys(TEMPLATE_TYPES).map(k => (
<option value={k}>{k}</option>
<option key={'templatetype' + k} value={k}>{k}</option>
))}
{Object.keys(schema.types).map(k => (
<option value={k}>{k}</option>
<option key={'schematype' + k} value={k}>{k}</option>
))}
</datalist>
</label>

View File

@ -43,7 +43,7 @@ export const ValueField: FC<IValueProps> = ({ type, bind }) => {
>
<option value=""></option>
{DICE_SIDES.map(d => (
<option value={'d' + d}>{d}</option>
<option key={'dice sides' + d} value={'d' + d}>{d}</option>
))}
</select>
</label>

View File

@ -0,0 +1,20 @@
import { FC, useEffect, useState } from "react";
import { GameSystemsService } from "../services/game-systems";
import { Link } from "react-router-dom";
export const SchemaList: FC = () => {
const [schemas, setSchemas] = useState<[string,string][]>([]);
useEffect(() => {
GameSystemsService.getSchemaList().then(l => {
setSchemas(l)
});
}, [])
return (
<div className="container pt-12 flex flex-col gap-6">
<Link to="/schema/new"><button>New Schema</button></Link>
<ul className="panel">
{schemas.map(([id,name])=> <li key={id}><Link to={'/schema/' + id}><span className="w-full">{name || 'Unnamed Schema'}</span></Link></li>)}
</ul>
</div>
)
}

View File

@ -1,4 +1,4 @@
import { FieldTypes, TypeType } from '../types/schema';
import { FieldTypes, TypeType } from "../types/schema";
export const TEMPLATE_TYPES: Record<string, TypeType> = {
section: {
@ -7,14 +7,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: ''
value: "",
},
body: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes['long text'],
value: ''
type: FieldTypes["long text"],
value: "",
},
},
steps: {
@ -23,8 +23,8 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
limit: 0,
minimum: 1,
type: FieldTypes.type,
value: 'section'
}
value: "section",
},
},
image: {
name: {
@ -32,14 +32,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: ''
value: "",
},
link: {
isConstant: false,
limit: 1,
minimum: 1,
type: FieldTypes.text,
value: ''
value: "",
},
},
list: {
@ -47,18 +47,34 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes['long text'],
value: ''
}
type: FieldTypes["long text"],
value: "",
},
},
table_column: {
name: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.any,
value: "",
},
value: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.any,
value: "",
},
},
table_row: {
columns: {
isConstant: false,
limit: 0,
minimum: 1,
type: FieldTypes.any,
value: ''
}
type: FieldTypes.type,
value: "tableColumn",
},
},
table: {
rows: {
@ -66,14 +82,14 @@ export const TEMPLATE_TYPES: Record<string, TypeType> = {
limit: 0,
minimum: 1,
type: FieldTypes.type,
value: 'tableRow'
value: "tableRow",
},
header: {
isConstant: false,
limit: 1,
minimum: 0,
type: FieldTypes.type,
value: 'tableRow'
}
}
value: "tableRow",
},
},
};

View File

@ -1,46 +1,58 @@
import React, { ChangeEvent, useCallback, useState } from 'react';
import { InputBinder } from '../types/inputBinder';
import React, { ChangeEvent, useCallback, useState } from "react";
import { InputBinder } from "../types/inputBinder";
type ObjectStateHookConfig = {
disallowSpaces?: boolean;
spaceReplacer?: string;
}
};
export const useObjectState = <T extends object>(initial: T) => {
const [state, setState] = useState<T>(initial || {} as T);
const bindProperty = useCallback(<K extends keyof T>(property: K, config: ObjectStateHookConfig) => ({
value: state[property] ?? '',
const bindProperty = useCallback(
<K extends keyof T>(property: K, config?: ObjectStateHookConfig) => ({
value: state[property] ?? "",
name: property,
onChange: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) =>
setState(value => (
onChange: (
event: ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>,
) =>
setState((value) => (
{
...value,
[event.target.name]: (
(typeof value[property] === 'number') ?
Number(event.target.value) || 0 :
config?.disallowSpaces ?
event.target.value.replace(' ', config.spaceReplacer || '_') :
event.target.value
)
(typeof value[property] === "number")
? Number(event.target.value) || 0
: config?.disallowSpaces
? event.target.value.replace(" ", config.spaceReplacer || "_")
: event.target.value
),
}
))
}), [state])
)),
}),
[state],
);
const bindPropertyCheck = useCallback(<K extends keyof T>(property: K) => ({
checked: !!state[property],
name: property,
onChange: (event: ChangeEvent<HTMLInputElement>) => setState(value => ({
...value, [event.target.name]: (event.target.checked)
onChange: (event: ChangeEvent<HTMLInputElement>) =>
setState((value) => ({
...value,
[event.target.name]: (event.target.checked),
})),
readOnly: true
}), [state])
readOnly: true,
}), [state]);
const update = useCallback((updates: Partial<T>) => setState(s => ({ ...s, ...updates })), [])
const update = useCallback(
(updates: Partial<T>) => setState((s) => ({ ...s, ...updates })),
[],
);
const reset = useCallback(() => {
setState(initial);
}, [initial])
}, [initial]);
return {
bindProperty,
@ -48,48 +60,67 @@ export const useObjectState = <T extends object>(initial: T) => {
update,
state,
setState,
reset
}
}
reset,
};
};
export const useObjectStateWrapper = <T extends object>(state: T, setState: React.Dispatch<React.SetStateAction<T>>) => {
const bindProperty = useCallback(<K extends keyof T>(property: K, config?: ObjectStateHookConfig): InputBinder => ({
value: state[property] ?? '',
export const useObjectStateWrapper = <T extends object>(
state: T,
setState: React.Dispatch<React.SetStateAction<T>>,
) => {
const bindProperty = useCallback(
<K extends keyof T>(
property: K,
config?: ObjectStateHookConfig,
): InputBinder => ({
value: state[property]?.toString() ?? "",
name: property.toString(),
onChange: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) =>
setState(value => (
onChange: (
event: ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>,
) =>
setState((value) => (
{
...value,
[event.target.name]: (
(typeof value[property] === 'number') ?
Number(event.target.value) || 0 :
config?.disallowSpaces ?
event.target.value.replace(' ', config.spaceReplacer || '_') :
event.target.value
)
(typeof value[property] === "number")
? Number(event.target.value) || 0
: config?.disallowSpaces
? event.target.value.replace(" ", config.spaceReplacer || "_")
: event.target.value
),
}
))
}), [setState, state])
)),
}),
[setState, state],
);
const bindPropertyCheck = useCallback(<K extends keyof T>(property: K) => ({
checked: !!state[property],
name: property,
onChange: (event: ChangeEvent<HTMLInputElement>) => setState(value => ({
...value, [event.target.name]: (event.target.checked)
onChange: (event: ChangeEvent<HTMLInputElement>) =>
setState((value) => ({
...value,
[event.target.name]: (event.target.checked),
})),
readOnly: true
}), [setState, state])
readOnly: true,
}), [setState, state]);
const update = useCallback((updates: Partial<T> | ((arg: T) => Partial<T>)) =>
setState(s => ({ ...s, ...(typeof updates === 'function' ? updates(s) : updates) }
)), [setState])
const update = useCallback(
(updates: Partial<T> | ((arg: T) => Partial<T>)) =>
setState((s) => ({
...s,
...(typeof updates === "function" ? updates(s) : updates),
})),
[setState],
);
return {
bindProperty,
bindPropertyCheck,
update,
state,
setState
}
}
setState,
};
};

View File

@ -13,7 +13,7 @@ export const Accordion: FCC<IProps> = ({ children, expandOnHover, expanded, titl
return (
<div
data-expanded={open || expanded}
data-expandOnHover={expandOnHover}
data-expandonhover={expandOnHover}
className={(expandOnHover ? 'group/hover' : 'group/controlled') + ' group'}
onClick={() => !title && !expandOnHover && setOpen(!open)}
>

View File

@ -1,51 +1,45 @@
import { GameSystem } from '../types/gameSystem';
import { Schema } from '../types/schema';
import { GameSystem } from "../types/gameSystem";
import { Schema } from "../types/schema";
import { idb } from "./indexeddb";
const emptySchema = { name: '', types: {}, templates: {}, id: crypto.randomUUID() };
const emptySchema = {
name: "",
types: {},
templates: {},
id: crypto.randomUUID(),
};
export const GameSystemsService = {
// todo - connect to service to save schema for game
saveSchema: async (schema: Schema) => {
localStorage.setItem('schema ' + schema.id, JSON.stringify(schema));
return { status: 200 }
// localStorage.setItem('schema ' + schema.id, JSON.stringify(schema));
try {
return await idb.createOrUpdate({
storeName: "schema",
...schema,
});
} catch (e) {
console.log(e);
}
},
// todo - connect to service to fetch schema for game
getSchema: async (id: string): Promise<{ status: 200 | 404, json: () => Promise<Schema> }> => {
const schema = localStorage.getItem('schema ' + id);
if (schema)
return {
status: 200,
json: async () => JSON.parse(schema)
}
return {
status: 404,
json: async () => (emptySchema)
}
},
getSchemaList: async () => {
return {
status: 200, json: async () => [{
name: 'Test Schema',
id: '286f4c18-d280-444b-8d7e-9a3dd09f64ef'
}]
getSchema: async (
id: string,
): Promise<Schema> => {
try {
const schema = await idb.read("schema", id);
return schema;
} catch (e) {
throw e;
}
},
getSchemaList: async () => await idb.listAll<string>("schema", "name"),
saveGameSystem: async (gs: GameSystem) => {
localStorage.setItem('game-system ' + gs.id, JSON.stringify(gs));
return { status: 200 }
localStorage.setItem("game-system " + gs.id, JSON.stringify(gs));
return { status: 200 };
},
getGameSystem: async (id: string): Promise<{ status: 200 | 404, json: () => Promise<GameSystem> }> => {
const gs = localStorage.getItem('game-system ' + id);
if (!gs) return {status: 404, json:async () => ({
accolades: [],
name: '',
id: crypto.randomUUID(),
schema: emptySchema,
schemaId: ''
})}
return { status: 200, json:async () => (JSON.parse(gs))}
}
}
getGameSystem: async (
id: string,
): Promise<GameSystem> => await idb.read("game-system", id),
};

View File

@ -0,0 +1,7 @@
import { IndexedDBService } from "../utils/indexeddb";
export const idb = new IndexedDBService("commander", 1, [
"game-system",
"publication",
"schema",
]);

View File

@ -1,4 +1,6 @@
class IndexedDBService {
type Data = Record<string, any> & { storeName: string };
export class IndexedDBService {
private dbName: string;
private dbVersion: number;
private storeNames: string[];
@ -17,7 +19,7 @@ class IndexedDBService {
const request = indexedDB.open(this.dbName, this.dbVersion);
request.onerror = () => {
reject(new Error('Failed to open the database.'));
reject(new Error("Failed to open the database."));
};
request.onsuccess = () => {
@ -31,13 +33,13 @@ class IndexedDBService {
// Create all the required object stores during the upgrade
for (const storeName of this.storeNames) {
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName, { keyPath: 'uuid' });
db.createObjectStore(storeName, { keyPath: "uuid" });
}
}
};
request.onblocked = () => {
reject(new Error('Database is blocked and cannot be accessed.'));
reject(new Error("Database is blocked and cannot be accessed."));
};
});
}
@ -46,11 +48,11 @@ class IndexedDBService {
return crypto.randomUUID();
}
public async create(data: any): Promise<string> {
public async create(data: Data): Promise<string> {
const db = await this.openDB();
const uuid = this.generateUUID();
return new Promise((resolve, reject) => {
const transaction = db.transaction(data.storeName, 'readwrite');
const transaction = db.transaction(data.storeName, "readwrite");
const objectStore = transaction.objectStore(data.storeName);
const request = objectStore.add({ ...data, uuid });
@ -60,7 +62,7 @@ class IndexedDBService {
};
request.onerror = () => {
reject(new Error('Failed to add data to IndexedDB.'));
reject(new Error("Failed to add data to IndexedDB."));
};
});
}
@ -68,7 +70,7 @@ class IndexedDBService {
public async read(storeName: string, uuid: string): Promise<any | null> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const transaction = db.transaction(storeName, "readonly");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.get(uuid);
@ -78,25 +80,29 @@ class IndexedDBService {
};
request.onerror = () => {
reject(new Error('Failed to read data from IndexedDB.'));
reject(new Error("Failed to read data from IndexedDB."));
};
});
}
public async update(storeName: string, uuid: string, newData: any): Promise<void> {
public async update(
storeName: string,
uuid: string,
newData: any,
): Promise<void> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const transaction = db.transaction(storeName, "readwrite");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.put({ ...newData, uuid }, uuid);
const request = objectStore.put({ ...newData, uuid });
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(new Error('Failed to update data in IndexedDB.'));
reject(new Error("Failed to update data in IndexedDB."));
};
});
}
@ -104,7 +110,7 @@ class IndexedDBService {
public async delete(storeName: string, uuid: string): Promise<void> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readwrite');
const transaction = db.transaction(storeName, "readwrite");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.delete(uuid);
@ -114,16 +120,19 @@ class IndexedDBService {
};
request.onerror = () => {
reject(new Error('Failed to delete data from IndexedDB.'));
reject(new Error("Failed to delete data from IndexedDB."));
};
});
}
public async listAll(storeName: string, fieldName: string): Promise<[string, any][]> {
public async listAll<T = any>(
storeName: string,
fieldName: string,
): Promise<[string, T][]> {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction(storeName, 'readonly');
const transaction = db.transaction(storeName, "readonly");
const objectStore = transaction.objectStore(storeName);
const request = objectStore.openCursor();
@ -143,14 +152,12 @@ class IndexedDBService {
};
request.onerror = () => {
reject(new Error('Failed to list data from IndexedDB.'));
reject(new Error("Failed to list data from IndexedDB."));
};
});
}
public async createOrUpdate(data: any): Promise<string> {
const db = await this.openDB();
public async createOrUpdate(data: Data): Promise<string> {
// If the provided data already has a UUID, check if it exists in the database
if (data.uuid) {
const existingData = await this.read(data.storeName, data.uuid);