<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Israel Martins</title>
    <description>The latest articles on DEV Community by Israel Martins (@israelmarmar).</description>
    <link>https://dev.to/israelmarmar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3511921%2F81219228-d216-4b28-b1ed-6e330aa09ba4.jpeg</url>
      <title>DEV Community: Israel Martins</title>
      <link>https://dev.to/israelmarmar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/israelmarmar"/>
    <language>en</language>
    <item>
      <title>Building Anime Lip Sync in ComfyUI: A Detection-Guided Diffusion Pipeline</title>
      <dc:creator>Israel Martins</dc:creator>
      <pubDate>Wed, 01 Jul 2026 19:47:51 +0000</pubDate>
      <link>https://dev.to/israelmarmar/building-anime-lip-sync-in-comfyui-a-detection-guided-diffusion-pipeline-91d</link>
      <guid>https://dev.to/israelmarmar/building-anime-lip-sync-in-comfyui-a-detection-guided-diffusion-pipeline-91d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqxf4lfu4atm0cbhwpqfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqxf4lfu4atm0cbhwpqfm.png" alt=" " width="800" height="572"&gt;&lt;/a&gt;Lip syncing anime footage is deceptively difficult. A realistic face already gives a model many visual cues, but anime faces often reduce the mouth to a few lines, flatten facial depth, and change proportions dramatically between styles. A generic lip-sync system may produce unstable shapes, paste skin around the lips, mistake an eyelash for a closed mouth, or place a valid mouth at the wrong location when the character moves.&lt;/p&gt;

&lt;p&gt;The Anime Lip Sync custom node for ComfyUI approaches the problem as a controlled, multi-stage pipeline. Instead of asking one model to solve audio alignment, face editing, mouth generation, segmentation, tracking, and compositing at once, it gives each problem to a specialized component.&lt;/p&gt;

&lt;p&gt;The result is a workflow that combines phoneme analysis, YOLO detection, diffusion, ControlNet guidance, BEN2 background removal, anatomical validation, temporal caching, and final video composition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Idea
&lt;/h2&gt;

&lt;p&gt;The node receives an audio track, a batch of video frames, and the models needed for diffusion. It then produces three mouth states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;neutral_closed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;half_open&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fully_open&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are intentionally broad categories. Anime animation often communicates speech with a small vocabulary of mouth shapes, so generating a stable set of reusable states can look more coherent than trying to synthesize a unique mouth for every phoneme.&lt;/p&gt;

&lt;p&gt;The pipeline is organized into four phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect and prepare the face.&lt;/li&gt;
&lt;li&gt;Generate the required mouth states with diffusion.&lt;/li&gt;
&lt;li&gt;Isolate and track the generated mouth.&lt;/li&gt;
&lt;li&gt;Composite the mouth and build the final video.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Phase 0: Preparing a Clean Face
&lt;/h2&gt;

&lt;p&gt;The first phase uses a custom YOLO model to identify facial regions. The current anatomical validator expects the following classes:&lt;/p&gt;

&lt;p&gt;Model Link: &lt;a href="https://huggingface.co/israelmarmar/anime_face_mouth/tree/main" rel="noopener noreferrer"&gt;https://huggingface.co/israelmarmar/anime_face_mouth/tree/main&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 = face
1 = mouth
2 = nose
3 = chin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The face bounding box defines the working crop. The original mouth is detected, masked, and inpainted when mouth removal is enabled. This produces a clean base frame that can receive a newly generated mouth later.&lt;/p&gt;

&lt;p&gt;For scenes with multiple characters, the node can optionally use Florence-2 phrase grounding. A text query such as &lt;code&gt;the girl with blue hair&lt;/code&gt; identifies the target character before YOLO searches for the face. This prevents the pipeline from switching to another character simply because that face has a higher detection score.&lt;/p&gt;

&lt;p&gt;All intermediate assets are written through a disk-backed store. Original frames, mouth masks, face masks, bounding boxes, generated faces, mouth crops, and output frames remain separated. This structure limits VRAM pressure and makes the pipeline easier to inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Audio-Driven Diffusion
&lt;/h2&gt;

&lt;p&gt;Audio is analyzed with Allosaurus. Detected sounds are mapped to the three mouth categories, including a neutral closed state for silence. The node then groups visually similar face crops so it does not need to run diffusion independently for every video frame.&lt;/p&gt;

&lt;p&gt;Face similarity is based on facial structure and scribble-like contours rather than color alone. HED guidance preserves important anime line work, head pose, and silhouette while allowing diffusion to change the mouth. The generated face is conditioned through a Qwen Image DiffSynth ControlNet workflow and sampled with Z-Image Turbo.&lt;/p&gt;

&lt;p&gt;Each mouth state can use its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prompt or external conditioning&lt;/li&gt;
&lt;li&gt;LoRA&lt;/li&gt;
&lt;li&gt;step count&lt;/li&gt;
&lt;li&gt;CFG value&lt;/li&gt;
&lt;li&gt;denoise strength&lt;/li&gt;
&lt;li&gt;ControlNet strength&lt;/li&gt;
&lt;li&gt;sampler and scheduler&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because a closed mouth and a fully open mouth are different generation problems. A closed mouth needs a crisp, restrained line, while an open mouth needs enough denoising freedom to create a visible interior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do Not Cache a Bad Generation
&lt;/h2&gt;

&lt;p&gt;Diffusion is stochastic. Even with a strong prompt and LoRA, a requested closed mouth may appear slightly open, or no usable mouth may be generated at all. Caching that result would spread the failure across many frames.&lt;/p&gt;

&lt;p&gt;The node therefore validates every generated face before saving it to the history cache. The YOLO model searches for a mouth, but confidence alone is not enough. Anime eyelashes and eye contours can look like closed mouths to an object detector.&lt;/p&gt;

&lt;p&gt;The validator uses the detected nose and chin to build an anatomical mouth region. A mouth candidate must be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inside the detected face&lt;/li&gt;
&lt;li&gt;below the nose&lt;/li&gt;
&lt;li&gt;above the chin&lt;/li&gt;
&lt;li&gt;horizontally aligned with the nose and chin&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the highest-confidence candidate lies near the eyes, it is rejected. The node performs a second YOLO pass with a lower confidence threshold and a larger inference resolution to look for another candidate inside the valid region.&lt;/p&gt;

&lt;p&gt;If no anatomically valid mouth is found, the KSampler runs again with a new seed. The &lt;code&gt;mouth_regen_attempts&lt;/code&gt; option controls how many additional samples may be generated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validating a Truly Closed Mouth
&lt;/h2&gt;

&lt;p&gt;A closed-mouth detection needs stricter rules. It should be wide enough to be visible, horizontally shaped, and thin enough to resemble a lip contour rather than a filled cavity.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;neutral_closed&lt;/code&gt;, the validator checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;relative width&lt;/li&gt;
&lt;li&gt;relative height&lt;/li&gt;
&lt;li&gt;width-to-height aspect ratio&lt;/li&gt;
&lt;li&gt;detected area&lt;/li&gt;
&lt;li&gt;internal fill&lt;/li&gt;
&lt;li&gt;foreground thickness&lt;/li&gt;
&lt;li&gt;the presence of a solid inner core&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final checks analyze the pixels inside the detected mouth bounding box. A thin lip line has low internal fill and little or no eroded core. A slightly open mouth usually contains a thicker dark or reddish region. When that filled region exceeds the allowed thresholds, the candidate is rejected and diffusion samples again.&lt;/p&gt;

&lt;p&gt;After a closed mouth passes validation, a local contrast enhancement makes the lip contour easier to read. CLAHE and mild sharpening are applied only around the accepted anatomical mouth region, preserving the rest of the face.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Extracting the Mouth with BEN2
&lt;/h2&gt;

&lt;p&gt;Once a generated face passes validation, the mouth must be isolated without carrying a rectangle of differently colored skin into the final frame.&lt;/p&gt;

&lt;p&gt;The node calls the same &lt;code&gt;easy imageRemBg&lt;/code&gt; custom node used by ComfyUI-Easy-Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rem_mode = BEN2
add_background = none
refine_foreground = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The returned RGBA image is used directly. This is important because multiplying the BEN2 alpha by additional heuristic masks can make the mouth unnecessarily transparent. BEN2 remains responsible for separating the foreground mouth from surrounding skin, while fallback masking is used only when the custom node is unavailable.&lt;/p&gt;

&lt;p&gt;The final mouth detector also uses the nose-and-chin anatomical selector. This prevents Phase 1 from validating the correct mouth only for Phase 2 to crop an eyelash with a higher confidence score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Mouth Position Relative to the Face
&lt;/h2&gt;

&lt;p&gt;Absolute coordinates are not enough for video. If a character moves to the right, a cached mouth should move with the face rather than remain at the old screen position.&lt;/p&gt;

&lt;p&gt;The mouth cache stores normalized coordinates relative to the face bounding box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rx, ry = relative mouth position
rw, rh = relative mouth size
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a visually similar face appears at a different position or scale, the node projects those normalized values into the new face bounding box. This allows a generated mouth state to be reused while still following head movement.&lt;/p&gt;

&lt;p&gt;Cache epochs are updated when facial appearance or motion changes enough to make previous mouth data unsafe. The pipeline can also process mouth extraction and composition concurrently with diffusion to reduce total runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: Compositing and Video Output
&lt;/h2&gt;

&lt;p&gt;The generated RGBA mouth is resized to its tracked destination and alpha-composited over the inpainted frame. Weak residual alpha values are removed to reduce halos and skin contamination. The output frame sequence is then encoded with FFmpeg and combined with the original audio.&lt;/p&gt;

&lt;p&gt;The node returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the final video path&lt;/li&gt;
&lt;li&gt;VHS-compatible video information&lt;/li&gt;
&lt;li&gt;an optional persistent debug directory&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Debugging the Pipeline
&lt;/h2&gt;

&lt;p&gt;Enabling &lt;code&gt;save_debug_folder&lt;/code&gt; copies the temporary working directory into the ComfyUI output folder before cleanup. This provides access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;original and inpainted frames&lt;/li&gt;
&lt;li&gt;face and mouth masks&lt;/li&gt;
&lt;li&gt;face bounding boxes&lt;/li&gt;
&lt;li&gt;generated diffusion faces&lt;/li&gt;
&lt;li&gt;generated mouth history&lt;/li&gt;
&lt;li&gt;BEN2 RGBA mouth caches&lt;/li&gt;
&lt;li&gt;final composed frames&lt;/li&gt;
&lt;li&gt;validation metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;History JSON files include whether the generated mouth passed validation and a message containing confidence, anatomy, dimensions, and contour metrics. Mouth cache metadata also records the alpha source, making it possible to confirm that &lt;code&gt;easy imageRemBg&lt;/code&gt; with BEN2 was used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Tuning
&lt;/h2&gt;

&lt;p&gt;The most useful controls are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mouth_conf&lt;/code&gt; for detector sensitivity&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_generated_mouth&lt;/code&gt; to enable generation validation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mouth_regen_attempts&lt;/code&gt; for diffusion retries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sim_threshold&lt;/code&gt; for face grouping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;motion_variance_factor&lt;/code&gt; for cache invalidation&lt;/li&gt;
&lt;li&gt;per-state denoise and ControlNet strength&lt;/li&gt;
&lt;li&gt;per-state mouth padding and brightness&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;compose_feather_px&lt;/code&gt; for final blending&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Low mouth confidence may recover subtle closed lines but can increase false positives. Anatomical validation helps make lower thresholds practical. Higher regeneration counts improve the chance of receiving a valid mouth at the cost of inference time.&lt;/p&gt;


  
  Seu navegador não suporta a tag de vídeo.



  
  Seu navegador não suporta a tag de vídeo.


&lt;h2&gt;
  
  
  Why This Architecture Works for Anime
&lt;/h2&gt;

&lt;p&gt;The pipeline does not assume that one neural network will always make the correct decision. It combines generative and discriminative models in a feedback loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;audio chooses the requested mouth state&lt;/li&gt;
&lt;li&gt;diffusion proposes a visual result&lt;/li&gt;
&lt;li&gt;YOLO checks whether the result contains a plausible mouth&lt;/li&gt;
&lt;li&gt;nose and chin detections verify its location&lt;/li&gt;
&lt;li&gt;pixel analysis verifies that a closed mouth is actually closed&lt;/li&gt;
&lt;li&gt;BEN2 extracts the accepted mouth&lt;/li&gt;
&lt;li&gt;face-relative tracking places it consistently over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That layered approach is especially useful for stylized animation, where a small line can represent a mouth, an eyelash can look like the same line, and a few pixels can determine whether an expression reads correctly.&lt;/p&gt;

&lt;p&gt;The custom node still needs some improvements, such as refining the shape of the mouths and stabilizing their position to prevent flickering. Any suggestions for improvement are welcome.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/israelmarmar/anime-lipsync" rel="noopener noreferrer"&gt;https://github.com/israelmarmar/anime-lipsync&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>deeplearning</category>
      <category>machinelearning</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I created a fork of GunDB and rewrote it in TypeScript using Vibe Code</title>
      <dc:creator>Israel Martins</dc:creator>
      <pubDate>Sat, 30 May 2026 18:43:59 +0000</pubDate>
      <link>https://dev.to/israelmarmar/i-created-a-fork-of-gundb-and-rewrote-it-in-typescript-using-vibe-code-5b9b</link>
      <guid>https://dev.to/israelmarmar/i-created-a-fork-of-gundb-and-rewrote-it-in-typescript-using-vibe-code-5b9b</guid>
      <description>&lt;p&gt;Inspired by a similar project called GenosDB and Cloudflare’s initiative to rebuild Next.js, I decided to rebuild GunDB with a modern coding style, incorporating improvements and addressing shortcomings in the original technology. I used the OpenCode tool with the Big Pickle model to rewrite the project in a new graph database called Garfo (the Portuguese word for “fork”), and I was impressed with the results and its practical applications. In this article, I’ll explain the technology and its improvements over GunDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Garfo&lt;/strong&gt; is a modern, browser-first fork of &lt;a href="https://gun.eco/" rel="noopener noreferrer"&gt;GUN.js&lt;/a&gt; — the decentralized, offline-first graph database. A fork of the original project that keeps the familiar GUN graph API while bringing meaningful improvements to the modern JavaScript ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Fork?
&lt;/h2&gt;

&lt;p&gt;GUN.js is a revolutionary technology — a graph database that syncs in real time, works peer-to-peer, resolves conflicts automatically, and runs in the browser. However, the JavaScript ecosystem has evolved. TypeScript has become the standard, ES modules are the norm, and new transport layers like &lt;strong&gt;Nostr&lt;/strong&gt; have emerged as promising decentralized protocols.&lt;/p&gt;

&lt;p&gt;Garfo was born to fill these gaps: a GUN rewritten with modern typing, designed with the browser as a first-class citizen, and with native support for the Nostr protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Familiar Graph API
&lt;/h3&gt;

&lt;p&gt;If you've used GUN before, you'll feel right at home. Garfo exposes the same chainable API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Garfo&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;garfo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Garfo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;online&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Update:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the classic methods are there: &lt;code&gt;get()&lt;/code&gt;, &lt;code&gt;put()&lt;/code&gt;, &lt;code&gt;set()&lt;/code&gt;, &lt;code&gt;on()&lt;/code&gt;, &lt;code&gt;once()&lt;/code&gt;, &lt;code&gt;map()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional Nostr Transport
&lt;/h3&gt;

&lt;p&gt;This is one of the most exciting additions. Garfo can use &lt;strong&gt;Nostr relays&lt;/strong&gt; as a transport layer, allowing peers to exchange graph messages through public or private relays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Garfo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;nostr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;sk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;private-key-hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;relays&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://relay.damus.io&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://nos.lol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;general&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello over Nostr!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IndexedDB and OPFS storage
&lt;/h3&gt;

&lt;p&gt;In addition to localStorage, I have also included IndexedDB and OPFS as alternative ways to store database data in the browser&lt;/p&gt;

&lt;p&gt;This unlocks immense possibilities: Garfo no longer depends solely on direct WebSocket connections — any Nostr relay can become a synchronization backbone.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEA — Security, Encryption, Authorization
&lt;/h3&gt;

&lt;p&gt;The SEA module is included, providing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public/private key pairs for users&lt;/li&gt;
&lt;li&gt;End-to-end encryption&lt;/li&gt;
&lt;li&gt;Data signing and verification&lt;/li&gt;
&lt;li&gt;Proof-of-work for rate limiting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Browser-First
&lt;/h3&gt;

&lt;p&gt;Unlike GUN.js which carries a Node.js legacy, Garfo is built with the browser as the primary target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web-optimized bundle&lt;/li&gt;
&lt;li&gt;TypeScript throughout&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;Garfo shines in &lt;strong&gt;local-first&lt;/strong&gt; and &lt;strong&gt;decentralized&lt;/strong&gt; applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Peer-to-peer chat&lt;/strong&gt; — the repo includes a complete Nostr-backed chat example with React + TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborative apps&lt;/strong&gt; — document editing, Kanban boards, shared whiteboards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P2P content delivery&lt;/strong&gt; — the repo has a livestream example using peer-to-peer CDN&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline IoT&lt;/strong&gt; — field data collection with sync when online&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decentralized social networks&lt;/strong&gt; — combining graph data model with Nostr transport&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Example: Nostr Chat
&lt;/h2&gt;

&lt;p&gt;The project ships with a working chat demo. Run &lt;code&gt;npm start&lt;/code&gt;, open two browser tabs, and each creates a Nostr identity. Messages flow through relays and are stored locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/israelmarmar/garfodb.git
&lt;span class="nb"&gt;cd &lt;/span&gt;garfodb/examples/nostr-chat
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm start
&lt;span class="c"&gt;# Open http://127.0.0.1:8900/ in two windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chat supports generating identities, copying public keys, backing up and restoring keys — recovering full history from relays.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Garfo Compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;GUN.js&lt;/th&gt;
&lt;th&gt;Garfo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Package name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gun&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;garfo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constructor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Gun&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Garfo&lt;/code&gt; (with &lt;code&gt;Gun&lt;/code&gt; alias)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nostr transport&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ Native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Focus&lt;/td&gt;
&lt;td&gt;Node + Browser&lt;/td&gt;
&lt;td&gt;Browser-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;LocalStorage&lt;/td&gt;
&lt;td&gt;LocalStorage, IndexedDB, OPFS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance&lt;/td&gt;
&lt;td&gt;Original community&lt;/td&gt;
&lt;td&gt;Active fork&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Technical Notes
&lt;/h2&gt;

&lt;p&gt;Garfo retains the &lt;strong&gt;graph node&lt;/strong&gt; architecture of original GUN. Each piece of data lives in a "soul" — a node identified by a unique key. Peers connect in a mesh and exchange graph deltas.&lt;/p&gt;

&lt;p&gt;A key difference: while GUN.js was designed in a pre-TypeScript era, Garfo ships with complete types — you get intellisense and type checking out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you're building a decentralized application and need a real-time syncing database without central servers, Garfo deserves your attention.&lt;/p&gt;

&lt;p&gt;The project is already available in beta; not all security tests have been completed, so use it at your own risk&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt; &lt;a href="https://github.com/israelmarmar/garfodb" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; | &lt;a href="https://gun.eco/" rel="noopener noreferrer"&gt;GUN.eco&lt;/a&gt; | &lt;a href="https://nostr.com/" rel="noopener noreferrer"&gt;Nostr Protocol&lt;/a&gt;&lt;br&gt;
| &lt;a href="https://genosdb.com/" rel="noopener noreferrer"&gt;GenosDB&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>database</category>
      <category>showdev</category>
      <category>typescript</category>
      <category>vibecoding</category>
    </item>
  </channel>
</rss>
