diff --git a/src/frontends/react/HelloWorld.tsx b/src/frontends/react/HelloWorld.tsx
index f70b3a6..9d3fc96 100644
--- a/src/frontends/react/HelloWorld.tsx
+++ b/src/frontends/react/HelloWorld.tsx
@@ -1,22 +1,25 @@
import React from "react";
-import { useSignal } from "@gn8/alien-signals-react";
-
-import {
- deepSignalExample,
- computedSignalFromExample,
-} from "src/ng-mock/js-land/signalLibs/alienSignals";
+import useShape from "../../ng-mock/js-land/frontendAdapters/react/useShape";
export function HelloWorldReact() {
- const [state] = useSignal(deepSignalExample);
- const [computed] = useSignal(computedSignalFromExample);
+ const state = useShape("Shape2", "");
+
+ window.reactState = state;
+ console.log("react render", state);
+
+ if (!state) return <>Loading state>;
return (
Hello World from React!
-
Here is a reactive object with {computed.get()} props.
-
{JSON.stringify(state, null, 4)}
-
);
diff --git a/src/frontends/svelte/HelloWorld.svelte b/src/frontends/svelte/HelloWorld.svelte
index 75bcf44..539b749 100644
--- a/src/frontends/svelte/HelloWorld.svelte
+++ b/src/frontends/svelte/HelloWorld.svelte
@@ -1,24 +1,19 @@
Hello World from Svelte!
-
Here is a reactive object with {$computed.get()} props.
{JSON.stringify($nestedObject, null, 4)}
{
- $nestedObject.array.splice(0, 1);
+ window.svelteState = $nestedObject;
+ $nestedObject.name = $nestedObject.name.toUpperCase();
}}
>
- Click to remove first array element
+ upper-case name
diff --git a/src/frontends/vue/HelloWorld.vue b/src/frontends/vue/HelloWorld.vue
index 362ef46..c1c3fc9 100644
--- a/src/frontends/vue/HelloWorld.vue
+++ b/src/frontends/vue/HelloWorld.vue
@@ -1,22 +1,29 @@
Hello World from Vue!
-
Here is a reactive object with {{computed.get()}} props.
-
{{JSON.stringify(state, null, 4)}}
-
- Click to add val
-
+
+
Type is {{shapeObj.type}} with street {{shapeObj.address.street}}
+
+
+ Click to switch name
+
+
+
+ Loading state
+
\ No newline at end of file
diff --git a/src/ng-mock/js-land/connector/applyDiff.ts b/src/ng-mock/js-land/connector/applyDiff.ts
index 4f49540..f5fb856 100644
--- a/src/ng-mock/js-land/connector/applyDiff.ts
+++ b/src/ng-mock/js-land/connector/applyDiff.ts
@@ -1,6 +1,9 @@
import type { Diff } from "../types";
/** Mock function to apply diffs. Just uses a copy of the diff as the new object. */
-export function applyDiff(currentState: object, diff: Diff): object {
- return JSON.parse(JSON.stringify(diff));
+export function applyDiff(currentState: any, diff: Diff) {
+ const clone = JSON.parse(JSON.stringify(diff));
+ Object.keys(clone).forEach((k) => {
+ currentState[k] = clone[k];
+ });
}
diff --git a/src/ng-mock/js-land/connector/ngSignals.ts b/src/ng-mock/js-land/connector/ngSignals.ts
index 8f437c0..5a8aa8a 100644
--- a/src/ng-mock/js-land/connector/ngSignals.ts
+++ b/src/ng-mock/js-land/connector/ngSignals.ts
@@ -1,31 +1,70 @@
-import handleShapeUpdate from "src/ng-mock/wasm-land/handleShapeUpdate";
+import updateShape from "src/ng-mock/wasm-land/updateShape";
import type { Connection, Diff, Scope, Shape } from "../types";
-import handleShapeRequest from "src/ng-mock/wasm-land/handleShapeRequest";
+import requestShape from "src/ng-mock/wasm-land/requestShape";
import { applyDiff } from "./applyDiff";
+import { batch, deepSignal, watch } from "alien-deepsignals";
+import { signal } from "alien-signals";
-export async function createSignalObjectForShape(shape: Shape, scope?: Scope) {
- const ret: {
- state?: any;
- connectionId?: Connection["id"];
- update: (diff: Diff) => Promise;
- } = {
- async update(diff) {
- if (!ret.connectionId)
- throw new Error("Connection not established yet for shape" + shape);
- await handleShapeUpdate(ret.connectionId, diff);
- },
+// TODO: The code is horrible.
+export function createSignalObjectForShape(shape: Shape, scope?: Scope) {
+ // TODO:
+ // DeepSignal has a different API to alien-signals.
+ // Therefore, we need to create a "root signal" wrapper that is
+ // triggered on deepSignal changes.
+ const rootSignal = signal(null);
+
+ // State
+ let stopWatcher: any = null;
+ let suspendWatcher = false;
+
+ // To update the root signal
+ const setUpDeepSignal = (
+ newSignal: ReturnType,
+ connectionId: Connection["id"]
+ ) => {
+ stopWatcher?.();
+
+ // Notify DB on changes.
+ stopWatcher = watch(
+ newSignal,
+ (newVal, oldVal, onCleanup) => {
+ if (!suspendWatcher) updateShape(connectionId, newVal);
+ },
+ { deep: true }
+ );
+
+ // Update the root signal.
+ rootSignal(newSignal);
};
- const onDbUpdate = (diff: Diff) => {
- ret.state = applyDiff(ret.state || {}, diff);
+ const onUpdateFromDb = (diff: Diff, connectionId: Connection["id"]) => {
+ const nestedObj = rootSignal();
+ console.log("Update received", connectionId, diff);
+ // Set new value from applying the diffs to the old value.
+
+ // suspendWatcher = true;
+ // We need to replace the root signal for now, so this is redundant.
+ // batch(() => {
+ // if (!nestedObj) return; // This shouldn't happen but we make the compiler happy.
+ // applyDiff(nestedObj, diff);
+ // });
+ // suspendWatcher = false;nestedObj
+
+ // Create a new deep signal.
+ // We need to do that because the deepSignals ref hasn't changed otherwise
+ // and no update is triggered.
+ const newDeepSignal = deepSignal(JSON.parse(JSON.stringify(diff)));
+ setUpDeepSignal(newDeepSignal, connectionId);
};
- await handleShapeRequest(shape, onDbUpdate).then(
+ // Do the actual db request.
+ requestShape(shape, scope, onUpdateFromDb).then(
({ connectionId, shapeObject }) => {
- ret.state = shapeObject;
- ret.connectionId = connectionId;
+ // Create a deepSignal to put into the vanilla alien-signal.
+ const deepSignalFromDb = deepSignal(shapeObject);
+ setUpDeepSignal(deepSignalFromDb, connectionId);
}
);
- return ret;
+ return rootSignal;
}
diff --git a/src/ng-mock/js-land/frontendAdapters/react/useShape.ts b/src/ng-mock/js-land/frontendAdapters/react/useShape.ts
new file mode 100644
index 0000000..84975af
--- /dev/null
+++ b/src/ng-mock/js-land/frontendAdapters/react/useShape.ts
@@ -0,0 +1,19 @@
+import { useSignal } from "@gn8/alien-signals-react";
+import { useMemo } from "react";
+import { createSignalObjectForShape } from "src/ng-mock/js-land/connector/ngSignals";
+import type { Scope, Shape } from "src/ng-mock/js-land/types";
+
+const useShape = (shape: Shape, scope: Scope) => {
+ const signalOfShape = useMemo(() => {
+ console.log("react memo called...");
+ return createSignalObjectForShape(shape, scope);
+ }, [shape, scope]);
+
+ const [shapeObject, setShapeObject] = useSignal(signalOfShape);
+
+ // We don't need the setter.
+ // The object is recursively proxied and changes are recorded there.
+ return shapeObject;
+};
+
+export default useShape;
diff --git a/src/ng-mock/js-land/frontendAdapters/svelte/useShape.ts b/src/ng-mock/js-land/frontendAdapters/svelte/useShape.ts
new file mode 100644
index 0000000..3f93157
--- /dev/null
+++ b/src/ng-mock/js-land/frontendAdapters/svelte/useShape.ts
@@ -0,0 +1,13 @@
+import { useSignal } from "@gn8/alien-signals-svelte";
+import { createSignalObjectForShape } from "src/ng-mock/js-land/connector/ngSignals";
+import type { Scope, Shape } from "src/ng-mock/js-land/types";
+
+const useShape = (shape: Shape, scope: Scope) => {
+ const signalOfShape = createSignalObjectForShape(shape, scope);
+
+ const writeableStoreForShape = useSignal(signalOfShape);
+
+ return writeableStoreForShape;
+};
+
+export default useShape;
diff --git a/src/ng-mock/js-land/frontendAdapters/vue/useShape.ts b/src/ng-mock/js-land/frontendAdapters/vue/useShape.ts
new file mode 100644
index 0000000..e5eacbe
--- /dev/null
+++ b/src/ng-mock/js-land/frontendAdapters/vue/useShape.ts
@@ -0,0 +1,13 @@
+import { useSignal } from "@gn8/alien-signals-vue";
+import { createSignalObjectForShape } from "src/ng-mock/js-land/connector/ngSignals";
+import type { Scope, Shape } from "src/ng-mock/js-land/types";
+
+const useShape = (shape: Shape, scope: Scope) => {
+ const signalOfShape = createSignalObjectForShape(shape, scope);
+
+ const refOfShape = useSignal(signalOfShape);
+
+ return refOfShape;
+};
+
+export default useShape;
diff --git a/src/ng-mock/js-land/signalLibs/alienSignals.ts b/src/ng-mock/js-land/signalLibs/alienSignals.ts
deleted file mode 100644
index 3175291..0000000
--- a/src/ng-mock/js-land/signalLibs/alienSignals.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { deepSignal, watch, computed } from "alien-deepsignals";
-import { signal } from "alien-signals";
-
-const deepSignalExample = deepSignal({
- count: 0,
- name: "John",
- nested: {
- deep: "value",
- },
- array: [1, 2, 3],
- set: new Set(["el1"]),
-});
-
-const computedSignalFromExample = computed(() => {
- return Object.keys(deepSignalExample).length;
-});
-
-/** Create a vanilla alien-signals Signal so that it can be normally used by universe-alien-signals. */
-const wrapDeepIntoAlienSignal = (
- nestedSignal: ReturnType>
-) => {
- const alienSignal = signal(nestedSignal);
-
- // Watch deep signal and notify vanilla alien-signal on changes.
- watch(
- nestedSignal,
- (value) => {
- // We need to destructure because the object otherwise remained the same.
- alienSignal({ ...nestedSignal });
- },
- { deep: true }
- );
-
- return alienSignal;
-};
-
-const wrappedDeepSignalState = wrapDeepIntoAlienSignal(deepSignalExample);
-const wrappedComputedPropertiesOfObj = wrapDeepIntoAlienSignal(
- computedSignalFromExample
-);
-
-export {
- wrappedDeepSignalState as deepSignalExample,
- wrappedComputedPropertiesOfObj as computedSignalFromExample,
-};
diff --git a/src/ng-mock/tests/subscriptionToStore.test.ts b/src/ng-mock/tests/subscriptionToStore.test.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/ng-mock/tests/updatesWithWasm.test.ts b/src/ng-mock/tests/updatesWithWasm.test.ts
index 65abdd9..686b40b 100644
--- a/src/ng-mock/tests/updatesWithWasm.test.ts
+++ b/src/ng-mock/tests/updatesWithWasm.test.ts
@@ -1,23 +1,30 @@
import { expect, test } from "vitest";
import { createSignalObjectForShape } from "../js-land/connector/ngSignals";
+const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+// TODO: Redo
test("shape object notification comes back to others", async () => {
- const object1 = await createSignalObjectForShape("Shape1");
- const object2 = await createSignalObjectForShape("Shape1");
+ const object1 = createSignalObjectForShape("Shape1");
+ const object2 = createSignalObjectForShape("Shape1");
+
+ const object3 = createSignalObjectForShape("Shape2");
+ const object4 = createSignalObjectForShape("Shape2");
- const object3 = await createSignalObjectForShape("Shape2");
- const object4 = await createSignalObjectForShape("Shape2");
+ wait(50);
// Update object 1 and expect object 2 to update as well.
- await object1.update({ name: "Updated name from object1" });
+ object1()!.name = "Updated name from object1";
- expect(object2.state?.name).toBe("Updated name from object1");
+ wait(30);
+ expect(object2()!.name).toBe("Updated name from object1");
// Expect object of different shape not to have changed.
- expect(object3.state?.name).toBe("Niko's cat");
+ expect(object3().name).toBe("Niko's cat");
// Update object 4 and expect object 3 with same shape to have updated.
- await object4.update({ name: "Updated name from object4" });
+ object4()!.name = "Updated name from object4";
- expect(object3.state?.name).toBe("Updated name from object4");
+ wait(30);
+ expect(object3()!.name).toBe("Updated name from object4");
});
diff --git a/src/ng-mock/wasm-land/handleShapeRequest.ts b/src/ng-mock/wasm-land/requestShape.ts
similarity index 82%
rename from src/ng-mock/wasm-land/handleShapeRequest.ts
rename to src/ng-mock/wasm-land/requestShape.ts
index 2530e3a..10e31af 100644
--- a/src/ng-mock/wasm-land/handleShapeRequest.ts
+++ b/src/ng-mock/wasm-land/requestShape.ts
@@ -1,6 +1,5 @@
-import { randomUUID } from "crypto";
import * as shapeManager from "./shapeManager";
-import type { WasmConnection, Diff } from "./types";
+import type { WasmConnection, Diff, Scope } from "./types";
import type { Shape } from "../js-land/types";
const mockShapeObject1 = {
@@ -20,19 +19,21 @@ const mockShapeObject2 = {
numberOfHomes: 3,
address: {
street: "Niko's street",
- compartment: 2,
+ houseNumber: "15",
+ floor: 0,
},
};
-export default async function handleShapeRequest(
+export default async function requestShape(
shape: Shape,
+ scope: Scope | undefined,
callback: (diff: Diff, connectionId: WasmConnection["id"]) => void
): Promise<{
connectionId: string;
shapeObject: object;
}> {
const connection: WasmConnection = {
- id: randomUUID(),
+ id: Math.random().toFixed(4),
shape,
// Create a deep copy to prevent accidental by-reference changes.
state: JSON.parse(
diff --git a/src/ng-mock/wasm-land/handleShapeUpdate.ts b/src/ng-mock/wasm-land/updateShape.ts
similarity index 82%
rename from src/ng-mock/wasm-land/handleShapeUpdate.ts
rename to src/ng-mock/wasm-land/updateShape.ts
index 4caad93..c713667 100644
--- a/src/ng-mock/wasm-land/handleShapeUpdate.ts
+++ b/src/ng-mock/wasm-land/updateShape.ts
@@ -1,13 +1,15 @@
import * as shapeManager from "./shapeManager";
import type { WasmConnection, Diff } from "./types";
-export default async function handleShapeUpdate(
+export default async function updateShape(
connectionId: WasmConnection["id"],
diff: Diff
) {
const connection = shapeManager.connections.get(connectionId);
if (!connection) throw new Error("No Connection found.");
+ console.log("BACKEND: Received update request from ", connectionId);
+
const newState = shapeManager.applyDiff(connection.state, diff);
connection.state = newState;