feat/orm-diffs
Laurin Weger 3 days ago
parent cce7dbfbe9
commit 2bdaa84438
No known key found for this signature in database
GPG Key ID: 9B372BB0B792770F
  1. 10
      engine/verifier/src/orm/handle_frontend_update.rs
  2. 1
      engine/verifier/src/orm/initialize.rs
  3. 20
      sdk/js/alien-deepsignals/test-map-type.js
  4. 28
      sdk/js/alien-deepsignals/test-set-map.js
  5. 25
      sdk/js/alien-deepsignals/test-set-paths.js
  6. 11
      sdk/js/examples/multi-framework-signals/src/app/pages/index.astro
  7. 440
      sdk/js/examples/multi-framework-signals/src/frontends/react/HelloWorld.tsx
  8. 175
      sdk/js/examples/multi-framework-signals/src/frontends/svelte/HelloWorld.svelte
  9. 389
      sdk/js/examples/multi-framework-signals/src/frontends/vue/HelloWorld.vue

@ -302,10 +302,7 @@ fn create_sparql_update_query_for_diff(
remove_statement,
wheres.join(" .\n ")
));
log_info!(
"[create_sparql_update_query_for_diff] Added delete query #{}",
sparql_sub_queries.len()
);
log_info!("[create_sparql_update_query_for_diff] Added delete query.");
// var_counter += 1; // Not necessary because not used afterwards.
}
// The actual INSERT.
@ -315,10 +312,7 @@ fn create_sparql_update_query_for_diff(
add_statement,
where_statements.join(". \n ")
));
log_info!(
"[create_sparql_update_query_for_diff] Added insert query #{}",
sparql_sub_queries.len()
);
log_info!("[create_sparql_update_query_for_diff] Added insert query.");
}
}

@ -62,7 +62,6 @@ impl Verifier {
.push(orm_subscription);
let orm_objects = self.create_orm_object_for_shape(nuri, session_id, &shape_type)?;
// log_debug!("create_orm_object_for_shape return {:?}", orm_objects);
let _ = tx
.send(AppResponse::V0(AppResponseV0::OrmInitial(orm_objects)))

@ -0,0 +1,20 @@
import { deepSignal } from './dist/index.js';
const root = deepSignal({
mySet: new Set([
{ "@id": "obj1", value: 10 },
{ "@id": "obj2", value: 20 }
])
});
const result = root.mySet.values().map(entry => entry);
console.log('Type:', typeof result);
console.log('Constructor:', result.constructor.name);
console.log('Result:', result);
console.log('Has next?:', typeof result.next);
console.log('Is iterable?:', Symbol.iterator in result);
// Convert to array
const arr = Array.from(result);
console.log('Array:', arr);
console.log('First entry:', arr[0]);

@ -0,0 +1,28 @@
import { deepSignal, subscribeDeepMutations } from './dist/index.js';
const root = deepSignal({
mySet: new Set([
{ "@id": "obj1", value: 10 },
{ "@id": "obj2", value: 20 }
])
});
subscribeDeepMutations(root, (patches) => {
console.log('Patches:', JSON.stringify(patches, null, 2));
});
// Use .map() to get entries
const entries = root.mySet.values().map(entry => {
console.log('Entry:', entry);
return entry;
});
console.log('Got entries:', entries.length);
console.log('Modifying first entry...');
// Modify the first one
entries[0].value = 100;
setTimeout(() => {
console.log('Done');
}, 100);

@ -0,0 +1,25 @@
import { deepSignal, subscribeDeepMutations } from './dist/index.js';
const root = deepSignal({
mySet: new Set([
{ "@id": "obj1", value: 10 }
])
});
subscribeDeepMutations(root, (patches) => {
console.log('Patches:', JSON.stringify(patches, null, 2));
});
// Get the first entry from the set
const entries = root.mySet.values();
const firstEntry = entries.next().value;
console.log('First entry:', firstEntry);
console.log('Modifying value...');
// Modify it
firstEntry.value = 20;
setTimeout(() => {
console.log('Done');
}, 100);

@ -29,7 +29,8 @@ const title = "Multi-framework app";
console.log(info.V0.details);
initNg(ng, event.session);
window.ng = ng;
window.ng = ng;
window.session = event.session;
},
true,
[]
@ -37,15 +38,15 @@ const title = "Multi-framework app";
</script>
<Layout title={title}>
<!-- <Highlight vue>
<Highlight vue>
<VueRoot client:only />
</Highlight> -->
</Highlight>
<Highlight react>
<ReactRoot client:only="react" />
</Highlight>
<!--
<Highlight svelte>
<SvelteRoot client:only />
</Highlight> -->
</Highlight>
</Layout>

