userdo
TypeScript icon, indicating that this package has built-in type declarations

0.1.49 • Public • Published

UserDO

A Durable Object base class that provides user authentication, per-user data storage, and real-time updates for Cloudflare Workers applications.

Features

  • Authentication: JWT-based user auth with signup, login, logout, and password reset
  • Database Tables: Type-safe SQLite tables with Zod schemas and query builder
  • Key-Value Storage: Per-user KV storage with automatic broadcasting
  • Real-time Updates: WebSocket connections with hibernation API support
  • Worker Factory: Pre-built Hono server with configurable binding names
  • Browser Client: Auto-reconnecting WebSocket client with change listeners

Core Components

  • UserDO: Extend this class to add your business logic
  • createUserDOWorker(): Creates a Hono server with auth endpoints
  • createWebSocketHandler(): Handles WebSocket upgrades separately
  • UserDOClient: Browser client for API calls and real-time subscriptions

Quick Start

1. Define Your Schema

import { UserDO, type Env, type Table } from "userdo";
import { z } from "zod";

const PostSchema = z.object({
  title: z.string(),
  content: z.string(),
  createdAt: z.string(),
});

type Post = z.infer<typeof PostSchema>;

2. Extend UserDO

export class MyAppDO extends UserDO {
  posts: Table<Post>;

  constructor(state: DurableObjectState, env: Env) {
    super(state, env);
    this.posts = this.table('posts', PostSchema, { userScoped: true });
  }

  async createPost(title: string, content: string) {
    return await this.posts.create({
      title,
      content,
      createdAt: new Date().toISOString(),
    });
  }

  async getPosts() {
    return await this.posts.orderBy('createdAt', 'desc').get();
  }
}

3. Create Your Worker

import { createUserDOWorker, createWebSocketHandler, getUserDOFromContext } from 'userdo';

// Create worker with custom binding name
const app = createUserDOWorker('MY_APP_DO');
const wsHandler = createWebSocketHandler('MY_APP_DO');

// Add custom routes
app.post('/api/posts', async (c) => {
  const user = c.get('user');
  if (!user) return c.json({ error: 'Unauthorized' }, 401);
  
  const { title, content } = await c.req.json();
  const myAppDO = getUserDOFromContext(c, user.email, 'MY_APP_DO') as MyAppDO;
  const post = await myAppDO.createPost(title, content);
  
  return c.json({ post });
});

// Export worker with WebSocket support
export default {
  async fetch(request: Request, env: any, ctx: any): Promise<Response> {
    if (request.headers.get('upgrade') === 'websocket') {
      return wsHandler.fetch(request, env, ctx);
    }
    return app.fetch(request, env, ctx);
  }
};

Built-in API Endpoints

Authentication

  • POST /api/signup - Create user account
  • POST /api/login - Authenticate user
  • POST /api/logout - End user session
  • GET /api/me - Get current user info

Password Reset

  • POST /api/password-reset/request - Generate reset token
  • POST /api/password-reset/confirm - Reset password with token

Data Operations

  • GET /data - Get user's key-value data
  • POST /data - Set user's key-value data

Real-time

  • GET /api/ws - WebSocket connection for live updates

Browser Client

import { UserDOClient } from 'userdo';

const client = new UserDOClient('/api');

// Authentication
await client.signup('user@example.com', 'password');
await client.login('user@example.com', 'password');

// Key-value operations
await client.set('preferences', { theme: 'dark' });
const prefs = await client.get('preferences');

// Watch for changes
const unsubscribe = client.onChange('preferences', data => {
  console.log('Preferences updated:', data);
});

// Collections (if you have custom endpoints)
const posts = client.collection('posts');
await posts.create({ title: 'Hello', content: 'World' });

Database Operations

Table Creation

// In your UserDO constructor
this.posts = this.table('posts', PostSchema, { userScoped: true });

CRUD Operations

// Create
const post = await this.posts.create({ title, content, createdAt });

// Read
const post = await this.posts.findById(id);
const posts = await this.posts.get();

// Update
const updated = await this.posts.update(id, { title: 'New Title' });

// Delete
await this.posts.delete(id);

Queries

// Filter and sort
const posts = await this.posts
  .where('title', '==', 'Hello')
  .orderBy('createdAt', 'desc')
  .limit(10)
  .get();

// Count
const count = await this.posts.count();

Real-time Events

Data changes automatically broadcast WebSocket events:

  • kv:{key} - When key-value data changes
  • table:{tableName} - When table data changes (create/update/delete)
// Listen for specific events
client.onChange('preferences', data => console.log('Prefs changed:', data));

// Listen for table changes
const posts = client.collection('posts');
posts.onChange(data => console.log('Post changed:', data));

Multiple Projects

Use different binding names to isolate projects:

// Project 1: Blog
const blogWorker = createUserDOWorker('BLOG_DO');

// Project 2: Shop
const shopWorker = createUserDOWorker('SHOP_DO');

// Default binding
const defaultWorker = createUserDOWorker(); // Uses 'USERDO'

Configuration

wrangler.jsonc

{
  "main": "src/index.ts",
  "vars": {
    "JWT_SECRET": "your-jwt-secret-here"
  },
  "durable_objects": {
    "bindings": [
      {
        "name": "MY_APP_DO",
        "class_name": "MyAppDO"
      }
    ]
  }
}

Environment Variables

  • JWT_SECRET (required) - Secret for signing JWT tokens

Authentication Methods

// All methods return { user, token, refreshToken }
await userDO.signup({ email, password });
await userDO.login({ email, password });

// Token operations
await userDO.verifyToken({ token });
await userDO.refreshToken({ refreshToken });

// Password management
await userDO.changePassword({ oldPassword, newPassword });
await userDO.resetPassword({ newPassword });

// Session management
await userDO.logout();

Type Safety

All endpoints are typed with TypeScript and validated with Zod:

import type { UserDOEndpoints, EndpointRequest, EndpointResponse } from 'userdo';

type SignupRequest = EndpointRequest<'POST /api/signup'>;
type SignupResponse = EndpointResponse<'POST /api/signup'>;

Installation

npm install userdo

Examples

Complete working examples:

Architecture

  • Per-user isolation: Each user gets their own Durable Object instance
  • Email-based routing: User emails are hashed to generate Durable Object IDs
  • WebSocket hibernation: Uses Cloudflare's hibernation API for efficient WebSocket handling
  • Type-safe schemas: Zod validation for all data operations
  • Automatic broadcasting: Real-time events for all data changes

Security

  • PBKDF2 password hashing with WebCrypto
  • JWT tokens with configurable expiration
  • HTTP-only cookies for token storage
  • Per-user data isolation by default
  • Rate limiting on authentication endpoints

Limitations

  • Requires Cloudflare Workers environment
  • SQLite storage is per-Durable Object instance
  • WebSocket connections are limited by Durable Object hibernation limits
  • No built-in admin interface
  • Cannot be self-hosted (requires Cloudflare infrastructure)

Recent Updates

  • ✅ WebSocket hibernation API support
  • ✅ Configurable Durable Object binding names
  • ✅ Auto-reconnecting browser client
  • ✅ Real-time change listeners for KV and tables
  • ✅ Separate WebSocket handler to avoid Hono serialization issues

Package Sidebar

Install

npm i userdo

Weekly Downloads

998

Version

0.1.49

License

MIT

Unpacked Size

108 kB

Total Files

20

Last publish

Collaborators

  • acoyfellow