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

sqlite-nas-sync

v0.2.0

Published

Sync SQLite databases across multiple clients via NAS with changelog-based conflict resolution

Readme

sqlite-nas-sync

複数クライアント間でNASを経由してSQLiteデータベースを安全に同期するためのnpmパッケージです。

概要

NAS環境で複数のクライアント(PC、サーバー、Electronアプリなど)が同じSQLiteデータベースを共有したい場合、直接同じファイルにアクセスするとロック競合やデータ破損のリスクがあります。

sqlite-nas-syncは、各クライアントがローカルのSQLiteファイルで作業し、任意のタイミングでNASと同期することで、これらの問題を解決します。

特徴

  • ローカルファーストアーキテクチャ: 各クライアントはローカルDBで高速に読み書き
  • 柔軟な同期タイミング: プッシュ/プル/同期を任意のタイミングで実行
  • 自動競合解決: updated_atによる Last Write Wins 方式
  • 論理削除対応: deleted_atカラムがある場合は削除も同期
  • テーブル自動検出: UUID主キーとupdated_atを持つテーブルのみ同期
  • リトライ機能: ファイルロック時の自動リトライ

インストール

npm install sqlite-nas-sync

クイックスタート

import { SqliteNasSync } from 'sqlite-nas-sync';

const sync = new SqliteNasSync({
  localPath: './data/local.sqlite',
  nasDir: '/mnt/nas/shared-db/',
  clientId: 'client-abc123', // 省略時は自動生成
});

// NASへプッシュ
await sync.push();

// NASからプル
await sync.pull();

// プッシュ→プルを一度に実行
await sync.sync();

// 初回セットアップ(NAS上の全DBからローカルを初期化)
await sync.init();

インタラクティブデモ

実際の動作を確認したい場合は、対話的なデモを実行できます:

npm run demo

このデモでは、以下の内容を段階的に確認できます:

  1. 環境のセットアップ - デモ用のデータベースとディレクトリを作成
  2. クライアントAからプッシュ - ローカルDBをNASにプッシュ
  3. クライアントBでプル - NAS上のデータをマージ
  4. 競合解決 - Last Write Wins方式での競合解決を確認
  5. 新しいクライアントの初期化 - init()で全データを取得

各ステップでEnterキーを押すと次に進み、データベースの内容を確認しながら進められます。

テーブル要件

マージ対象となるテーブルは以下の条件を満たす必要があります:

必須条件

  1. 主キーが単一カラム
  2. 主キーがString型(UUID、cuid等)
  3. updated_atカラムが存在(DateTime型)

推奨条件

  • deleted_atカラムの追加(論理削除用)

Prismaスキーマ例

model User {
  id        String    @id @default(uuid())
  name      String
  email     String
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  deletedAt DateTime? // 論理削除用(推奨)
}

model Post {
  id        String    @id @default(cuid())
  title     String
  content   String
  userId    String
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  deletedAt DateTime?
}

スキップされるテーブル

以下のテーブルは自動的にスキップされ、警告ログが出力されます:

  • Integer型の自動採番主キー: クライアント間でID衝突リスクがあるため
  • 複合主キー: 現在サポートされていません
  • updated_atカラムなし: 競合解決ができないため
  • システムテーブル: sqlite_*, _prisma_migrations

API リファレンス

new SqliteNasSync(options)

オプション

| オプション | 型 | デフォルト | 説明 | |----------|-----|----------|------| | localPath | string | 必須 | ローカルSQLiteファイルのパス | | nasDir | string | 必須 | NAS上の共有ディレクトリパス | | clientId | string | 自動生成 | クライアント識別子(省略時は自動生成して永続化) | | excludeTables | string[] | [] | マージ対象から除外するテーブル名 | | retryCount | number | 3 | ファイルロック時のリトライ回数 | | retryDelay | number | 1000 | リトライ間隔(ミリ秒) | | logLevel | 'debug' \| 'info' \| 'warn' \| 'error' \| 'silent' | 'info' | ログレベル |

メソッド

async push(): Promise<void>

ローカルDBをNASにプッシュします(local.sqliteNAS/client-xxx.sqlite)。

await sync.push();

async pull(): Promise<void>

NAS上の他クライアントのDBからデータをプルしてローカルにマージします。

await sync.pull();

async sync(): Promise<void>

push()pull()を順番に実行します。

await sync.sync();

async init(): Promise<void>

初回セットアップ用。NAS上の全DBからローカルDBを初期化します。

await sync.init();

getClientId(): string

現在のクライアントIDを取得します。

const clientId = sync.getClientId();
console.log(`Client ID: ${clientId}`);

動作の仕組み

アーキテクチャ

NAS共有ディレクトリ/
├── client-abc123.sqlite    # クライアントAのDB
├── client-def456.sqlite    # クライアントBのDB
└── client-ghi789.sqlite    # クライアントCのDB

ローカル/
└── local.sqlite            # Prismaが参照するDB

マージロジック

各テーブルについて:
  NAS上の他クライアントDBからレコードを読み込み
  各レコードについて:
    ローカルに同一主キーが存在しない → INSERT
    ローカルに同一主キーが存在する:
      リモートの updated_at > ローカルの updated_at → UPDATE
      それ以外 → スキップ(ローカルが最新)

論理削除の同期

deleted_atカラムが存在する場合:

  • deleted_atに値があるレコードは削除済みとして扱われます
  • 削除もupdated_atで同期されます
  • 物理削除されたレコードは同期で復活する可能性があるため、論理削除の使用を強く推奨します

使用例

Electronアプリでの定期同期

import { SqliteNasSync } from 'sqlite-nas-sync';

const sync = new SqliteNasSync({
  localPath: './data/local.sqlite',
  nasDir: '/mnt/nas/shared-db/',
});

// 起動時に初回プル
await sync.pull();

// 5分ごとに同期
setInterval(async () => {
  try {
    await sync.sync();
    console.log('Sync completed');
  } catch (error) {
    console.error('Sync failed:', error);
  }
}, 5 * 60 * 1000);

// アプリ終了時に最後のプッシュ
process.on('beforeExit', async () => {
  await sync.push();
});

特定のテーブルを除外

const sync = new SqliteNasSync({
  localPath: './data/local.sqlite',
  nasDir: '/mnt/nas/shared-db/',
  excludeTables: ['logs', 'cache', 'temp_data'],
});

デバッグログの有効化

const sync = new SqliteNasSync({
  localPath: './data/local.sqlite',
  nasDir: '/mnt/nas/shared-db/',
  logLevel: 'debug',
});

ログ出力例

[sqlite-nas-sync] === Pull started ===
[sqlite-nas-sync] Found 2 remote databases: client-def456, client-ghi789
[sqlite-nas-sync]
[sqlite-nas-sync] Processing table: users
[sqlite-nas-sync]   ✓ users: 3 inserted, 5 updated, 0 skipped
[sqlite-nas-sync] Processing table: posts
[sqlite-nas-sync]   ✓ posts: 12 inserted, 2 updated, 8 skipped
[sqlite-nas-sync]
[sqlite-nas-sync] Skipped tables:
[sqlite-nas-sync]   - _prisma_migrations (system table)
[sqlite-nas-sync]   - counters (no updated_at column)
[sqlite-nas-sync]
[sqlite-nas-sync] === Pull completed ===

エラーハンドリング

try {
  await sync.sync();
} catch (error) {
  if (error.message.includes('NAS directory does not exist')) {
    console.error('NASディレクトリにアクセスできません');
  } else if (error.message.includes('Failed to copy file')) {
    console.error('ファイルコピーに失敗しました(ロック競合の可能性)');
  } else {
    console.error('同期エラー:', error);
  }
}

制限事項

  1. Integer型の自動採番主キーは非対応: クライアント間でID衝突が発生するため、UUID/cuidを使用してください
  2. 複合主キーは非対応: 単一カラムの主キーのみサポート
  3. SQLite専用: PostgreSQL等の他のデータベースには対応していません
  4. ファイルシステムベース: NAS(NFSやSMB等)でのファイル共有が前提

ベストプラクティス

  1. 論理削除の使用: deletedAtカラムを追加し、物理削除を避ける
  2. 定期的な同期: 長時間同期しないとコンフリクトが増えるため、定期的にsync()を実行
  3. エラーハンドリング: NASの接続エラー等に備えて適切なエラーハンドリングを実装
  4. ログレベルの調整: 本番環境では'warn'または'error'、開発時は'debug'を推奨

ライセンス

MIT