npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@grayfalcon666/loom

v1.0.0

Published

A Go/WASM CRDT engine for collaborative text editing — runs in the browser with no build step

Downloads

15

Readme

@grayfalcon666/loom

A Go-powered CRDT engine for collaborative text editing, compiled to WebAssembly and running entirely in the browser — no backend required for the CRDT itself.

Features

  • RGA (Replicated Growable Array) — character-level CRDT text editing with concurrent insert/delete support
  • LWW-Map — key-value CRDT map
  • Zero-config init — WASM is embedded as base64, npm install and go
  • Full TypeScript type declarations included
  • Go + syscall/js — all CRDT logic runs in the browser via WASM

Install

npm install @grayfalcon666/loom

Quick Start

import { init } from '@grayfalcon666/loom';

const loom = await init();           // WASM loaded from embedded base64 — no extra files

const aliceDoc = loom.newDocument('alice');
const bobDoc   = loom.newDocument('bob');

loom.addText(aliceDoc, 'body', 'shared-text');

// Alice inserts 'H' at position 0
const op = loom.insertChar(aliceDoc, 'shared-text', 0, 'H'.charCodeAt(0));
// Send op to your WebSocket server → peers receive it

// Bob applies the remote op
loom.unwrapReceive(bobDoc, op);

console.log(loom.peekText(bobDoc, 'shared-text')); // 'H'

WebSocket Sync

loom does not include a WebSocket server. Any WS relay works. See the server implementation for a reference Go backend.

const WS_URL = 'ws://localhost:8080/ws?doc=my-doc';
const ws = new WebSocket(WS_URL);

// Send local op to peers
ws.addEventListener('open', () => {
  const op = loom.insertChar(doc, 'text-1', 0, 'X'.charCodeAt(0));
  ws.send(op);
});

// Apply remote op from peers
ws.addEventListener('message', (e) => {
  loom.unwrapReceive(doc, e.data);
});

API Reference

init(wasmUrl?)

Initialize the WASM module.

  • No argument (default): uses the embedded base64-encoded WASM. Zero network requests, no CORS issues.
  • wasmUrl: load from a custom URL. Useful if you want to host loom.wasm yourself.
// Zero-config
const loom = await init();

// Custom URL (e.g. from /slim export or CDN)
const loom = await init('https://cdn.example.com/loom.wasm');

Document Management

| Method | Returns | Description | |---|---|---| | newDocument(actorID) | number (docID) | Create a CRDT document | | freeDocument(docID) | void | Free memory | | save(docID) | string (JSON) | Serialize document | | load(snapshotJSON) | number (new docID) | Load from snapshot | | getActorID(docID) | string | Get actor ID |

Text Editing

| Method | Returns | Description | |---|---|---| | addText(docID, key, textObjID) | string (Op JSON) | Declare shared text object | | insertChar(docID, textObjID, index, charCode) | string (Op JSON) | Insert character | | deleteChar(docID, textObjID, index) | string (Op JSON) | Delete character | | peekText(docID, textObjID) | string | Get current text |

  • insertChar / deleteChar return an Op JSON string — send this to your WebSocket server
  • addText also returns an Op — call it on all peers before inserting
  • unwrapReceive(docID, opJSON) applies a remote Op to a document

LWW-Map

| Method | Returns | Description | |---|---|---| | set(docID, key, value) | string (Op JSON) | Set key-value | | get(docID, key) | string (JSON) | Get value |

Package Structure

@grayfalcon666/loom/
├── index.js          # Main entry — init() with embedded base64 WASM
├── loom.js           # Raw JS wrapper (same as index.js but older API)
├── loom.d.ts         # TypeScript declarations
├── loom.wasm         # Raw .wasm binary (used if you pass url to init())
├── wasm_exec.js      # Go WASM bootstrap (required by Go runtime)
└── embedded_wasm.js  # AUTO-GENERATED: base64-encoded loom.wasm

Saving / Loading

// Save to localStorage
localStorage.setItem('doc', loom.save(docID));

// Load on page refresh
const id = loom.load(localStorage.getItem('doc'));

Subpath Exports

// Full package (base64 WASM embedded — recommended)
import { init } from '@grayfalcon666/loom';

// Slim version — requires you to host loom.wasm separately
// (loom.wasm is ~3MB; slim skips the embedded base64 for smaller JS bundle)
import { init } from '@grayfalcon666/loom/slim';
const loom = await init('/node_modules/@grayfalcon666/loom/loom.wasm');

TypeScript

Full TypeScript declarations are included. No @types/ package needed.

import { init, type LoomAPI } from '@grayfalcon666/loom';

const loom: LoomAPI = await init();
const docID: number = loom.newDocument('alice');

Limitations

  • syscall/js is the standard Go WASM path. Note that Go has deprecated syscall/js and the recommended future path is TinyGo + wasm-tools.
  • This package is currently at v1.0.0 — API is stabilizing but breaking changes are possible in minor releases until v2.
  • The WASM is large (~3MB) because it includes the full Go runtime. Consider using the /slim export if bundle size matters.

License

MIT