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

@gasboost/replica

v0.3.0

Published

Type-safe IndexedDB replica powered by Dexie and Zod

Readme

@gasboost/replica

Dexie と Zod を利用した、ブラウザ向けの型安全な IndexedDB レプリカです。

@gasboost/replica は、Zod Schema で定義された Record を IndexedDB 上に保存し、ローカルレプリカとして扱うためのパッケージです。

Query 処理は @gasboost/query と連携して行うため、Filter、Sort、Pagination、JOIN、Nested JOIN のロジックを Replica 側で重複実装しません。

Remote Data
   ↓
Full Sync
   ↓
@gasboost/replica
   ↓
IndexedDB / Dexie
   ↑
Loader
   ↑
@gasboost/query

Features

  • Dexie を利用した IndexedDB ストレージ
  • Zod Schema からの型推論
  • 型安全な Table 名
  • 型安全な Primary Key
  • put
  • bulkPut
  • delete
  • toArray
  • Full Sync
  • Dexie Transaction による同期
  • @gasboost/query との統合
  • JOIN
  • Nested JOIN
  • Google Apps Script ランタイム非依存
  • SheetORM ランタイム非依存

Installation

pnpm add @gasboost/replica @gasboost/table dexie zod

npm の場合:

npm install @gasboost/replica @gasboost/table dexie zod

Table Definition

Replica では以下を使って Table を定義します。

  • Table 名
  • Zod Schema
  • Primary Key
import { defineTable } from "@gasboost/table";
import { z } from "zod";

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  active: z.boolean(),
});

const ReservationSchema = z.object({
  id: z.string(),
  userId: z.string(),
});

const tables = [
  defineTable({
    name: "users",
    schema: UserSchema,
    primaryKey: "id",
  }),
  defineTable({
    name: "reservations",
    schema: ReservationSchema,
    primaryKey: "id",
  }),
] as const;

primaryKey には、対応する Zod Schema に存在する Column のみ指定できます。


Replica の作成

import { createReplica } from "@gasboost/replica";

const replica = createReplica({
  name: "example-app",
  tables,
});

name は IndexedDB の Database 名として利用されます。

各 Table は、定義された primaryKey を使って Dexie に登録されます。

例えば、

{
  name: "users",
  primaryKey: "id",
}

は概念的に次の Dexie Schema として登録されます。

users: "id"

Table Access

table() で Replica 内の Table を参照します。

const users = replica.table("users");

Table 名は Table Definition から型推論されます。

replica.table("users"); // OK
replica.table("reservations"); // OK

定義されていない Table 名は TypeScript 上で拒否されます。


Put

Record を追加または更新します。

await replica.table("users").put({
  id: "u1",
  name: "Alice",
  active: true,
});

Record 型は、選択した Table の Zod Schema から推論されます。


Bulk Put

複数 Record をまとめて追加または更新します。

await replica.table("users").bulkPut([
  {
    id: "u1",
    name: "Alice",
    active: true,
  },
  {
    id: "u2",
    name: "Bob",
    active: false,
  },
]);

Delete

Primary Key を指定して Record を削除します。

await replica.table("users").delete("u1");

Primary Key の型は、Table Definition の primaryKey Column から推論されます。


To Array

Table 内の Record をすべて取得します。

const users = await replica.table("users").toArray();

戻り値の型は、対象 Table の Zod Schema から推論されます。


Full Sync

リモート側から取得した完全な Table データを、ローカル Replica へ同期できます。

await replica.sync("users", remoteUsers);

同期時には、

  • remote に存在する Record を local に反映
  • remote に存在しない Record を local から削除
  • 同期処理全体を Dexie Transaction 内で実行

します。

例えば、local に以下の Record があるとします。

await replica.table("users").bulkPut([
  {
    id: "u1",
    name: "Old Alice",
    active: true,
  },
  {
    id: "u2",
    name: "Deleted User",
    active: false,
  },
]);

remote の完全データが以下だった場合、

await replica.sync("users", [
  {
    id: "u1",
    name: "Alice",
    active: true,
  },
]);

同期後の local は次の状態になります。

[
  {
    id: "u1",
    name: "Alice",
    active: true,
  },
];

remote に存在しない u2 は local から削除されます。

空配列を同期すると Table は空になります。

await replica.sync("users", []);

Query Integration

@gasboost/replica@gasboost/query と連携します。 通常は replica.query() から型安全な Query を生成できます。

const query = replica
  .query("users")
  .and("active", "=", [true])
  .orderBy("name", "asc");

Replica に対して Query を実行します。

const users = await replica.find(query);

Replica 自身は Filter や Sort のロジックを持ちません。

内部では IndexedDB から Record を取得し、Query.resolve() に渡します。

replica.find(query)
      ↓
query.resolve(loader)
      ↓
replica.table(name).toArray()

これにより、異なる Storage Adapter 間で Query semantics を共有できます。


JOIN

JOIN の評価は @gasboost/query が担当します。

const query = replica.query("users").join("id", "reservations", "userId");
const users = await replica.find(query);

結果:

[
  {
    id: "u1",
    name: "Alice",
    active: true,
    reservations: [
      {
        id: "r1",
        userId: "u1",
      },
    ],
  },
];

Replica 側に JOIN Engine は持ちません。

Replica は @gasboost/query に Record を提供するだけです。


Nested JOIN

Nested JOIN も @gasboost/query によって解決されます。

const reservations = replica
  .query("reservations")
  .join("staffId", "staffs", "id");

const users = replica.query("users").join(
  "id",
  "reservations",
  "userId",
  reservations,
);
const result = await replica.find(users);

Nested JOIN は Query.resolve() によって bottom-up に解決されます。

staffs
  ↓
reservations
  ↓
users

Architecture

@gasboost/replica は Storage の責務だけを持ちます。

@gasboost/replica
  - IndexedDB 初期化
  - Dexie Table
  - put
  - bulkPut
  - delete
  - toArray
  - Full Sync
  - Query Loader

@gasboost/query
  - Filter
  - Sort
  - Offset
  - Limit
  - JOIN
  - Nested JOIN
  - Query Resolution

この責務分離により、ローカル Storage とリモート Storage の間で Query 処理を重複実装せずに済みます。


Shared Schemas

Server と Browser で同じ Table Definition を共有できます。

export const tables = [
  {
    name: "users",
    schema: UserSchema,
    primaryKey: "id",
  },
] as const;
Server / GAS
     ↓
 shared tables
     ↑
Browser / Replica

Shared Schema を置く Module には、以下のような Runtime 固有依存を含めないことを推奨します。

  • SpreadsheetApp
  • CacheService
  • GAS Handler
  • Browser 固有の Application Code

これにより、Server Runtime が Frontend Bundle に混入することを防ぎます。


対象外

@gasboost/replica core では以下を扱いません。

  • Firebase Realtime Database 同期
  • 端末間リアルタイム同期
  • Firebase Authentication
  • Row Level Security
  • Offline Mutation Queue
  • Conflict Resolution
  • Incremental Sync
  • Secondary Index を利用した Query 最適化
  • React Hooks

これらは Replica core の上に別レイヤーとして追加できます。


License

MIT