Skip to content

feat(collaborative-vue): Vue.js composables, components & context for json-joy CRDTs#1041

Open
mmamedel wants to merge 1 commit into
streamich:masterfrom
mmamedel:feat/collaborative-vue
Open

feat(collaborative-vue): Vue.js composables, components & context for json-joy CRDTs#1041
mmamedel wants to merge 1 commit into
streamich:masterfrom
mmamedel:feat/collaborative-vue

Conversation

@mmamedel

@mmamedel mmamedel commented Jun 2, 2026

Copy link
Copy Markdown

What

Adds @jsonjoy.com/collaborative-vue — the Vue 3 counterpart of @jsonjoy.com/collaborative-react, mirroring its surface and naming: composables, provide/inject context helpers, and render-prop components for binding Vue UI to json-joy CRDT models and nodes.

Note

Supersedes the earlier approach in this PR. The first revision was a writable-proxy binding built on a proposed .w core API (#1040). Per the feedback on #1040 — that the existing verbs already cover every write and are more precise — this PR was rewritten to build entirely on json-joy's existing public API. There are no changes to json-joy core, and the branch no longer depends on #1040.

import {Model} from 'json-joy/lib/json-crdt';
import {provideModel, useModelView, useStr} from '@jsonjoy.com/collaborative-vue';

const model = Model.create({title: 'Hello'});
provideModel(model);

const root = useModelView(model);            // ShallowRef<view> — reactive snapshot
const title = useStr(['title'], model.api);  // ShallowRef<StrApi | undefined>

// reads are reactive; writes are plain json-joy verbs
title.value?.ins(title.value.view().length, '!');

Surface (mirrors collaborative-react)

Module React analog Exports
context.ts context.tsx createNodeCtx, provideModel/provideNode, useCtx{Model,Node}[Strict], <ModelProvider>/<NodeProvider>
composables.ts hooks.ts useModelTick/useModelView/useModel/useModelTry; useNodeEvents/useNodeEffect/useNodeChange/useNode/useNodeView; usePath/usePathView/useObj/useArr/useStr
components.ts components.ts <UseModel> / <UseNode> (scoped-slot render props)
types.ts / utils.ts same CrdtNodeApi, selectNode

Design

  • Reads are reactive, writes are plain verbs. Composables return Vue refs/computed; you mutate with json-joy's existing node API (obj.set/del, str.ins, arr.push/upd/del, …). The binding adds no write abstraction, so the full precise CRDT API stays available (set vs merge, character-level text ops, etc.).
  • Granularity is explicit via the event arg — 'self' | 'child' | 'subtree' — just like React.
  • React → Vue adaptation. Node/path composables triggerRef on every matching change, so a stable node handle still re-notifies dependents (React re-renders on change regardless of return identity). useModelView is identity-gated (re-renders only when the view object changes); useModelTick/useModelView buffer on a microtask, node events fire synchronously.
  • Auto-teardown of subscriptions via onScopeDispose when used inside a component/effectScope.

Tests

11 tests, all green:

  • composables.spec.tsuseModelView/useModelTick reactivity + microtask flush, useStr resolve+read+in-place edit, usePath missing→present, useNodeView, scope teardown (run under effectScope).
  • context.spec.tsModelProvider provide/inject, strict-context throws, UseModel/UseNode render and update on change (mounted in jsdom).

Builds via the root solution tsc -b.

Follow-up

A small stacked PR will add an optional high-level collaborate() proxy (plain-object reads + v-model two-way binding) built on top of these composables — keeping the project's layering (node API ← composables ← optional sugar) instead of competing with it.

… json-joy CRDTs

Adds @jsonjoy.com/collaborative-vue — the Vue 3 counterpart of
@jsonjoy.com/collaborative-react, mirroring its surface and names:

- context.ts  — provide/inject helpers (createNodeCtx, provideModel/provideNode,
  useCtx{Model,Node}[Strict]) and <ModelProvider>/<NodeProvider>
- composables.ts — useModelTick/View/Model/ModelTry, useNodeEvents/Effect/
  Change/Node/NodeView, usePath/PathView/Obj/Arr/Str
- components.ts — <UseModel>/<UseNode> render-prop components via scoped slots

Reads return Vue refs/computed; writes use json-joy's existing node verbs
(obj.set, str.ins, arr.push, …) — no additions to json-joy core. Node/path
composables force-trigger on every matching change so stable node handles still
re-notify, matching React's re-render-on-change semantics.

Builds entirely on the existing public API. Tested with jest (composables via
effectScope, context/components mounted in jsdom).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mmamedel
mmamedel force-pushed the feat/collaborative-vue branch from 7a49245 to 69c7e90 Compare June 6, 2026 12:58
@mmamedel mmamedel changed the title feat(collaborative-vue): Vue 3 binding for json-joy CRDTs feat(collaborative-vue): Vue.js composables, components & context for json-joy CRDTs Jun 6, 2026
@mmamedel mmamedel closed this Jun 6, 2026
@mmamedel mmamedel reopened this Jun 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant