diff --git a/bun.lockb b/bun.lockb
index e306713..7fefa9a 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 36cbb0a..6d5eb95 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
- "start": "vinxi start"
+ "start": "vinxi start",
+ "test": "vitest"
},
"author": "Dev Agrawal (http://devagr.me/)",
"dependencies": {
@@ -16,7 +17,10 @@
"@solid-primitives/websocket": "^1.2.2",
"@solidjs/router": "^0.14.10",
"@solidjs/start": "^1.0.9",
+ "immer": "^10.1.1",
"rxjs": "^7.8.1",
+ "seroval": "^1.2.0",
+ "solid-events": "^0.0.5",
"solid-icons": "^1.1.0",
"solid-js": "^1.9.2",
"unique-names-generator": "^4.7.1",
@@ -30,6 +34,7 @@
"@babel/preset-typescript": "^7.26.0",
"@vinxi/plugin-directives": "^0.4.3",
"babel-plugin-transform-import-paths": "^1.0.3",
- "vite-plugin-babel": "^1.2.0"
+ "vite-plugin-babel": "^1.2.0",
+ "vitest": "^3.0.4"
}
}
\ No newline at end of file
diff --git a/socket/__tests/serializer.test.ts b/socket/__tests/serializer.test.ts
new file mode 100644
index 0000000..7f54a6a
--- /dev/null
+++ b/socket/__tests/serializer.test.ts
@@ -0,0 +1,38 @@
+import { createPlugin, serialize } from "seroval";
+import { describe, expect, test } from "vitest";
+
+type FunctionNode = {
+ __type: "ref";
+ id: string;
+};
+const refPlugin = (map: Map) =>
+ createPlugin({
+ tag: "seroval-plugins/socket/ref",
+ test(value) {
+ return typeof value === "function";
+ },
+ parse: {
+ sync(value, ctx) {
+ const id = Math.random().toString();
+ map.set(id, value);
+ return { __type: "ref", id };
+ },
+ },
+ deserialize(node, ctx) {
+ return map.get(node.id)!;
+ },
+ serialize(node, ctx) {
+ return `node`;
+ },
+ });
+
+describe("serializer", () => {
+ test(`serializes functions`, () => {
+ const source = { id: 1, greet: () => "Hello" };
+ const serialized = serialize(source, {
+ plugins: [refPlugin],
+ });
+ console.log(serialized);
+ expect(true).toBe(true);
+ });
+});
diff --git a/socket/events/v2.ts b/socket/events/v2.ts
new file mode 100644
index 0000000..a1be1f7
--- /dev/null
+++ b/socket/events/v2.ts
@@ -0,0 +1,325 @@
+import { assert } from "console";
+import {
+ createEvent,
+ createSubject,
+ createSubjectStore,
+ createTopic,
+ Emitter,
+ Handler,
+} from "solid-events";
+import { createMemo, createRenderEffect, createSignal } from "solid-js";
+import { createStore } from "solid-js/store";
+import { Todo } from "~/components/todos";
+import {
+ TodoCreated,
+ TodoDeleted,
+ TodoEdited,
+ TodoEvent,
+ TodoToggled,
+} from "~/lib/todos";
+
+// export const createEventLog = () => {
+// const [onEvent, emitEvent] = createEvent()
+// const log = createSubjectStore(
+// () => [],
+// onEvent((e) => (log) => log.push(e))
+// );
+
+// return { log, onEvent, emitEvent };
+// };
+
+type LogEvent = {
+ seq: number;
+ data: E;
+};
+
+export const createLog = (props: { onEvent: Handler> }) => {
+ const [_log, setLog] = createStore<{ events: LogEvent[] }>({ events: [] });
+
+ props.onEvent((event) => {});
+
+ const log = createSubjectStore(
+ () => [],
+ props.onEvent((e) => (log) => log.push(e))
+ );
+
+ return { log, onEvent: props.onEvent };
+};
+
+export const createServerLog = (props: { onEvent: Handler }) => {
+ const log = createSubjectStore(
+ () => [],
+ props.onEvent((e) => (log) => log.push(e))
+ );
+
+ return { log, onEvent: props.onEvent };
+};
+
+// export const createLogProjection = (
+// init: () => T,
+// ...events: Array<(prev: T) => void>>
+// ) => {
+// const actualEvents
+// return createSubjectStore(
+// init,
+// ...events.map((onEvent) =>
+// onEvent((mutation) => {
+
+// return mutation;
+// })
+// )
+// );
+// };
+
+// const [onTodoEvent, emitTodoEvent] = createTopic();
+
+// const [todoId, setTodoId] = createSignal();
+// onTodoEvent(
+// () => `todos.${todoId()}`,
+// (todoEvent) => {}
+// );
+
+const [counter, setCounter] = createSignal(0);
+
+const [onCounter, emitCounter] = createEvent();
+
+onCounter((count) => {
+ if (count) {
+ emitCounter(count - 1);
+ }
+});
+onCounter(setCounter);
+
+onCounter(console.log);
+
+// onCounter(setCounter);
+
+emitCounter(1);
+flushSync();
+// assert(counter() === 10);
+// assert(counter() === 0);
+
+const [onTodoEvent, emitTodoEvent] = createEvent();
+
+const todos1 = createSubjectStore(
+ () => [] as Todo[],
+ onTodoEvent((e) => (todos) => {
+ if (e.type === "todo-added") {
+ const todo = todos.find((t) => t.id === e.id);
+ if (!todo) todos.push({ id: e.id, title: e.title, completed: false });
+ }
+ if (e.type === "todo-toggled") {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.completed = !todo.completed;
+ }
+ if (e.type === "todo-deleted") {
+ const index = todos.findIndex((note) => note.id === e.id);
+ if (index !== -1) todos.splice(index, 1);
+ }
+ if (e.type === "todo-edited") {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.title = e.title;
+ }
+ })
+);
+
+const [onTodoAdded, emitTodoAdded] = createEvent();
+const [onTodoToggled, emitTodoToggled] = createEvent();
+const [onTodoDeleted, emitTodoDeleted] = createEvent();
+const [onTodoEdited, emitTodoEdited] = createEvent();
+
+const todos2 = createSubjectStore(
+ () => [] as Todo[],
+ onTodoAdded((e) => (todos) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (!todo) todos.push({ id: e.id, title: e.title, completed: false });
+ }),
+ onTodoToggled((e) => (todos) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.completed = !todo.completed;
+ }),
+ onTodoDeleted((e) => (todos) => {
+ const index = todos.findIndex((note) => note.id === e.id);
+ if (index !== -1) todos.splice(index, 1);
+ }),
+ onTodoEdited((e) => (todos) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.title = e.title;
+ })
+);
+
+const [onTodoEvents, emitTodoEvents] = createEvent<{
+ Added: [Handler, Emitter];
+ Toggled: [Handler, Emitter];
+ Deleted: [Handler, Emitter];
+ Edited: [Handler, Emitter];
+}>();
+
+const Added = createEvent(),
+ Toggled = createEvent(),
+ Deleted = createEvent(),
+ Edited = createEvent();
+
+emitTodoEvents({ Added, Toggled, Deleted, Edited });
+
+const todos3 = createSubjectStore(
+ () => [] as Todo[],
+ onTodoEvents((events) => (todos) => {
+ events.Added[0]((e) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (!todo) todos.push({ id: e.id, title: e.title, completed: false });
+ });
+ events.Toggled[0]((e) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.completed = !todo.completed;
+ });
+ events.Deleted[0]((e) => {
+ const index = todos.findIndex((note) => note.id === e.id);
+ if (index !== -1) todos.splice(index, 1);
+ });
+ events.Edited[0]((e) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.title = e.title;
+ });
+ })
+);
+
+const [onTodoTopic, emitTodoTopic] = createTopic<{
+ Added: TodoCreated;
+ Toggled: TodoToggled;
+ Deleted: TodoDeleted;
+ Edited: TodoEdited;
+}>();
+
+const todos4 = createSubjectStore(
+ () => [] as Todo[],
+ onTodoTopic((events) => (todos) => {
+ events.Added((e) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (!todo) todos.push({ id: e.id, title: e.title, completed: false });
+ });
+ events.Toggled((e) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.completed = !todo.completed;
+ });
+ events.Deleted((e) => {
+ const index = todos.findIndex((note) => note.id === e.id);
+ if (index !== -1) todos.splice(index, 1);
+ });
+ events.Edited((e) => {
+ const todo = todos.find((t) => t.id === e.id);
+ if (todo) todo.title = e.title;
+ });
+ })
+);
+
+emitTodoTopic({ Added: {} });
+emitTodoTopic({ Added: {} });
+
+const todoId = () => 0;
+
+const firstTodo1 = createSubject(
+ null,
+ onTodoEvent((e) => (todo) => {
+ if (e.type === "todo-added" && e.id === todoId()) {
+ return { ...e, completed: false };
+ }
+ if (todo) {
+ if (e.type === "todo-toggled" && e.id === todoId()) {
+ return { ...todo, completed: !todo.completed };
+ }
+ if (e.type === "todo-deleted") {
+ if (e.id === todoId()) return null;
+ }
+ if (e.type === "todo-edited") {
+ if (e.id === todoId()) return { ...todo, title: e.title };
+ }
+ }
+ return todo;
+ })
+);
+
+const firstTodo2 = createMemo(() => {
+ const id = todoId();
+
+ return createSubject(
+ null,
+ onTodoEvent((e) => (todo) => {
+ if (e.type === "todo-added" && e.id === id) {
+ return { ...e, completed: false };
+ }
+ if (todo) {
+ if (e.type === "todo-toggled" && e.id === id) {
+ return { ...todo, completed: !todo.completed };
+ }
+ if (e.type === "todo-deleted") {
+ if (e.id === id) return null;
+ }
+ if (e.type === "todo-edited") {
+ if (e.id === id) return { ...todo, title: e.title };
+ }
+ }
+ return todo;
+ })
+ );
+});
+
+type TodoTopic = {
+ Added: Record;
+ Toggled: Record;
+ Deleted: Record;
+ Edited: Record;
+};
+const [onTodosTopic, emitTodosTopic] = createEvent();
+
+const firstTodo3 = createSubject(
+ null,
+ onTodosTopic.Added((id: number, e: TodoCreated) => (todo) => {
+ if (id === todoId()) return { ...e, completed: false };
+ return todo;
+ }),
+ onTodosTopic.Toggled((id: number, e: TodoToggled) => (todo) => {
+ if (id === todoId()) return { ...todo, completed: !todo.completed };
+ return todo;
+ })
+);
+
+const firstTodo5 = createSubject(
+ null,
+ onTodosTopic("Added", id, (e: TodoCreated) => (todo) => {
+ return { ...e, completed: false };
+ }),
+ onTodosTopic("Toggled", id, (e: TodoToggled) => (todo) => {
+ return { ...todo, completed: !todo.completed };
+ })
+);
+
+const onToggledTodos = onTodosTopic("Toggled");
+
+const onToggledMyTodo1 = onToggledTodos(id);
+const onToggledMyTodo2 = onTodosTopic("Toggled", id);
+
+const onToggledTodoIds1 = onToggledTodos((e: Record) =>
+ Object.keys(e)
+);
+const onToggledTodoIds2 = onTodosTopic(
+ "Toggled",
+ (e: Record) => Object.keys(e)
+);
+
+const onToggledMyTodoId1 = onToggledMyTodo1((e) => e.id);
+const onToggledMyTodoId2 = onToggledTodos(id, (e) => e.id);
+const onToggledMyTodoId3 = onTodosTopic("Toggled", id, (e) => e.id);
+
+emitTodosTopic("Added", 0, {});
+emitTodosTopic("Toggled", 3, {});
+emitTodosTopic("Added", { 0: {}, 1: {} });
+emitTodosTopic({ Added: { 1: {} }, Deleted: { 0: {} } });
+
+const [nextTopic] = createTopic((emit) => {
+ onTodoAdded((todoAdded) => {
+ emit("Added", todoAdded.id, todoAdded);
+ });
+});
+
+const [onLocalEvent, emitLocalEvent] = createEvent(onServerEvent);
diff --git a/socket/lib/client.tsx b/socket/lib/client.tsx
index a34bb10..f489c75 100644
--- a/socket/lib/client.tsx
+++ b/socket/lib/client.tsx
@@ -1,30 +1,22 @@
-import { from as rxFrom, Observable } from "rxjs";
+import { createLazyMemo } from "@solid-primitives/memo";
+import { createWS } from "@solid-primitives/websocket";
+import { createAsync } from "@solidjs/router";
+import { applyPatches, Patch } from "immer";
+import { fromJSON, SerovalJSON, toJSON } from "seroval";
+import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
+import { createStore, produce } from "solid-js/store";
+import {
+ deserializeReactivePayload,
+ serializeReactivePayload,
+} from "./serializer";
import {
- createSeriazliedMemo,
SerializedMemo,
SerializedProjection,
SerializedRef,
- SerializedStoreAccessor,
- SerializedThing,
WsMessage,
WsMessageDown,
WsMessageUp,
} from "./shared";
-import {
- Accessor,
- createComputed,
- createEffect,
- createMemo,
- createSignal,
- from,
- getListener,
- onCleanup,
- untrack,
-} from "solid-js";
-import { createAsync } from "@solidjs/router";
-import { createLazyMemo } from "@solid-primitives/memo";
-import { createCallback } from "@solid-primitives/rootless";
-import { createWS } from "@solid-primitives/websocket";
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
const wsUrl = `${protocol}://${window.location.hostname}:${window.location.port}/_ws`;
@@ -37,200 +29,164 @@ export type SimpleWs = {
send(data: string): void;
};
-function wsRpc(message: WsMessageUp) {
+function wsRpc(message: WsMessageUp) {
const ws = getWs();
const id = crypto.randomUUID() as string;
- return new Promise<{ value: T; dispose: () => void }>(async (res, rej) => {
- function dispose() {
- ws.send(
- JSON.stringify({ type: "dispose", id } satisfies WsMessage)
- );
- }
-
- function handler(event: { data: string }) {
- // console.log(`handler ${id}`, message, { data: event.data });
- const data = JSON.parse(event.data) as WsMessage>;
- if (data.id === id && data.type === "value") {
- res({ value: data.value, dispose });
- ws.removeEventListener("message", handler);
+ return new Promise<{ value: SerovalJSON; dispose: () => void }>(
+ async (res, rej) => {
+ function dispose() {
+ ws.send(
+ JSON.stringify({
+ type: "dispose",
+ id,
+ } satisfies WsMessage)
+ );
}
- }
- ws.addEventListener("message", handler);
- ws.send(
- JSON.stringify({ ...message, id } satisfies WsMessage)
- );
- });
-}
-
-function wsSub(message: WsMessageUp) {
- const ws = getWs();
- const id = crypto.randomUUID();
-
- return rxFrom(
- new Observable((obs) => {
- // console.log(`attaching sub handler`);
function handler(event: { data: string }) {
- const data = JSON.parse(event.data) as WsMessage>;
- // console.log(`data`, data, id);
- if (data.id === id && data.type === "value") obs.next(data.value);
+ // console.log(`handler ${id}`, message, { data: event.data });
+ const data = JSON.parse(event.data) as WsMessage;
+ if (data.id === id && data.type === "value") {
+ res({ value: data.value, dispose });
+ ws.removeEventListener("message", handler);
+ }
}
ws.addEventListener("message", handler);
ws.send(
JSON.stringify({ ...message, id } satisfies WsMessage)
);
-
- return () => {
- // console.log(`detaching sub handler`);
- ws.removeEventListener("message", handler);
- };
- })
+ }
);
}
-export function createRef(ref: SerializedRef) {
- return (...input: any[]) =>
- wsRpc({
- type: "invoke",
- ref,
- input,
- }).then(({ value }) => value);
+// function wsSub(message: WsMessageUp) {
+// const ws = getWs();
+// const id = crypto.randomUUID();
+
+// return rxFrom(
+// new Observable((obs) => {
+// // console.log(`attaching sub handler`);
+// function handler(event: { data: string }) {
+// const data = JSON.parse(event.data) as WsMessage;
+// // console.log(`data`, data, id);
+// if (data.id === id && data.type === "value") obs.next(data.value);
+// }
+
+// ws.addEventListener("message", handler);
+// ws.send(
+// JSON.stringify({ ...message, id } satisfies WsMessage)
+// );
+
+// return () => {
+// // console.log(`detaching sub handler`);
+// ws.removeEventListener("message", handler);
+// };
+// })
+// );
+// }
+
+function createSocketRefConsumer(ref: SerializedRef) {
+ return async (...payload: I) => {
+ const input = toJSON(payload);
+ const { value } = await wsRpc({ type: "invoke", ref, input });
+ return fromJSON(value);
+ };
}
-export function createSocketMemoConsumer(ref: SerializedMemo) {
- // console.log({ ref });
- const memo = createLazyMemo(
- () =>
- from(
- wsSub({
- type: "subscribe",
- ref,
- })
- ),
- () => ref.initial
- );
+function createSocketMemoConsumer(ref: SerializedMemo) {
+ const [signal, setSignal] = createSignal(ref.initial);
- return () => {
- const memoValue = memo()();
- // console.log({ memoValue });
- return memoValue;
- };
+ const ws = getWs();
+ function handler(event: { data: string }) {
+ const data = JSON.parse(event.data) as WsMessage;
+ if (data.type === "value" && data.id === ref.id) {
+ setSignal(() => fromJSON(data.value));
+ }
+ }
+ ws.addEventListener("message", handler);
+
+ onCleanup(() => {
+ ws.removeEventListener("message", handler);
+ });
+
+ return signal;
}
-export function createSocketProjectionConsumer(
- ref: SerializedProjection | SerializedStoreAccessor
+function createSocketProjectionConsumer(
+ ref: SerializedProjection
) {
- const nodes = [] as { path: string; accessor: Accessor }[];
-
- function getNode(path: string) {
- const node = nodes.find((node) => node.path === path);
- if (node) return node;
- const newNode = {
- path,
- accessor: from(wsSub({ type: "subscribe", ref, path })),
- };
- nodes.push(newNode);
- return newNode;
+ const [store, setStore] = createStore(ref.initial!);
+
+ const ws = getWs();
+ function handler(event: { data: string }) {
+ const data = JSON.parse(event.data) as WsMessage;
+ if (data.type === "value" && data.id === ref.id) {
+ setStore(
+ produce((draft) => {
+ applyPatches(draft, fromJSON(data.value));
+ })
+ );
+ }
}
+ ws.addEventListener("message", handler);
- // @ts-expect-error
- return new Proxy(ref.initial || {}, {
- get(target, path: string) {
- return getListener()
- ? getNode(path).accessor()
- : ((target as any)[path] as O);
- },
+ onCleanup(() => {
+ ws.removeEventListener("message", handler);
});
-}
-type SerializedValue = SerializedThing | Record;
-
-const deserializeValue = (value: SerializedValue) => {
- if (value.__type === "ref") {
- return createRef(value);
- } else if (value.__type === "memo") {
- return createSocketMemoConsumer(value);
- } else if (value.__type === "projection") {
- return createSocketProjectionConsumer(value);
- } else {
- return Object.entries(value).reduce((res, [name, value]) => {
- return {
- ...res,
- [name]:
- value.__type === "ref"
- ? createRef(value)
- : value.__type === "memo"
- ? createSocketMemoConsumer(value)
- : value.__type === "projection"
- ? createSocketProjectionConsumer(value)
- : value.__type === "store-accessor"
- ? createSocketProjectionConsumer(value)
- : value,
- };
- }, {} as any);
- }
-};
+ return store;
+}
-export function createEndpoint(name: string, input?: any) {
+export function createEndpoint(name: string, rawInput?: any) {
const inputScope = crypto.randomUUID();
- const serializedInput =
- input?.type === "memo"
- ? createSeriazliedMemo({
- name: `input`,
- scope: inputScope,
- initial: untrack(input),
- })
- : input;
+ const {
+ value: input,
+ refs,
+ signals,
+ } = serializeReactivePayload(inputScope, rawInput);
// console.log({ serializedInput });
- const scopePromise = wsRpc({
- type: "create",
- name,
- input: serializedInput,
- });
-
- if (input?.type === "memo") {
- const [inputSignal, setInput] = createSignal(input());
- createComputed(() => setInput(input()));
-
- const onSubscribe = createCallback(
- (ws: SimpleWs, data: WsMessage>) => {
- createEffect(() => {
- const value = inputSignal();
- // console.log(`sending input update to server`, value, input);
- ws.send(
- JSON.stringify({
- type: "value",
- id: data.id,
- value,
- } satisfies WsMessage)
- );
- });
- }
- );
+ const scopePromise = wsRpc({ type: "create", name, input });
- const ws = getWs();
- function handler(event: { data: string }) {
- const data = JSON.parse(event.data) as WsMessage>;
+ const ws = getWs();
+ signals.forEach((signal, id) => {
+ createEffect(() => {
+ ws.send(JSON.stringify({ type: "value", id, value: signal() }));
+ });
+ });
- if (data.type === "subscribe" && data.ref.scope === inputScope) {
- onSubscribe(ws, data);
+ async function refHandler(event: { data: string }) {
+ const data = JSON.parse(event.data) as WsMessage;
+ if (data.type === "invoke" && data.ref.scope === inputScope) {
+ const fn = refs.get(data.ref.id);
+ if (fn) {
+ const fnInput = fromJSON(data.input);
+ const arified = Array.isArray(fnInput) ? fnInput : [fnInput];
+ const res = await fn(...arified);
+ const value = toJSON(res);
+ ws.send(JSON.stringify({ type: "value", id: data.id, value }));
}
}
- ws.addEventListener("message", handler);
- onCleanup(() => ws.removeEventListener("message", handler));
}
+ ws.addEventListener("message", refHandler);
onCleanup(() => {
// console.log(`cleanup endpoint`);
+ ws.removeEventListener("message", refHandler);
scopePromise.then(({ dispose }) => dispose());
});
const scope = createAsync(() => scopePromise);
const deserializedScope = createMemo(
- () => scope() && deserializeValue(scope()!.value)
+ () =>
+ scope() &&
+ deserializeReactivePayload(scope()!.value, {
+ createSocketMemoConsumer,
+ createSocketRefConsumer,
+ createSocketProjectionConsumer,
+ })
);
return new Proxy((() => {}) as any, {
diff --git a/socket/lib/serializer.tsx b/socket/lib/serializer.tsx
new file mode 100644
index 0000000..6e23a13
--- /dev/null
+++ b/socket/lib/serializer.tsx
@@ -0,0 +1,128 @@
+import { enablePatches, produce as immerProduce, Patch } from "immer";
+import { createPlugin, fromJSON, SerovalJSON, toJSON } from "seroval";
+import { Accessor, createMemo } from "solid-js";
+import {
+ createSeriazliedMemo,
+ createSeriazliedProjection,
+ createSeriazliedRef,
+ SerializedMemo,
+ SerializedMemoClass,
+ SerializedProjection,
+ SerializedProjectionClass,
+ SerializedRef,
+ SerializedRefClass,
+} from "./shared";
+enablePatches();
+
+export function serializeReactivePayload(scope: string, input: any) {
+ const refs = new Map();
+ const signals = new Map();
+
+ const value = toJSON(input, {
+ plugins: [
+ createPlugin({
+ tag: "seroval-plugins/socket/ref",
+ test: (value) => value instanceof SerializedRefClass,
+ parse: {
+ sync(value) {
+ const id = crypto.randomUUID();
+ refs.set(id, value.handler);
+ return createSeriazliedRef({ scope, id });
+ },
+ },
+ serialize: () => ``,
+ deserialize: () => ({} as any),
+ }),
+ createPlugin({
+ tag: "seroval-plugins/socket/memo",
+ test: (value: any) => value instanceof SerializedMemoClass,
+ parse: {
+ sync(value) {
+ const id = crypto.randomUUID();
+ signals.set(id, value.signal);
+ return createSeriazliedMemo({ scope, id });
+ },
+ },
+ serialize: () => ``,
+ deserialize: () => ({} as any),
+ }),
+ createPlugin({
+ tag: "seroval-plugins/socket/projection",
+ test: (value: any) => value instanceof SerializedProjectionClass,
+ parse: {
+ sync(store) {
+ const id = crypto.randomUUID();
+
+ const projection = createMemo(
+ ({ state, changes: _c }) => {
+ let changes = [] as Patch[];
+ const s = immerProduce(
+ state,
+ store.mutation as any,
+ (patches) => {
+ changes.push(...patches);
+ }
+ );
+ return { state: s, changes };
+ },
+ { state: store.init, changes: [] as Patch[] }
+ );
+
+ signals.set(id, () => projection().changes);
+ return createSeriazliedProjection({
+ scope,
+ id,
+ initial: store.init,
+ });
+ },
+ },
+ serialize: () => ``,
+ deserialize: () => ({} as any),
+ }),
+ ],
+ });
+
+ return { value, refs, signals };
+}
+
+export function deserializeReactivePayload(
+ value: SerovalJSON,
+ plugins: {
+ createSocketRefConsumer(
+ ref: SerializedRef
+ ): (...payload: I) => Promise;
+ createSocketMemoConsumer(
+ ref: SerializedMemo
+ ): Accessor;
+ createSocketProjectionConsumer(
+ ref: SerializedProjection
+ ): O;
+ }
+) {
+ console.log({ value });
+ return fromJSON(value, {
+ plugins: [
+ createPlugin({
+ tag: "seroval-plugins/socket/ref",
+ test: () => true,
+ parse: {},
+ serialize: () => ``,
+ deserialize: plugins.createSocketRefConsumer,
+ }),
+ createPlugin({
+ tag: "seroval-plugins/socket/memo",
+ test: () => true,
+ parse: {},
+ serialize: () => ``,
+ deserialize: plugins.createSocketMemoConsumer,
+ }),
+ createPlugin