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

servly-sync

v0.2.2

Published

Bidirectional sync engine for Servly — diff, patch, hash, and conflict resolution for Layout JSON, props, and design tokens

Readme

servly-sync

Bidirectional sync engine for keeping design systems and code in sync. Diff, patch, hash, and resolve conflicts across Figma, code editors, CLIs, and AI tools.

Stack-agnostic. Zero dependencies. Works in Node.js, browsers, and edge runtimes.

Install

npm install servly-sync

Quick start

import { diff, applyPatch, hashComponent } from 'servly-sync';

// 1. Diff two versions of a component layout
const result = diff(
  [{ i: 'btn-1', componentId: 'Button', className: 'px-4 py-2' }],
  [{ i: 'btn-1', componentId: 'Button', className: 'px-6 py-3' }],
);

console.log(result.hasChanges); // true
console.log(result.summary);   // { added: 0, removed: 0, modified: 1, moved: 0 }
console.log(result.patch);     // [{ op: 'replace', path: '/0/className', value: 'px-6 py-3' }]

// 2. Apply the patch to get the updated version
const updated = applyPatch(
  [{ i: 'btn-1', componentId: 'Button', className: 'px-4 py-2' }],
  result.patch,
);

// 3. Hash the component for change detection
const hash = hashComponent({
  layout: updated,
  propsSchema: [{ name: 'label', type: 'string' }],
});

API

Diff engine

Compare two values and produce RFC 6902 JSON Patch operations.

import { diff, diffLayout, diffProps, diffTokens, deepEqual } from 'servly-sync';

// General-purpose diff (objects, arrays, primitives)
const result = diff(before, after);
// result.hasChanges  — boolean
// result.patch       — JSONPatchOperation[]
// result.changedPaths — string[]
// result.summary     — { added, removed, modified, moved }

// Domain-specific diffs (same output, scoped base paths)
const layoutDiff = diffLayout(oldLayout, newLayout);   // base: /layout
const propsDiff  = diffProps(oldProps, newProps);        // base: /propsSchema
const tokensDiff = diffTokens(oldTokens, newTokens);    // base: /tokens

// Deep equality check
deepEqual({ a: 1 }, { a: 1 }); // true

Custom array identity

By default, arrays are diffed by matching items on i, id, or name fields, falling back to index. Override this when your data model uses different ID fields:

// Use any field as the array item key
const result = diff(before, after, {
  getKey: (item: any) => item.uuid,
});

// Works with all diff functions
const layoutDiff = diffLayout(oldLayout, newLayout, {
  getKey: (item: any) => item.nodeId,
});

Patch application

Apply JSON Patch operations (RFC 6902) to any value. Returns a new deep-cloned result — the original is never mutated.

import { applyPatch, validatePatch, PatchError } from 'servly-sync';

const updated = applyPatch(document, [
  { op: 'replace', path: '/title', value: 'New Title' },
  { op: 'add', path: '/tags/-', value: 'new-tag' },
  { op: 'remove', path: '/deprecated' },
]);

// Validate without applying
const error = validatePatch(document, operations);
if (error) console.error(error.message);

Supports all six RFC 6902 operations: add, remove, replace, move, copy, test.

Hashing

DJB2-based hashing for change detection. Compatible with the Figma plugin's hashing, so hashes match across all sync surfaces.

import {
  hashValue,
  hashLayout,
  hashProps,
  hashTokens,
  hashComponent,
  hashComponentParts,
  DEFAULT_LAYOUT_FIELDS,
} from 'servly-sync';

// Hash individual parts
const layoutHash = hashLayout(elements);
const propsHash  = hashProps(propsSchema);
const tokensHash = hashTokens(tokens);

// Hash an entire component snapshot
const componentHash = hashComponent({
  layout: elements,
  propsSchema,
  tokens,
  metadata: { version: '1.0' },
});

// Get per-part hashes to know *which* part changed
const parts = hashComponentParts({ layout: elements, propsSchema });
// { layout: 'a3f2c1b0', props: '7e1d4f2a', tokens: '00000000', metadata: '00000000' }

