Solid
No extra package: a Solid store mirrors the pulse, and reconcile updates only the fields that changed. Every effect and DOM node depends on exactly what it reads.
The definition
import {
array, createWebSocketConnector, definePulse, object,
type ModelFromDefinition, type Pulse,
} from "pulsify-core";
export const Note = definePulse("Note", object({
title: "string",
tags: array("string"),
author: object({ name: "string" }),
})).materialize();
export type NotePulse = Pulse<ModelFromDefinition<typeof Note>>;
export const remote = createWebSocketConnector("wss://sync.example.com/sync");
The helper
import { onCleanup } from "solid-js";
import { createStore, reconcile } from "solid-js/store";
import { snapshotPulse, subscribePulse, type Pulse } from "pulsify-core";
/** A read-only store that follows the pulse. Write to the pulse itself. */
export function createPulseStore<P extends Pulse<any>>(pulse: P) {
const [store, setStore] = createStore(snapshotPulse(pulse));
onCleanup(subscribePulse(pulse, (p) => setStore(reconcile(snapshotPulse(p)))));
return store;
}
Each local or remote change takes a snapshot and reconciles it into the store. Rich text reads as its plain text, a sub-pulse as its ID. The cost is one snapshot per change; split very large documents into sub-pulses.
Render and edit
import { For } from "solid-js";
import { createPulseStore } from "./createPulseStore";
import type { NotePulse } from "./note";
export function Editor(props: { pulse: NotePulse }) {
const note = createPulseStore(props.pulse);
return (
<>
<input value={note.title} onInput={(e) => (props.pulse.title = e.currentTarget.value)} />
<p>by {note.author.name}</p>
<ul>
<For each={note.tags}>{(tag) => <li>{tag}</li>}</For>
</ul>
<button onClick={() => props.pulse.tags.push("new")}>Add tag</button>
</>
);
}
Read from the store, write to the pulse. A write commits, notifies the store synchronously and syncs to every peer.
Load on the client
// src/components/NotePage.tsx: imported with clientOnly, so it never runs on the server.
import { useParams } from "@solidjs/router";
import { createResource, onCleanup, Show } from "solid-js";
import { disconnectPulse, loadPulse } from "pulsify-core";
import { Editor } from "./Editor";
import { Note, remote } from "./note";
export default function NotePage() {
const params = useParams();
const [pulse] = createResource(() => params.id, (id) => loadPulse(Note, id, { connectors: [remote] }));
onCleanup(() => pulse() && disconnectPulse(pulse()!));
return (
<Show when={pulse()} keyed fallback={<p>Loading…</p>}>
{(note) => <Editor pulse={note} />}
</Show>
);
}
// src/routes/notes/[id].tsx
import { clientOnly } from "@solidjs/start";
const NotePage = clientOnly(() => import("~/components/NotePage"));
export default function Route() {
return <NotePage fallback={<p>Loading…</p>} />;
}