@ -3,195 +3,289 @@ import { useShape } from "@ng-org/signals/react";
import flattenObject from "../utils/flattenObject";
import { TestObjectShapeType } from "../../shapes/orm/testShape.shapeTypes";
import { BasicShapeType } from "../../shapes/orm/basic.shapeTypes";
import type { ShapeType } from "@ng-org/shex-orm";
import type { Basic } from "../../shapes/orm/basic.typings";
const sparqlExampleData = `
PREFIX ex: <http://example.org/>
INSERT DATA {
<urn:test:obj1> a ex:TestObject ;
ex:stringValue "hello world" ;
ex:numValue 42 ;
ex:boolValue true ;
ex:arrayValue 1,2,3 ;
ex:objectValue <urn:test:id3> ;
ex:anotherObject <urn:test:id1>, <urn:test:id2> ;
ex:numOrStr "either" ;
ex:lit1Or2 "lit1" ;
ex:unrelated "some value" ;
ex:anotherUnrelated 4242 .
<urn:test:id3>
ex:nestedString "nested" ;
ex:nestedNum 7 ;
ex:nestedArray 5,6 .
<urn:test:id1>
ex:prop1 "one" ;
ex:prop2 1 .
<urn:test:id2>
ex:prop1 "two" ;
ex:prop2 2 .
<urn:test:obj2> a ex:TestObject ;
ex:stringValue "hello world #2" ;
ex:numValue 422 ;
ex:boolValue false ;
ex:arrayValue 4,5,6 ;
ex:objectValue <urn:test:id6> ;
ex:anotherObject <urn:test:id4>, <urn:test:id5> ;
ex:numOrStr 4 ;
ex:lit1Or2 "lit2" ;
ex:unrelated "some value2" ;
ex:anotherUnrelated 42422 .
<urn:test:id6>
ex:nestedString "nested2" ;
ex:nestedNum 72 ;
ex:nestedArray 7,8,9 .
<urn:test:id4>
ex:prop1 "one2" ;
ex:prop2 12 .
<urn:test:id5>
ex:prop1 "two2" ;
ex:prop2 22 .
<urn:basicObject4>
a <http://example.org/Basic> ;
ex:basicString "string of object 1" .
<urn:basicObject5>
a <http://example.org/Basic> ;
ex:basicString "string of object 2" .
}
`;
export function HelloWorldReact() {
const state = [...(useShape(BasicShapeType)?.entries() || [])][0];
const state = useShape(BasicShapeType);
// @ts-expect-error
window.reactState = state;
console.log("react state", state);
if (!state) return <>Loading state</>;
if (!state) return <div>Loading...</div>;
// Create a table from the state object: One column for keys, one for values, one with an input to change the value.
return (
<div>
<p>Rendered in React</p>
{/* <button
<button
onClick={() => {
state.boolValue = !state.boolValue;
state.numValue += 2;
window.ng.sparql_update(
window.session.session_id,
sparqlExampleData,
"did:ng:" + window.session.private_store_id
);
}}
>
click me to change multiple props
</button> */}
<table border={1} cellPadding={5}>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
{(() => {
const setNestedValue = (
obj: any,
path: string,
value: any
) => {
const keys = path.split(".");
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
};
const getNestedValue = (obj: any, path: string) => {
return path
.split(".")
.reduce((current, key) => current[key], obj);
};
return flattenObject(state).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>
{value instanceof Set
? Array.from(value).join(", ")
: Array.isArray(value)
? `[${value.join(", ")}]`
: JSON.stringify(value)}
</td>
<td>
{typeof value === "string" ? (
<input
type="text"
value={value}
onChange={(e) => {
setNestedValue(
state,
key,
e.target.value
);
}}
/>
) : typeof value === "number" ? (
<input
type="number"
value={value}
onChange={(e) => {
setNestedValue(
state,
key,
Number(e.target.value)
);
}}
/>
) : typeof value === "boolean" ? (
<input
type="checkbox"
checked={value}
onChange={(e) => {
setNestedValue(
state,
key,
e.target.checked
);
}}
/>
) : Array.isArray(value) ? (
<div>
<button
onClick={() => {
const currentArray =
getNestedValue(
state,
key
);
setNestedValue(state, key, [
...currentArray,
currentArray.length + 1,
]);
}}
>
Add
</button>
<button
onClick={() => {
const currentArray =
getNestedValue(
state,
key
);
if (
currentArray.length > 0
) {
setNestedValue(
state,
key,
currentArray.slice(
0,
-1
)
);
}
}}
>
Remove
</button>
</div>
) : value instanceof Set ? (
<div>
<button
onClick={() => {
const currentSet =
getNestedValue(
state,
key
);
currentSet.add(
`item${currentSet.size + 1}`
);
}}
>
Add
</button>
<button
onClick={() => {
const currentSet =
getNestedValue(
state,
key
);
const lastItem =
Array.from(
currentSet
).pop();
if (lastItem) {
currentSet.delete(
lastItem
);
}
}}
>
Remove
</button>
</div>
) : (
"N/A"
)}
</td>
Add example data
</button>
<div>
{state.values()?.map((ormObj) => (
<table border={1} cellPadding={5} key={ormObj["@id"]}>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Edit</th>
</tr>
));
})()}
</tbody>
</table>
</thead>
<tbody>
{(() => {
const setNestedValue = (
obj: any,
path: string,
value: any
) => {
const keys = path.split(".");
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
};
const getNestedValue = (
obj: any,
path: string
) => {
return path
.split(".")
.reduce(
(current, key) => current[key],
obj
);
};
return flattenObject(ormObj).map(
([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>
{value instanceof Set
? Array.from(value).join(
", "
)
: Array.isArray(value)
? `[${value.join(", ")}]`
: JSON.stringify(value)}
</td>
<td>
{typeof value === "string" ? (
<input
type="text"
value={value}
onChange={(e) => {
setNestedValue(
state,
key,
e.target.value
);
}}
/>
) : typeof value ===
"number" ? (
<input
type="number"
value={value}
onChange={(e) => {
setNestedValue(
state,
key,
Number(
e.target
.value
)
);
}}
/>
) : typeof value ===
"boolean" ? (
<input
type="checkbox"
checked={value}
onChange={(e) => {
setNestedValue(
state,
key,
e.target.checked
);
}}
/>
) : Array.isArray(value) ? (
<div>
<button
onClick={() => {
const currentArray =
getNestedValue(
state,
key
);
setNestedValue(
state,
key,
[
...currentArray,
currentArray.length +
1,
]
);
}}
>
Add
</button>
<button
onClick={() => {
const currentArray =
getNestedValue(
state,
key
);
if (
currentArray.length >
0
) {
setNestedValue(
state,
key,
currentArray.slice(
0,
-1
)
);
}
}}
>
Remove
</button>
</div>
) : value instanceof Set ? (
<div>
<button
onClick={() => {
const currentSet =
getNestedValue(
state,
key
);
currentSet.add(
`item${currentSet.size + 1}`
);
}}
>
Add
</button>
<button
onClick={() => {
const currentSet =
getNestedValue(
state,
key
);
const lastItem =
Array.from(
currentSet
).pop();
if (lastItem) {
currentSet.delete(
lastItem
);
}
}}
>
Remove
</button>
</div>
) : (
"N/A"
)}
</td>
</tr>
)
);
})()}
</tbody>
</table>
))}
</div>
</div>
);
}

@ -19,9 +19,9 @@
}
cur[keys[keys.length - 1]] = value;
}
const flatEntries = $derived(
const flattenedObjects = $derived(
$shapeObject
? $shapeObject.entries().map((o) => flattenObject(o)[0] || ({} as any))
? $shapeObject.values().map((o) => flattenObject(o)[0] || ({} as any))
: []
);
$effect(() => {
@ -32,92 +32,95 @@
{#if $shapeObject}
<div>
<p>Rendered in Svelte</p>
<table border="1" cellpadding="5">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
{#each flatEntries as [key, value] (key)}
{#each flattenedObjects as flatEntries}
<table border="1" cellpadding="5">
<thead>
<tr>
<td style="white-space:nowrap;">{key}</td>
<td>
{#if value instanceof Set}
{Array.from(value).join(", ")}
{:else if Array.isArray(value)}
[{value.join(", ")}]
{:else}
{JSON.stringify(value)}
{/if}
</td>
<td>
{#if typeof value === "string"}
<input
type="text"
{value}
oninput={(e: any) =>
setNestedValue($shapeObject, key, e.target.value)}
/>
{:else if typeof value === "number"}
<input
type="number"
{value}
oninput={(e: any) =>
setNestedValue($shapeObject, key, Number(e.target.value))}
/>
{:else if typeof value === "boolean"}
<input
type="checkbox"
checked={value}
onchange={(e: any) =>
setNestedValue($shapeObject, key, e.target.checked)}
/>
{:else if Array.isArray(value)}
<div style="display:flex; gap:.5rem;">
<button
onclick={() => {
const cur = getNestedValue($shapeObject, key) || [];
setNestedValue($shapeObject, key, [
...cur,
cur.length + 1,
]);
}}>Add</button
>
<button
onclick={() => {
const cur = getNestedValue($shapeObject, key) || [];
if (cur.length)
setNestedValue($shapeObject, key, cur.slice(0, -1));
}}>Remove</button
>
</div>
{:else if value instanceof Set}
<div style="display:flex; gap:.5rem;">
<button
onclick={() => {
const cur: Set<any> = getNestedValue($shapeObject, key);
cur.add(`item${cur.size + 1}`);
}}>Add</button
>
<button
onclick={() => {
const cur: Set<any> = getNestedValue($shapeObject, key);
const last = Array.from(cur).pop();
if (last !== undefined) cur.delete(last);
}}>Remove</button
>
</div>
{:else}
N/A
{/if}
</td>
<th>Key</th>
<th>Value</th>
<th>Edit</th>
</tr>
{/each}
</tbody>
</table>
</thead>
<tbody>
{#each flatEntries as [key, value] (key)}
<tr>
<td style="white-space:nowrap;">{key}</td>
<td>
{#if value instanceof Set}
{Array.from(value).join(", ")}
{:else if Array.isArray(value)}
[{value.join(", ")}]
{:else}
{JSON.stringify(value)}
{/if}
</td>
<td>
{#if typeof value === "string"}
<input
type="text"
{value}
oninput={(e: any) =>
setNestedValue($shapeObject, key, e.target.value)}
/>
{:else if typeof value === "number"}
<input
type="number"
{value}
oninput={(e: any) =>
setNestedValue($shapeObject, key, Number(e.target.value))}
/>
{:else if typeof value === "boolean"}
<input
type="checkbox"
checked={value}
onchange={(e: any) =>
setNestedValue($shapeObject, key, e.target.checked)}
/>
{:else if Array.isArray(value)}
<div style="display:flex; gap:.5rem;">
<button
onclick={() => {
const cur = getNestedValue($shapeObject, key) || [];
setNestedValue($shapeObject, key, [
...cur,
cur.length + 1,
]);
}}>Add</button
>
<button
onclick={() => {
const cur = getNestedValue($shapeObject, key) || [];
if (cur.length)
setNestedValue($shapeObject, key, cur.slice(0, -1));
}}>Remove</button
>
</div>
{:else if value instanceof Set}
<div style="display:flex; gap:.5rem;">
<button
onclick={() => {
const cur: Set<any> = getNestedValue($shapeObject, key);
cur.add(`item${cur.size + 1}`);
}}>Add</button
>
<button
onclick={() => {
const cur: Set<any> = getNestedValue($shapeObject, key);
const last = Array.from(cur).pop();
if (last !== undefined) cur.delete(last);
}}>Remove</button
>
</div>
{:else}
N/A
{/if}
</td>
</tr>
{/each}
</tbody>
</table>
{/each}
</div>
{:else}
<p>Loading state</p>

@ -5,215 +5,214 @@ import flattenObject from "../utils/flattenObject";
import { TestObjectShapeType } from "../../shapes/orm/testShape.shapeTypes";
// Acquire deep signal object (proxy) for a shape; scope second arg left empty string for parity
const shapeObj = useShape(TestObjectShapeType);
const shapeObjects = useShape(TestObjectShapeType);
// Expose for devtools exploration
// @ts-ignore
window.vueState = shapeObj;
window.vueState = shapeObjects;
const flatEntries = computed(() => flattenObject(shapeObj));
</script>
<template>
<div class="vue">
<p>Rendered in Vue</p>
<template v-if="shapeObj && 'type' in shapeObj">
<!-- Direct property access -->
<input type="text" v-model="shapeObj.type" />
<input type="text" v-model="shapeObj.objectValue.nestedString" />
<!-- Property access through object recursion -->
<table
border="1"
cellpadding="5"
style="margin-top: 1rem; max-width: 100%; font-size: 0.9rem"
>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<tr
v-for="[path, value, key, parent] in flatEntries"
:key="path"
<template v-if="shapeObjects">
<template v-for="obj in shapeObjects" :key="obj">
<template v-for="flatEntries in [flattenObject(obj)]">
<!-- Property access through object recursion -->
<table
border="1"
cellpadding="5"
style="margin-top: 1rem; max-width: 100%; font-size: 0.9rem"
>
<!-- Key-->
<td style="white-space: nowrap">{{ path }}</td>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<tr
v-for="[path, value, key, parent] in flatEntries"
:key="path"
>
<!-- Key-->
<td style="white-space: nowrap">{{ path }}</td>
<!-- Value -->
<td>
<template v-if="value instanceof Set">
{{ Array.from(value).join(", ") }}
</template>
<template v-else-if="Array.isArray(value)">
[{{ value.join(", ") }}]
</template>
<template v-else>
{{ JSON.stringify(value) }}
</template>
</td>
<!-- Value -->
<td>
<template v-if="value instanceof Set">
{{ Array.from(value).join(", ") }}
</template>
<template v-else-if="Array.isArray(value)">
[{{ value.join(", ") }}]
</template>
<template v-else>
{{ JSON.stringify(value) }}
</template>
</td>
<!-- Edit -->
<td>
<!-- String editing -->
<template v-if="typeof value === 'string'">
<template v-if="path.indexOf('.') === -1">
<input
type="text"
v-model="shapeObj[key]"
/>
</template>
<template v-else>
<input
type="text"
v-bind:value="parent[key]"
v-on:input="
(e) => {
parent[key] = (
e.target as any
).value;
}
"
/>
</template>
</template>
<!-- Number editing -->
<template v-else-if="typeof value === 'number'">
<template v-if="path.indexOf('.') === -1">
<input
type="number"
v-model="shapeObj[key]"
/>
</template>
<template v-else>
<input
type="number"
v-bind:value="parent[key]"
v-on:input="
(e) => {
parent[key] = +(e.target as any)
.value;
}
"
/>
</template>
</template>
<!-- Boolean editing -->
<template v-else-if="typeof value === 'boolean'">
<template v-if="path.indexOf('.') === -1">
<input
type="checkbox"
v-model="shapeObj[key]"
/>
</template>
<template v-else>
<input
type="checkbox"
v-bind:value="value"
v-on:input="
(e) => {
parent[key] = (
e.target as any
).value;
}
"
/>
</template>
</template>
<!-- Array editing -->
<template v-else-if="Array.isArray(value)">
<template v-if="path.indexOf('.') === -1">
<div style="display: flex; gap: 0.5rem">
<button
@click="
() => {
parent[key] = [
...value,
value.length + 1,
];
}
"
>
Add
</button>
<button
@click="
() => {
parent[key] =
value.slice(1);
}
"
>
Remove
</button>
</div>
</template>
<template v-else>
<div style="display: flex; gap: 0.5rem">
<button
@click="
() => {
parent[key] = [
...value,
value.length + 1,
];
}
"
>
Add
</button>
<button
@click="
() => {
parent[key] =
value.slice(1);
}
"
>
Remove
</button>
</div>
</template>
</template>
<!-- Edit -->
<td>
<!-- String editing -->
<template v-if="typeof value === 'string'">
<template v-if="path.indexOf('.') === -1">
<input
type="text"
v-model="obj[key]"
/>
</template>
<template v-else>
<input
type="text"
v-bind:value="parent[key]"
v-on:input="
(e) => {
parent[key] = (
e.target as any
).value;
}
"
/>
</template>
</template>
<!-- Number editing -->
<template v-else-if="typeof value === 'number'">
<template v-if="path.indexOf('.') === -1">
<input
type="number"
v-model="obj[key]"
/>
</template>
<template v-else>
<input
type="number"
v-bind:value="parent[key]"
v-on:input="
(e) => {
parent[key] = +(e.target as any)
.value;
}
"
/>
</template>
</template>
<!-- Boolean editing -->
<template v-else-if="typeof value === 'boolean'">
<template v-if="path.indexOf('.') === -1">
<input
type="checkbox"
v-model="obj[key]"
/>
</template>
<template v-else>
<input
type="checkbox"
v-bind:value="value"
v-on:input="
(e) => {
parent[key] = (
e.target as any
).value;
}
"
/>
</template>
</template>
<!-- Array editing -->
<template v-else-if="Array.isArray(value)">
<template v-if="path.indexOf('.') === -1">
<div style="display: flex; gap: 0.5rem">
<button
@click="
() => {
parent[key] = [
...value,
value.length + 1,
];
}
"
>
Add
</button>
<button
@click="
() => {
parent[key] =
value.slice(1);
}
"
>
Remove
</button>
</div>
</template>
<template v-else>
<div style="display: flex; gap: 0.5rem">
<button
@click="
() => {
parent[key] = [
...value,
value.length + 1,
];
}
"
>
Add
</button>
<button
@click="
() => {
parent[key] =
value.slice(1);
}
"
>
Remove
</button>
</div>
</template>
</template>
<!-- Set editing -->
<template v-else-if="value instanceof Set">
<div style="display: flex; gap: 0.5rem">
<button
@click="
() => {
value.add(
`item${value.size + 1}`
);
}
"
>
Add
</button>
<button
@click="
() => {
const last =
Array.from(value).pop();
if (last !== undefined)
value.delete(last);
}
"
>
Remove
</button>
</div>
</template>
<template v-else> N/A </template>
</td>
</tr>
</tbody>
</table>
<!-- Set editing -->
<template v-else-if="value instanceof Set">
<div style="display: flex; gap: 0.5rem">
<button
@click="
() => {
value.add(
`item${value.size + 1}`
);
}
"
>
Add
</button>
<button
@click="
() => {
const last =
Array.from(value).pop();
if (last !== undefined)
value.delete(last);
}
"
>
Remove
</button>
</div>
</template>
<template v-else> N/A </template>
</td>
</tr>
</tbody>
</table>
</template>
</template>
</template>
<template v-else>
<p>Loading state</p>

Loading…
Cancel
Save