Custom layout fields

By default, hashLayout hashes 9 structural fields. Use custom fields when your component model is different:

// Hash only the fields your component model uses
const hash = hashLayout(elements, ['key', 'type', 'children', 'props']);

// Extend the defaults with your own fields
const hash = hashLayout(elements, [...DEFAULT_LAYOUT_FIELDS, 'customField']);

Conflict detection & resolution

Detect and resolve conflicts when two sources modify the same component concurrently.

import {
  detectConflict,
  detectAllConflicts,
  resolveConflict,
  mergeChanges,
} from 'servly-sync';

// Detect a single conflict between two change events
const conflict = detectConflict(localChange, remoteChange);

// Detect all conflicts between sets of changes
const conflicts = detectAllConflicts(localChanges, remoteChanges);

// Resolve with a strategy
const resolution = resolveConflict(conflict, 'auto-merge');
// Strategies: 'local-wins' | 'remote-wins' | 'auto-merge' | 'manual'

// Full merge pipeline — resolves what it can, surfaces the rest
const { merged, conflicts: unresolved, resolutions } = mergeChanges(
  baseDocument,
  localChanges,
  remoteChanges,
  'auto-merge',
);

Sync pipeline

High-level orchestration that coordinates diff, hash, conflict, and patch into a single call. Use these when you want the full sync workflow without manually calling each primitive.

import { preparePush, preparePull, prepareMerge, applyResolution } from 'servly-sync';

// Push: diff local against remote, get patches to send
const pushResult = preparePush(localSnapshot, remoteSnapshot);
// pushResult.patches  — JSONPatchOperation[] to apply on remote
// pushResult.hash     — hash of local state

// Pull: diff remote against local, get patches to apply locally
const pullResult = preparePull(localSnapshot, remoteSnapshot);

// Merge: when both sides changed, detect and resolve conflicts
const mergeResult = prepareMerge(baseSnapshot, localSnapshot, remoteSnapshot, {
  strategy: 'auto-merge',
  diffOptions: { getKey: (item: any) => item.uid },
});
// mergeResult.patches    — resolved patches
// mergeResult.conflicts  — unresolved conflicts (if strategy is 'manual')

// Apply resolved patches to a snapshot
const updated = applyResolution(baseSnapshot, mergeResult.patches);

Sync state management

Track the sync status of each component across local and remote sources.

import {
  createSyncState,
  recordLocalChange,
  recordRemoteChange,
  computeStatus,
  markSynced,
  describeSyncState,
  SyncStateStore,
} from 'servly-sync';

// Functional API
let state = createSyncState('btn-primary', initialHash);
state = recordLocalChange(state, changeEvent);
console.log(computeStatus(state));      // 'ahead'
console.log(describeSyncState(state));   // '1 local change(s) to push'

state = markSynced(state, newHash);
console.log(computeStatus(state));      // 'in-sync'

// Class-based store for managing multiple components
const store = new SyncStateStore();
store.recordLocal('btn-primary', changeEvent);
store.recordRemote('card-hero', remoteEvent);

store.getByStatus('conflict');  // components with conflicts
store.getAllConflicts();         // all unresolved conflicts

Statuses: in-sync | ahead | behind | diverged | conflict | unknown

Change events

Create and manage sync change events that flow through the system.

import {
  createChangeEvent,
  createBatch,
  chunkBatches,
  validateChangeEvent,
  groupByComponent,
  groupByChangeType,
  sortEvents,
} from 'servly-sync';

// Split large event lists into sized batches for rate-limited APIs
const batches = chunkBatches('cli', 'my-tool', events, 50);
// batches[0].changes.length <= 50

Sources: figma | builder | cli | cursor | ai

Change types: layout | props | tokens | style | metadata | component | behavior

Design token sync

Transform design tokens between Figma Variables, CSS custom properties, and Tailwind config.

import {
  figmaVariablesToServlyTokens,
  servlyTokensToCSS,
  servlyTokensToCSSWithModes,
  servlyTokensToTailwindConfig,
  servlyTokensToTailwindConfigString,
  cssToServlyTokens,
  buildTokenSyncManifest,
  DEFAULT_CATEGORY_RULES,
} from 'servly-sync';

