New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

servly-sync

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

servly-sync

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

latest
npmnpm
Version
0.2.2
Version published
Maintainers
1
Created
Source

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:

  • Diff — Compare two component snapshots, produce JSON Patch ops
  • Hash — Fingerprint components for fast change detection
  • Conflict — Detect overlapping changes from concurrent edits
  • Resolve — Apply a strategy (auto-merge, local-wins, remote-wins, manual)
  • 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:

  • Fork the repo and create a branch from main
  • Install dependencies: npm install
  • Make your changes and add tests
  • Run the test suite: npm test
  • Run the build: npm run build
  • 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

Keywords

servly

FAQs

Package last updated on 29 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts