JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 17
  • Score
    100M100P100Q52416F
  • License MIT

Type-driven, offline-first SDK for TypeScript developers

Package Exports

  • mpb-localkit
  • mpb-localkit/react
  • mpb-localkit/svelte
  • mpb-localkit/vue

Readme

MPB LocalKit

Type-driven, offline-first SDK for TypeScript developers. Define your data schema once — get local storage, sync, auth, and error tracking out of the box.

Quick Start

npm install mpb-localkit
import { createApp, collection, z } from 'mpb-localkit'

const app = createApp({
  collections: {
    brews: collection(z.object({
      brewDate: z.date(),
      style: z.string(),
      og: z.number(),
      fg: z.number().optional(),
      notes: z.string().optional(),
    })),
  },
})

// Fully typed CRUD — works offline immediately
const brew = await app.brews.create({ brewDate: new Date(), style: 'IPA', og: 1.065 })
const allBrews = await app.brews.findMany()
await app.brews.update(brew._id, { fg: 1.012 })
await app.brews.delete(brew._id)

API Reference

createApp(config)

Creates a typed app instance with CRUD methods for each collection.

const app = createApp({
  collections: { ... },      // Required: collection definitions
  sync: {                    // Optional: sync configuration
    endpoint: 'https://...',
    interval: 30000,
    enabled: true,
  },
  errorTracking: {           // Optional: error tracking config
    enabled: true,
    snapshot: true,
    maxLocalErrors: 100,
  },
})

collection(schema)

Wraps a Zod schema into a typed collection descriptor. TypeScript infers the document type automatically.

const todos = collection(z.object({
  text: z.string(),
  done: z.boolean().default(false),
}))
// todos._inferredType is { text: string; done: boolean }

z

Re-exported from Zod. Use it to define your schemas.

Collection CRUD

Every collection in app.collections gets these methods:

// Create — returns the document with metadata fields
const doc = await app.todos.create({ text: 'Buy hops', done: false })
// doc._id, doc._collection, doc._updatedAt, doc._deleted are added automatically

// Read
const all = await app.todos.findMany()
const one = await app.todos.findOne(doc._id)

// Update — merges partial data
await app.todos.update(doc._id, { done: true })

// Delete — soft delete (sets _deleted: true for sync propagation)
await app.todos.delete(doc._id)

Auth

// Sign up
await app.auth.signUp({ email: 'user@example.com', password: 'secret' })

// Sign in
await app.auth.signIn({ email: 'user@example.com', password: 'secret' })

// Sign out
await app.auth.signOut()

// Current user
const user = app.auth.currentUser() // { id, email } | null

Passwords are hashed client-side (PBKDF2) before transmission. Sessions are JWTs cached locally for offline access.

Sync

Sync happens automatically when configured with an endpoint. MPB LocalKit uses a Last-Write-Wins (LWW) protocol — the document with the highest _updatedAt timestamp wins conflicts.

// Manual sync
await app.sync()

Sync triggers automatically on:

  • Tab focus (returning to the app)
  • Network reconnect (coming back online)
  • Periodic interval (configurable, default 30s)

All reads and writes hit local storage immediately — sync never blocks the UI.

React Hooks

import { useCollection, useAuth, useSync } from 'mpb-localkit/react'
import { app } from './schema'

function BrewList() {
  const { data: brews, isLoading } = useCollection(app.brews)
  const { user, signIn, signOut } = useAuth(app)
  const { status, lastSyncAt } = useSync(app)

  if (isLoading) return <div>Loading...</div>

  return (
    <ul>
      {brews.map(brew => <li key={brew._id}>{brew.style}</li>)}
    </ul>
  )
}

CLI

# Start local dev (SDK works without a Worker)
npx mpb-localkit dev

# Generate Cloudflare Worker from your schema
npx mpb-localkit build --name my-app

# Build + deploy to Cloudflare
npx mpb-localkit deploy

The generated Worker handles auth, sync, and error storage via Cloudflare R2 + KV.

Architecture

Your App (React/Vue/Svelte)
        ↓
  Framework Bindings
  (useCollection, useAuth)
        ↓
     Core SDK
  ┌──────────────────────────────┐
  │  Schema  │  Sync  │  Auth   │
  │  Engine  │ Engine │ Module  │
  ├──────────────────────────────┤
  │      Local Storage           │
  │    (IndexedDB, via idb)      │
  └──────────────────────────────┘
        ↓ (when online)
  Cloudflare Worker (generated)
  ┌──────────────────────────────┐
  │  Auth   │  Sync  │  Errors  │
  │ Routes  │ Routes │  Routes  │
  ├──────────────────────────────┤
  │    Cloudflare R2 + KV        │
  └──────────────────────────────┘