// Figma Variables -> Servly tokens
const { tokens, entries } = figmaVariablesToServlyTokens(figmaVariables);

// Servly tokens -> CSS custom properties
const css = servlyTokensToCSS(tokens);
// :root {
//   --ds-primary: #3b82f6;
//   --ds-spacing-sm: 8px;
// }

// With light/dark mode support
const cssWithModes = servlyTokensToCSSWithModes(tokens);

// Servly tokens -> Tailwind config
const tailwindConfig = servlyTokensToTailwindConfig(tokens);

// CSS -> Servly tokens (reverse sync)
const parsedTokens = cssToServlyTokens(existingCSS);

Custom category inference

Control how token categories are inferred from variable names:

// Add custom category rules (checked before built-in rules)
const { tokens } = figmaVariablesToServlyTokens(variables, {
  prefix: 'my',
  categoryRules: [
    { keywords: ['motion', 'duration', 'animation'], category: 'motion' },
    { keywords: ['breakpoint'], category: 'responsive' },
  ],
});

// Or override category inference entirely
const { tokens } = figmaVariablesToServlyTokens(variables, {
  inferCategory: (variable) => myCustomCategoryLogic(variable),
});

Props mapper

Bidirectional prop type mapping between design tool types and code types. Ships with a Figma preset — create your own for Sketch, Adobe XD, Penpot, or custom component models.

import {
  mapPropType,
  reversePropType,
  mapPropValue,
  extendPreset,
  FIGMA_PROP_PRESET,
} from 'servly-sync';

// Map Figma prop types to servly-sync types
mapPropType('TEXT', FIGMA_PROP_PRESET);      // 'string'
mapPropType('VARIANT', FIGMA_PROP_PRESET);   // 'enum'
mapPropType('BOOLEAN', FIGMA_PROP_PRESET);   // 'boolean'

// Reverse map back to Figma types
reversePropType('string', FIGMA_PROP_PRESET); // 'TEXT'

// Create a custom preset for your design tool
const sketchPreset = extendPreset(FIGMA_PROP_PRESET, 'sketch', [
  { sourceType: 'COLOR', targetType: 'color' },
  { sourceType: 'TEXT', targetType: 'string', transformValue: (v) => String(v).trim() },
]);

Format converter registry

Register converters between component representations. Each registry is independent — no global state.

import { createFormatRegistry } from 'servly-sync';

const registry = createFormatRegistry();

// Register a Figma -> Snapshot converter
registry.register({
  from: 'figma-json',
  to: 'snapshot',
  convert: (figmaNode) => ({
    componentId: figmaNode.id,
    hash: '',
    timestamp: new Date().toISOString(),
    layout: extractLayout(figmaNode),
  }),
});

// Convert
const snapshot = registry.convert('figma-json', 'snapshot', figmaNode);

// List registered converters
registry.list(); // [['figma-json', 'snapshot']]

Semantic tag inference

Infer HTML semantic tags from component names and structure. 130+ built-in rules, extensible with your own.

import { inferTag, inferTagsForLayout, getTagRules } from 'servly-sync';

inferTag('Button');      // { tag: 'button', role: 'button', confidence: 0.95 }
inferTag('Hero Image');  // { tag: 'img', confidence: 0.8 }
inferTag('Navbar');      // { tag: 'nav', role: 'navigation', confidence: 0.95 }

// Add custom rules for your component library
inferTag('Widget', {
  customRules: [
    { pattern: 'widget', tag: 'section', role: 'region', confidence: 0.95 },
  ],
});

// Replace built-in rules entirely
inferTag('Button', {
  customRules: myRules,
  mergeWithDefaults: false,
});

Source adapter interface

Define extraction/application adapters for any design tool or code framework. The interface lives in servly-sync — implementations live in your code.

import type { SourceAdapter, ComponentSnapshot } from 'servly-sync';

class FigmaAdapter implements SourceAdapter<FigmaNode> {
  name = 'figma';
  async extract(): Promise<ComponentSnapshot[]> { /* ... */ }
  async apply(patches: JSONPatchOperation[]): Promise<void> { /* ... */ }
  toSnapshot(node: FigmaNode): ComponentSnapshot { /* ... */ }
  fromSnapshot(snapshot: ComponentSnapshot): FigmaNode { /* ... */ }
}

Notifications

Generate user-facing notifications from sync events.

import {
  notificationFromChange,
  conflictNotification,
  prCreatedNotification,
  filterNotifications,
} from 'servly-sync';

Architecture

servly-sync is the shared core consumed by every surface in the Servly ecosystem:

Figma Plugin ──┐
Code Editor  ──┤
CLI          ──┼── servly-sync ── diff/patch/hash/conflict ── Any Backend
AI Tools     ──┤
MCP Server   ──┘

The sync pipeline:

  1. Diff — Compare two component snapshots, produce JSON Patch ops
  2. Hash — Fingerprint components for fast change detection
  3. Conflict — Detect overlapping changes from concurrent edits
  4. Resolve — Apply a strategy (auto-merge, local-wins, remote-wins, manual)
  5. Patch — Apply the resolved operations to produce the final state

Development

# Install dependencies
npm install

# Build (ESM + CJS + types)
npm run build

# Run tests
npm test

# Watch mode
npm run dev

Project structure

src/
  index.ts          Public API exports
  types.ts          All TypeScript type definitions
  diff.ts           RFC 6902 diff engine (configurable array keying)
  patch.ts          JSON Patch application
  hash.ts           DJB2 hashing (configurable layout fields)
  conflict.ts       Conflict detection & resolution
  syncState.ts      Sync state management
  changeEvent.ts    Change event creation + batch chunking
  pipeline.ts       Sync orchestration (push/pull/merge)
  tokenSync.ts      Design token transformations (configurable categories)
  propsMapper.ts    Bidirectional prop type mapping
  formatRegistry.ts Format converter registry
  fingerprint.ts    Component fingerprinting
  semanticTags.ts   Semantic tag inference (extensible rules)
  notifications.ts  Notification generation
  __tests__/        Test suite (Vitest)

Running tests

npm test              # single run
npm run test:watch    # watch mode

Contributing

We welcome contributions. Here's how to get started:

  1. Fork the repo and create a branch from main
  2. Install dependencies: npm install
  3. Make your changes and add tests
  4. Run the test suite: npm test
  5. Run the build: npm run build
  6. Submit a pull request

What we're looking for

  • Bug fixes with a failing test case
  • Performance improvements to the diff/patch engine
  • New conflict resolution strategies
  • Additional design token format support (Style Dictionary, Tokens Studio, etc.)
  • Better array diffing algorithms (LCS-based, move detection)
  • Source adapter implementations for design tools (Sketch, Adobe XD, Penpot)
  • Props mapper presets for additional tools
  • Documentation improvements and examples

Conventions

  • TypeScript strict mode
  • No production dependencies — keep the bundle lean
  • Comments explain decision-making, not obvious code
  • All public functions need JSDoc comments
  • Tests use Vitest
  • Builds use tsup (ESM + CJS dual output)

What's being built

Servly is building an open standard for syncing design systems between tools and code. servly-sync is the engine at the center of that — it handles the hard parts of bidirectional sync so that every integration surface (Figma plugins, CLI tools, editor extensions, AI agents) speaks the same language.

Roadmap

  • Component Interchange Format (CIF) — A JSON spec for describing components in a tool-agnostic way. servly-sync will validate and transform CIF documents.
  • Framework wrappers — React, Vue, Svelte, and Angular adapters that consume CIF and render native components.
  • Move detection in diffs — Detect when array items are reordered, not just added/removed.
  • Three-way merge — Use a common ancestor for smarter conflict resolution.
  • Operational transform — Real-time collaborative editing across sync sources.
  • Token format adapters — Import/export from Style Dictionary, Tokens Studio, and other token formats.
  • Built-in source adapters — First-party adapters for Figma, Sketch, and code frameworks.

License

MIT