AbsoluteJS

Framework Hooks

Idiomatic bindings for React, Vue, Svelte, and Angular — all SSR-safe, all returning the same shape, so swapping frameworks in a multi-stack app doesn't change your data layer. There's a plain framework-agnostic client too.

#React — @absolutejs/sync/react

useSyncCollection returns { data, status, error, mutate }; useCollaborativeText returns { text, setText, status }. The socket opens in an effect, re-opens if the subscription identity changes, and closes on unmount.

TSX
import { useSyncCollection, useCollaborativeText } from '@absolutejs/sync/react';

const Issues = () => {
  const { data, status, mutate } = useSyncCollection({
    url: 'ws://localhost:3000/sync/ws',
    collection: 'issues',
  });
  return (
    <ul>{data.map((issue) => <li key={issue.id}>{issue.title}</li>)}</ul>
  );
};

const Description = ({ issueId }: { issueId: string }) => {
  const doc = useCollaborativeText({
    url: 'ws://localhost:3000/sync/ws',
    collection: 'issues',
    field: 'body',
    id: issueId,
  });
  return (
    <textarea value={doc.text} onChange={(e) => doc.setText(e.target.value)} />
  );
};

#Vue — @absolutejs/sync/vue

Composables returning reactive refs. SSR-safe — the socket opens in onMounted and closes in onUnmounted.

VUE
<script setup lang="ts">
import { useSyncCollection, useCollaborativeText } from '@absolutejs/sync/vue';

const { data, status, mutate } = useSyncCollection<Issue>({
  url: 'ws://localhost:3000/sync/ws',
  collection: 'issues',
});

const { text: docText, setText: setDocText } = useCollaborativeText({
  url: 'ws://localhost:3000/sync/ws',
  collection: 'issues',
  field: 'body',
  id: 'shared',
});
</script>

<template>
  <ul>
    <li v-for="issue in data" :key="issue.id">{{ issue.title }}</li>
  </ul>
  <textarea
    :value="docText"
    @input="setDocText(($event.target as HTMLTextAreaElement).value)"
  ></textarea>
</template>

#Svelte — @absolutejs/sync/svelte

Real Svelte stores — $store gives the live state, with mutate/setText/destroy attached. Connection opens lazily on the first browser subscription.

SVELTE
<script lang="ts">
  import {
    createSyncCollectionStore,
    createCollaborativeTextStore,
  } from '@absolutejs/sync/svelte';

  const issues = createSyncCollectionStore<Issue>({
    url: 'ws://localhost:3000/sync/ws',
    collection: 'issues',
  });

  const doc = createCollaborativeTextStore({
    url: 'ws://localhost:3000/sync/ws',
    collection: 'issues',
    field: 'body',
    id: 'shared',
  });
</script>

<ul>
  {#each $issues.data as issue (issue.id)}
    <li>{issue.title}</li>
  {/each}
</ul>
<textarea
  value={$doc.text}
  oninput={(e) => doc.setText(e.currentTarget.value)}
></textarea>

#Angular — @absolutejs/sync/angular

An injectable service returning Angular signals.connect opens a collection; collaborativeText opens a CRDT field. The service closes everything on its own destroy.

TS
import { Component, inject } from '@angular/core';
import { SyncCollectionService } from '@absolutejs/sync/angular';

@Component({
  selector: 'issues-page',
  standalone: true,
  template: `
    @for (issue of issues.data(); track issue.id) {
      <li>{{ issue.title }}</li>
    }
    <textarea
      [value]="doc.text()"
      (input)="doc.setText(($event.target as HTMLTextAreaElement).value)"
    ></textarea>
  `,
})
export class IssuesPageComponent {
  private sync = inject(SyncCollectionService);
  issues = this.sync.connect<Issue>({
    url: 'ws://localhost:3000/sync/ws',
    collection: 'issues',
  });
  doc = this.sync.collaborativeText({
    url: 'ws://localhost:3000/sync/ws',
    collection: 'issues',
    field: 'body',
    id: 'shared',
  });
}

#Plain client — @absolutejs/sync/client

Every framework binding is a thin wrapper over the same framework-agnostic client. Use it directly when you don't want a framework binding, or to build your own:

TS
// All four framework bindings are thin wrappers over a framework-agnostic
// client. You can use it directly when you don't want a framework binding:
import { createSyncCollection, createCollaborativeText } from '@absolutejs/sync/client';

const issues = createSyncCollection<Issue>({
  url: 'ws://localhost:3000/sync/ws',
  collection: 'issues',
});
issues.subscribe((state) => render(state.data));

const doc = createCollaborativeText({
  url: 'ws://localhost:3000/sync/ws',
  collection: 'issues',
  field: 'body',
  id: 'shared',
});
doc.subscribe(({ text }) => renderTextarea(text));