All writes hit IndexedDB first — your app works fully offline. The sync engine pushes/pulls changes to the Cloudflare Worker when connectivity is available.

Document Shape

Every document has these metadata fields added automatically:

{
  _id: string          // UUIDv7 — sortable by creation time
  _collection: string  // Collection name
  _updatedAt: number   // Unix ms — the sync cursor
  _deleted: boolean    // Soft delete for sync propagation
  // ...your fields
}

Create a New App

The fastest way to start is with the scaffolder:

npm create mpb-localkit@latest
# or
pnpm create mpb-localkit@latest

It prompts for project name, framework, auth method, sync transport, and deploy target — then scaffolds a ready-to-run project and installs dependencies.

? Project name: my-app
? Framework: React
? Auth method: Email + Password
? Sync transport: Auto (WebSocket with HTTP fallback)
? Deploy target: Cloudflare Workers

Done! Your project is ready.
  cd my-app
  pnpm run dev

Transport Configuration

MPB LocalKit supports three sync transports, configurable at createApp time:

HTTP Transport (default)

Polling-based sync. Works everywhere, no persistent connection required.

const app = createApp({
  collections: { ... },
  sync: {
    transport: 'http',
    endpoint: 'https://your-worker.workers.dev',
    interval: 30000,
  },
})

WebSocket Transport

Persistent connection for real-time sync. The server pushes changes as they happen.

const app = createApp({
  collections: { ... },
  sync: {
    transport: 'websocket',
    endpoint: 'wss://your-worker.workers.dev',
  },
})

Starts a WebSocket connection and falls back to HTTP polling if WebSocket is unavailable. Best of both worlds.

const app = createApp({
  collections: { ... },
  sync: {
    transport: 'auto',
    endpoint: 'https://your-worker.workers.dev',
  },
})

With auto, MPB LocalKit upgrades to WebSocket when the server supports it and degrades gracefully on restricted networks.

WebSocket Sync

When using transport: 'websocket' or transport: 'auto', MPB LocalKit maintains a persistent WebSocket connection to your sync Worker. The Worker sends change events as they occur — no polling delay.

import { createApp, collection, z } from 'mpb-localkit'

const app = createApp({
  collections: {
    messages: collection(z.object({
      text: z.string(),
      author: z.string(),
    })),
  },
  sync: {
    transport: 'auto',
    endpoint: 'https://chat.your-subdomain.workers.dev',
  },
})

// Writes go to IndexedDB immediately (no await on network)
await app.messages.create({ text: 'Hello!', author: 'alice' })

// The Worker broadcasts the change to all connected clients via WebSocket
// Recipients see the new message arrive without polling

The WebSocket connection is managed automatically:

  • Reconnects with exponential backoff on disconnect
  • Replays missed changes on reconnect using the LWW cursor
  • Falls back to HTTP polling when WebSocket is unavailable (Auto mode)

Better Auth Integration

MPB LocalKit integrates with Better Auth for full-featured server-side authentication.

Setup

import { createApp, collection, z } from 'mpb-localkit'

const app = createApp({
  collections: {
    notes: collection(z.object({ text: z.string() })),
  },
  auth: {
    type: 'better-auth',
    baseURL: 'https://auth.your-domain.com',
  },
  sync: {
    transport: 'auto',
    endpoint: 'https://sync.your-domain.com',
  },
})

Session handling

Better Auth sessions are cached locally so the app stays functional offline. The session is re-validated against the server on reconnect.

// Sign in — stores session locally for offline access
await app.auth.signIn({ email: 'user@example.com', password: 'secret' })

// Current user is available immediately (from local cache)
const user = app.auth.currentUser() // { id, email, name } | null

// Sync automatically includes the session token in requests
await app.sync()

// Sign out clears the local session
await app.auth.signOut()

React hooks with Better Auth

import { useAuth, useCollection } from 'mpb-localkit/react'
import { app } from './schema'

function App() {
  const { user, signIn, signOut, isLoading } = useAuth(app)
  const { data: notes } = useCollection(app.notes)

  if (isLoading) return <div>Loading...</div>
  if (!user) return <SignInForm onSignIn={signIn} />

  return (
    <div>
      <p>Signed in as {user.email}</p>
      <ul>{notes.map(n => <li key={n._id}>{n.text}</li>)}</ul>
      <button onClick={signOut}>Sign out</button>
    </div>
  )
}

License

MIT