Skip to content

trentbrew/PocketVex

Repository files navigation

logo

PocketVex

Schema migration system for PocketBase with real-time development workflow

PocketVex provides a Convex-style development experience for PocketBase, allowing you to define your schema as code and automatically apply changes during development.

Features

  • Schema-as-Code: Define your PocketBase schema in TypeScript
  • Real-time Migration: Watch schema files and apply changes automatically
  • Safe vs Unsafe Operations: Automatically categorizes changes and handles them appropriately
  • Migration Generation: Generate migration files for production deployments
  • Development Server: Hot-reload schema changes during development
  • Unified CLI: Comprehensive command-line interface with organized commands
  • JavaScript VM Integration: Full support for PocketBase JavaScript features
  • Event Hooks: Custom business logic with TypeScript definitions
  • Scheduled Jobs: CRON jobs for automation and background tasks
  • Console Commands: Custom CLI commands for database management
  • Raw Database Queries: Advanced SQL operations with type safety
  • Type Generation: Automatic TypeScript types from schema definitions
  • Interactive Demos: Comprehensive demo system for learning and testing
  • Convex-like Experience: Complete development workflow similar to Convex

Installation

Use the Node-based CLI (no Bun required for end users). Requires Node β‰₯ 18.

# zero-install CLI
npx pocketvex@latest help

# or install globally
npm i -g pocketvex

Development setup

# Clone and install dependencies
git clone https://github.com/trentbrew/pocketvex.git
cd pocketvex
bun install

# Or with npm
npm install

Configuration

Set up your environment variables:

export PB_URL="http://127.0.0.1:8090"
export PB_ADMIN_EMAIL="admin@example.com"
export PB_ADMIN_PASS="admin123"

Quick Start

Quickstart (Node CLI)

# zero-install CLI
npx pocketvex@latest help

# Start dev server - automatically creates project structure!
npx pocketvex@latest dev

# start dev watcher (safe ops auto-apply, types emit)
npx pocketvex@latest dev

Host Selection: When you run PocketVex commands, you'll be prompted to select a PocketBase host:

  • Live PocketBase (pocketvex.pockethost.io)
  • Local PocketBase (127.0.0.1:8090)
  • Cached Hosts (previously used hosts)
  • ✏Custom URL (enter your own PocketBase URL)

Credential Caching: PocketVex automatically caches your credentials securely for 24 hours per host, so you won't need to enter them repeatedly. Credentials are encrypted and stored locally in ~/.pocketvex/credentials.json.

Host Selection Workflow

When you run PocketVex commands, you'll see an interactive host selection menu:

? Select PocketBase host:
❯ 🌐 Live PocketBase (pocketvex.pockethost.io)
  🏠 Local PocketBase (127.0.0.1:8090)
  πŸ’Ύ https://my-pb.example.com
  ✏️  Enter custom URL

Features:

  • Cached Hosts: Previously used hosts appear in the menu for quick access
  • Custom URLs: Enter any PocketBase URL with validation
  • Credential Integration: Credentials are automatically retrieved for cached hosts
  • Secure Storage: Host URLs and credentials are encrypted and cached locally

Managing Cached Credentials

# View and manage cached credentials
npx pocketvex util credentials

# Clear all cached credentials
npx pocketvex util credentials   # Then select "Clear all credentials"

# Remove credentials for a specific URL
npx pocketvex util credentials   # Then select "Remove specific credentials"

Unified CLI Commands

PocketVex features a unified CLI system with organized commands:

# Show all available commands
npx pocketvex help

# Schema management
npx pocketvex schema diff          # Show schema differences
npx pocketvex schema apply         # Apply schema changes

# Migration management
npx pocketvex migrate generate     # Generate migration files
npx pocketvex migrate up           # Run migrations
npx pocketvex migrate down         # Rollback migrations
npx pocketvex migrate status       # Show migration status

# Type generation
# (Auto-generated by the dev server; no separate CLI needed)

# Development
npx pocketvex dev                  # Start development server

# Schema sync (Convex-like DX)
npx pocketvex schema pull          # Remote -> local (writes pocketvex/schema/schema.js)
npx pocketvex schema push          # Local  -> remote (apply diff)
npx pocketvex schema sync          # Default remote wins (use --strategy local for local wins)

# Interactive demos
npx pocketvex demo                 # Run unified demo system
# Tip: use `@latest` with npx if you want to bypass local cache (e.g., `npx pocketvex@latest demo`)

# Utilities
npx pocketvex util setup           # Interactive setup for credentials
npx pocketvex util credentials     # Manage cached credentials
npx pocketvex util test-connection # Test PocketBase connection
npx pocketvex init                 # Scaffold client pocketvex/ modules (records/auth)

Start Development Server

PocketVex includes a real-time development server similar to npx convex dev. The dev server automatically creates the project directory structure for you!

# Start dev server with file watching (default behavior)
npx pocketvex dev

# One-time schema sync (no watching)
npx pocketvex dev --once

Auto-Directory Creation: When you run npx pocketvex dev, it automatically creates:

  • pocketvex/schema/ - For your TypeScript schema files
  • pocketvex/jobs/ - For CRON jobs and scheduled tasks
  • pocketvex/hooks/ - For event hooks and middleware
  • pocketvex/commands/ - For console commands
  • pocketvex/queries/ - For custom queries
  • pocketvex/migrations/ - For generated migration files
  • generated/ - For auto-generated TypeScript types

Real-time Features:

  • File Watching: Automatically detects schema changes and JavaScript VM files
  • Safe Changes: Applied immediately to PocketBase
  • Unsafe Changes: Generate migration files for review
  • Type Generation: Auto-generates TypeScript types
  • JavaScript VM Deployment: Discovery mode (prints ready-to-deploy files). For managed hosts, use webhook/pack; for local hosts, copy to pb_* dirs. No auto-upload.
  • Hot Reload: No manual intervention required during development

Development Workflow

  1. Start the dev server (creates directories automatically):

    npx pocketvex dev
  2. Edit schema files in the schema/ directory or JavaScript VM files in pb_jobs/, pb_hooks/, etc.:

    // schema/my-schema.ts
    export const schema = {
      collections: [
        {
          name: 'products',
          type: 'base' as const,
          schema: [
            { name: 'name', type: 'text' as const, required: true },
            { name: 'price', type: 'number' as const, required: true },
          ],
        },
      ],
    };
  3. Watch the magic happen:

    • βœ… Safe changes (add fields, indexes) β†’ Applied automatically
    • ⚠️ Unsafe changes (remove fields, change types) β†’ Migration files generated
    • πŸ“ TypeScript types β†’ Auto-generated in generated/
  4. Review and apply migrations:

    npx pocketvex migrate up

JavaScript VM Deployment

PocketVex reports JavaScript VM files that are ready for deployment. For local/self-hosted PocketBase you can copy files to pb_* directories and restart PB. For managed hosts (e.g., PocketHost) use a webhook/pack flow. The dev server does not auto-upload to remote hosts.

Supported Directories:

  • ./pb_jobs/ - CRON jobs and scheduled tasks
  • ./pb_hooks/ - Event hooks and middleware
  • ./pb_commands/ - Console commands
  • ./pb_queries/ - Custom queries and utilities

Client Apps: pocketvex/ Modules

For frontend clients (React, SvelteKit, Nuxt), organize PocketVex-related client code in a local pocketvex/ directory inside the app. This is analogous to Convex’s client pattern and keeps queries/mutations/subscriptions together and typed. You can scaffold these files via npx pocketvex init. To mirror Convex’s DX, use pocketvex schema pull|push|sync to keep the local schema in pocketvex/schema/ in continuous sync with your PocketBase instance (remote wins by default).

Recommended layout per client app:

  • pocketvex/records.ts β€” Generic typed getters/setters for any collection
  • pocketvex/posts.ts β€” Optional specialization built on records.ts (for demos)
  • pocketvex/index.ts β€” Barrel exports (optional)
  • src/lib/pb.ts or pocketvex/client.ts β€” Client factory/singleton using PB URL from env
  • pocketvex/pb-types.d.ts β€” Generated types from PocketVex (see script)

Env conventions by framework:

  • React (Next.js): NEXT_PUBLIC_PB_URL
  • SvelteKit (Vite): VITE_PB_URL
  • Nuxt: NUXT_PUBLIC_PB_URL

Add a types generation script to each client:

"pv:types": "pocketvex types generate --output pocketvex/pb-types.d.ts"

Scaffold automatically:

# prompts for framework and writes files to src/pocketvex or pocketvex
npx pocketvex init

# non-interactive
npx pocketvex init --framework react|sveltekit|nuxt|other

Minimal example generic pocketvex/records.ts:

import type PocketBase from 'pocketbase'

export type ListParams = { page?: number; perPage?: number; filter?: string; sort?: string; expand?: string | string[] }

export async function listRecords<T = any>(pb: PocketBase, collection: string, { page = 1, perPage = 50, ...rest }: ListParams = {}) {
  return pb.collection(collection).getList<T>(page, perPage, rest as any)
}

export function subscribeToRecords<T = any>(pb: PocketBase, collection: string, topic: string | '*' = '*', handler: (e: { action: 'create' | 'update' | 'delete'; record: T }) => void) {
  return pb.collection(collection).subscribe(topic as any, handler as any)
}

export async function createRecord<T = any>(pb: PocketBase, collection: string, data: Partial<T>) {
  return pb.collection(collection).create<T>(data as any)
}

See working examples in:

  • React: examples/client-react/apps/web/pocketvex/
  • SvelteKit: examples/client-svelte/src/pocketvex/
  • Nuxt: examples/client-nuxt/pocketvex/

How it works:

  1. File discovery: PocketVex watches all JavaScript files in these directories
  2. No auto-upload: It prints which files are ready; you deploy them (local copy or pack)
  3. Local copy option: For self-hosted/local PB, copy files into pb_* dirs and restart PB
  4. Error surfacing: Problems are logged clearly

Example:

# Start the dev server
npx pocketvex dev

# Edit a CRON job file
echo '$jobs.register("test", "*/60 * * * * *", () => console.log("Hello!"));' > pb_jobs/test.js

# PocketVex reports the file as ready for deployment (no auto-upload)
# βœ… Ready: test.js β†’ pb_jobs/ (copy to your PB instance)

File Monitoring: When you start the dev server, PocketVex scans for JavaScript VM files:

πŸ” Scanning JavaScript VM files...
  πŸ“ Found 2 files in pb_jobs/
  πŸ“ basic-logging.js (ready for deployment)
  πŸ“ example-jobs.js (ready for deployment)
βœ… Found 2 JavaScript VM files
   πŸ“‹ Files are ready for manual deployment to PocketBase
   πŸ“– See README.md for deployment instructions

Manual Deployment: JavaScript VM files need to be manually deployed to PocketBase:

  1. Copy files to PocketBase instance:

    # Copy CRON jobs
    cp pb_jobs/*.js /path/to/pocketbase/pb_jobs/
    
    # Copy hooks
    cp pb_hooks/*.js /path/to/pocketbase/pb_hooks/
    
    # Copy commands
    cp pb_commands/*.js /path/to/pocketbase/pb_commands/
  2. Restart PocketBase to load the new JavaScript files

  3. Or use PocketBase Admin UI to upload files directly

Live Demo with PocketBase Instance

Try PocketVex with a live PocketBase instance:

# Set up environment variables for live instance
export PB_URL="https://pocketvex.pockethost.io/"
export PB_ADMIN_EMAIL="your-admin@email.com"
export PB_ADMIN_PASS="your-admin-password"

# Run unified demo system (interactive)
npx pocketvex demo

# Or run specific demo modes (if exposed by CLI)
npx pocketvex demo basic
npx pocketvex demo live
npx pocketvex demo realtime
npx pocketvex demo incremental
npx pocketvex demo js-vm
npx pocketvex demo test

# Repo dev (from source) – Bun:
# bun run demo

Note: You'll need the actual admin credentials for the live PocketBase instance to run the full demo.

Real-time Migration Demos

PocketVex includes interactive demos that show real-time schema migrations:

  • realtime-migration: Interactive demo that creates collections and applies schema changes in real-time
  • incremental-migration: Step-by-step schema evolution showing how to migrate from version to version

These demos will:

  • Connect to your live PocketBase instance
  • Show current schema analysis
  • Apply safe schema changes in real-time
  • Demonstrate the difference between safe and unsafe operations
  • Create example collections with various field types

JavaScript Features Demo

Explore PocketBase JavaScript VM integration:

# Run JavaScript features demo (via CLI if exposed)
npx pocketvex demo js-vm

# Start development server
npx pocketvex dev   # repo-dev from source: bun run dev-js

This demo showcases:

  • Event hooks for custom business logic
  • Scheduled jobs (CRON) for automation
  • Console commands for database management
  • Raw database queries with type safety
  • Automatic TypeScript type generation

Define Your Schema

Create schema files in schema/ directory:

// schema/my-app.schema.ts
import type { SchemaFieldStrict } from 'pocketvex/types/schema-strict';

export const schema = {
  collections: [
    {
      id: 'users_001',
      name: 'users',
      type: 'base',
      schema: [
        { name: 'name', type: 'text', required: true } as SchemaFieldStrict,
        {
          name: 'email',
          type: 'email',
          required: true,
          unique: true,
        } as SchemaFieldStrict,
        {
          name: 'role',
          type: 'select',
          options: { values: ['user', 'admin'] },
        } as SchemaFieldStrict,
      ] as SchemaFieldStrict[],
      rules: {
        list: "@request.auth.id != ''",
        view: "@request.auth.id != ''",
        create: "@request.auth.id != ''",
        update: '@request.auth.id = id',
        delete: "@request.auth.role = 'admin'",
      },
    },
  ],
} as const;

Watch Changes Apply Automatically

When you save schema files, PocketVex will:

  • βœ… Apply safe changes immediately (new collections, fields, rules)
  • ⚠️ Generate migration files for unsafe changes (type changes, deletions)
  • πŸ“Š Show you exactly what changed

πŸ› οΈ Commands

Development

# Start development server with file watching
npx pocketvex dev

# One-time schema sync
npx pocketvex dev --once

# Check PocketBase connection
npx pocketvex util test-connection

Schema Management

# Apply only safe changes
npx pocketvex schema apply --safe-only

# Apply all changes (with confirmation)
npx pocketvex schema apply --force

# Show schema differences
npx pocketvex schema diff

Migration Management

# Generate migration from schema changes
npx pocketvex migrate generate

# Run pending migrations
npx pocketvex migrate up

# Rollback last migration
npx pocketvex migrate down

# Show migration status
npx pocketvex migrate status

πŸ“ Project Structure

pocketvex/
β”œβ”€β”€ src/                    # Core library code
β”‚   β”œβ”€β”€ types/              # TypeScript definitions
β”‚   β”œβ”€β”€ utils/              # Core utilities (diff, pocketbase client, demo utils)
β”‚   β”œβ”€β”€ cli/                # Unified CLI system
β”‚   β”‚   └── index.ts        # Main CLI entry point
β”‚   β”œβ”€β”€ schema/             # Example schema definitions
β”‚   β”œβ”€β”€ dev-server.ts       # Development server
β”‚   └── index.ts            # Main entry point
β”œβ”€β”€ examples/               # Example projects and demos
β”‚   β”œβ”€β”€ basic/              # Basic usage examples
β”‚   β”œβ”€β”€ live-demo/          # Live PocketBase demos
β”‚   β”œβ”€β”€ javascript-vm/      # PocketBase JS VM examples
β”‚   └── demo.ts             # Unified demo system
β”œβ”€β”€ docs/                   # Documentation and templates
β”‚   β”œβ”€β”€ templates/          # Development templates
β”‚   └── memory/             # Project constitution
β”œβ”€β”€ config/                 # Configuration files
β”œβ”€β”€ scripts/                # Build and utility scripts
β”œβ”€β”€ schema/                 # Your schema definitions
β”œβ”€β”€ generated/              # Auto-generated files
β”œβ”€β”€ pb_migrations/          # Generated migration files
└── README.md

How It Works

Safe Operations (Auto-applied)

  • βœ… Create new collections
  • βœ… Add new fields
  • βœ… Update collection rules
  • βœ… Add indexes
  • βœ… Increase field limits
  • βœ… Add select values
  • βœ… Add editor fields

Unsafe Operations (Generate Migrations)

  • ❌ Change field types
  • ❌ Delete collections/fields
  • ❌ Make fields required without defaults
  • ❌ Make fields unique (may have duplicates)
  • ❌ Tighten validation constraints
  • ❌ Remove select values

Development Workflow

  1. Edit Schema: Modify your TypeScript schema files
  2. Auto-Apply: Safe changes are applied immediately
  3. Generate Migrations: Unsafe changes generate migration files
  4. Review & Deploy: Review migrations before production deployment

API Surface (current)

PocketVex exports a small, stable surface today. More typed APIs are coming in the next minor (see roadmap).

  • Library root (import 'pocketvex')

    • SchemaDiff: build a migration plan from desired vs current schema
    • Rules / PBRules: helper DSL for PocketBase rule strings (e.g., Rules.public() β†’ 1=1)
  • Config helper

    • import { defineConfig } from 'pocketvex/config'
  • Type helpers (opt‑in)

    • import type { SchemaFieldStrict } from 'pocketvex/types/schema-strict'
    • import type { Migration } from 'pocketvex/types/migration'

Examples

import { SchemaDiff, Rules } from 'pocketvex';
import { defineConfig } from 'pocketvex/config';
import type { SchemaFieldStrict } from 'pocketvex/types/schema-strict';

// Rules helper
const canEdit = Rules.or(Rules.owner('author'), Rules.role('admin'));

// defineConfig helper
export default defineConfig({
  hosts: [{ name: 'local', url: 'http://127.0.0.1:8090' }],
});

// strict field typing
const fields: SchemaFieldStrict[] = [
  { name: 'name', type: 'text', required: true },
];

Planned next‑minor additions (non‑breaking):

  • pocketvex/client β†’ typed PocketBaseClient (auth, fetch/apply, plan exec)
  • pocketvex/dev β†’ typed DevServer (watch, apply safe ops, emit types, VM mode)

Schema Definition

interface SchemaDefinition {
  collections: SchemaCollection[];
}

interface SchemaCollection {
  id?: string; // Stable ID for relations
  name: string; // Collection name
  type?: 'base' | 'auth'; // Collection type
  schema?: SchemaField[]; // Field definitions
  indexes?: string[]; // SQL indexes
  rules?: SchemaRules; // Access rules
}

interface SchemaField {
  name: string; // Field name
  type:
    | 'text'
    | 'number'
    | 'bool'
    | 'email'
    | 'url'
    | 'date'
    | 'select'
    | 'json'
    | 'file'
    | 'relation'
    | 'editor';
  required?: boolean; // Required field
  unique?: boolean; // Unique constraint
  options?: {
    // Field-specific options
    min?: number;
    max?: number;
    pattern?: string;
    values?: string[];
    maxSelect?: number;
    maxSize?: number;
    collectionId?: string;
    cascadeDelete?: boolean;
  };
}

interface SchemaRules {
  list?: string; // Who can list records
  view?: string; // Who can view individual records
  create?: string; // Who can create records
  update?: string; // Who can update records
  delete?: string; // Who can delete records
}

API Rules & Access Control

PocketVex uses PocketBase's powerful rule system for access control. Rules are JavaScript expressions that determine who can perform specific operations on your collections.

Rule Operations

  • list: Controls who can fetch multiple records (GET /api/collections/{collection}/records)
  • view: Controls who can fetch a single record (GET /api/collections/{collection}/records/{id})
  • create: Controls who can create new records (POST /api/collections/{collection}/records)
  • update: Controls who can update existing records (PATCH /api/collections/{collection}/records/{id})
  • delete: Controls who can delete records (DELETE /api/collections/{collection}/records/{id})

Rule Syntax

Rules use PocketBase's expression syntax with access to:

  • @request.auth: Current authenticated user (null if not authenticated)
  • @request.data: Data being sent in the request
  • @request.query: Query parameters
  • @request.headers: HTTP headers
  • @request.method: HTTP method (GET, POST, etc.)
  • @request.info: Request metadata (IP, user agent, etc.)

Common Rule Patterns

Note: examples using @request.auth.role assume your auth collection has a custom role field.

// Public access (anyone can read, only authenticated users can write)
rules: {
  list: "1=1",                    // Anyone can list
  view: "1=1",                    // Anyone can view
  create: "@request.auth.id != ''", // Only authenticated users
  update: "@request.auth.id != ''", // Only authenticated users
  delete: "@request.auth.id != ''", // Only authenticated users
}

// Owner-only access
rules: {
  list: "@request.auth.id != ''",           // Only authenticated users
  view: "@request.auth.id != ''",           // Only authenticated users
  create: "@request.auth.id != ''",         // Only authenticated users
  update: "@request.auth.id = author",      // Only the record owner
  delete: "@request.auth.id = author",      // Only the record owner
}

// Admin-only access
rules: {
  list: "@request.auth.role = 'admin'",     // Only admins
  view: "@request.auth.role = 'admin'",     // Only admins
  create: "@request.auth.role = 'admin'",   // Only admins
  update: "@request.auth.role = 'admin'",   // Only admins
  delete: "@request.auth.role = 'admin'",   // Only admins
}

// Mixed access (public read, owner write)
rules: {
  list: "1=1",                              // Anyone can list
  view: "1=1",                              // Anyone can view
  create: "@request.auth.id != ''",         // Only authenticated users
  update: "@request.auth.id = author",      // Only the record owner
  delete: "@request.auth.id = author",      // Only the record owner
}

// Complex conditions
rules: {
  list: "@request.auth.id != '' && (@request.auth.role = 'admin' || @request.auth.role = 'user')",
  view: "@request.auth.id != ''",
  create: "@request.auth.id != '' && @request.auth.role = 'admin'",
  update: "@request.auth.id = author || @request.auth.role = 'admin'",
  delete: "@request.auth.role = 'admin'",
}

Using the Rules Helper

PocketVex provides a Rules helper for building complex rule expressions:

import { Rules } from 'pocketvex';

// Simple rules
const publicRead = Rules.public(); // "1=1"
const authenticatedOnly = Rules.authenticated(); // "@request.auth.id != ''"
const adminOnly = Rules.role('admin'); // "@request.auth.role = 'admin'"

// Complex rules
const ownerOrAdmin = Rules.or(
  Rules.owner('author'), // "@request.auth.id = author"
  Rules.role('admin'), // "@request.auth.role = 'admin'"
);

const authenticatedUser = Rules.and(
  Rules.authenticated(), // "@request.auth.id != ''"
  Rules.or(
    Rules.role('user'), // "@request.auth.role = 'user'"
    Rules.role('admin'), // "@request.auth.role = 'admin'"
  ),
);

// Use in schema
export const schema = {
  collections: [
    {
      name: 'posts',
      type: 'base',
      schema: [
        { name: 'title', type: 'text', required: true },
        { name: 'content', type: 'editor' },
        {
          name: 'author',
          type: 'relation',
          options: { collectionId: 'users' },
        },
      ],
      rules: {
        list: publicRead, // Anyone can list
        view: publicRead, // Anyone can view
        create: authenticatedOnly, // Only authenticated users
        update: ownerOrAdmin, // Owner or admin
        delete: adminOnly, // Only admins
      },
    },
  ],
};

Rule Examples by Use Case

Blog Posts:

rules: {
  list: "1=1",                              // Public blog
  view: "1=1",                              // Anyone can read posts
  create: "@request.auth.role = 'author'",  // Only authors can create
  update: "@request.auth.id = author",      // Only post author can edit
  delete: "@request.auth.role = 'admin'",   // Only admins can delete
}

User Profiles:

rules: {
  list: "@request.auth.id != ''",           // Only authenticated users
  view: "@request.auth.id != ''",           // Only authenticated users
  create: "@request.auth.id != ''",         // Users can create their profile
  update: "@request.auth.id = id",          // Users can only edit their own
  delete: "@request.auth.id = id",          // Users can delete their own
}

Admin-Only Collections:

rules: {
  list: "@request.auth.role = 'admin'",     // Only admins
  view: "@request.auth.role = 'admin'",     // Only admins
  create: "@request.auth.role = 'admin'",   // Only admins
  update: "@request.auth.role = 'admin'",   // Only admins
  delete: "@request.auth.role = 'admin'",   // Only admins
}

Public API:

rules: {
  list: "1=1",                              // Anyone can list
  view: "1=1",                              // Anyone can view
  create: "1=1",                            // Anyone can create
  update: "1=1",                            // Anyone can update
  delete: "1=1",                            // Anyone can delete
}

πŸ”§ Advanced Usage

Programmatic Usage

You can also use PocketVex as a library in your Node.js applications:

import { SchemaDiff } from 'pocketvex';
import type { SchemaDefinition } from 'pocketvex';

// Define your schema
const schema: SchemaDefinition = {
  collections: [
    {
      name: 'users',
      schema: [
        { name: 'name', type: 'text', required: true },
        { name: 'email', type: 'email', required: true, unique: true },
        { name: 'bio', type: 'editor', options: {} },
      ],
    },
  ],
};

// Compare schemas
const currentSchema = await myPbAdmin.fetchCurrentSchema();
const plan = SchemaDiff.buildDiffPlan(schema, currentSchema);

// Apply safe changes
for (const op of plan.safe) {
  await myPbAdmin.applyOperation(op);
}

// DevServer programmatic API will be exposed in the next minor.

Editor Fields (Rich Text)

PocketVex supports PocketBase's editor field type for storing HTML content:

{
  name: 'bio',
  type: 'editor',
  options: {},
}

Editor fields store HTML content like:

<p>
  <strong>Trent</strong> likes
  <span style="text-decoration: underline;">computers</span> &amp; snowboarding
</p>

Common use cases:

  • User bios and profiles
  • Article content
  • Course descriptions
  • Product descriptions
  • Any content requiring rich text formatting

Custom Schema Loading

// Load your own schema
import { mySchema } from './schema/my-app.schema.js';

// Use in dev server
const config = {
  // ... other config
  schemaLoader: () => mySchema,
};

Migration Hooks

// In generated migration files
export const up = async (pb) => {
  // Backup data before destructive changes
  const backup = await backupData(pb, 'users');

  // Migrate field data
  await migrateFieldData(pb, 'users', 'email', (email) => {
    return email.toLowerCase();
  });
};

Environment-specific Configs

# Development
export PB_URL="http://localhost:8090"
export PB_ADMIN_EMAIL="dev@example.com"
export PB_ADMIN_PASS="dev123"

# Production
export PB_URL="https://myapp.pockethost.io"
export PB_ADMIN_EMAIL="prod@example.com"
export PB_ADMIN_PASS="secure_password"

Safety Features

  • Dry Run Mode: Test migrations without applying them
  • Backup Generation: Automatic data backup before destructive changes
  • Confirmation Prompts: Require explicit confirmation for unsafe operations
  • Rollback Support: Easy rollback of migrations
  • Validation: Comprehensive validation of schema changes

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Submit a pull request

CRON Job Scheduling

PocketVex includes comprehensive CRON job examples and demos for PocketBase's JavaScript VM scheduling capabilities:

Available CRON Patterns:

  • Every minute: 0 * * * * * - Session cleanup, real-time monitoring
  • Every 5 minutes: 0 */5 * * * * - Email processing, task queues
  • Every hour: 0 0 * * * * - Analytics generation, data aggregation
  • Daily at midnight: 0 0 0 * * * - Data archiving, cleanup tasks
  • Weekly on Monday: 0 0 9 * * 1 - Weekly reports, maintenance
  • Business hours: 0 */15 9-17 * * 1-5 - Health monitoring, alerts
  • High frequency: */30 * * * * * - Real-time task processing

CRON Job Examples:

# Run CRON jobs demo (interactive)
npx pocketvex demo
# Then select "⏰ CRON Jobs Demo"

Example CRON Jobs:

  • Session Cleanup: Automatically remove expired user sessions
  • Analytics Generation: Create hourly/daily/weekly analytics reports
  • Email Processing: Process queued emails with error handling
  • Health Monitoring: Monitor system health during business hours
  • Data Archiving: Archive old logs and data for performance
  • Task Processing: Handle background tasks and notifications
  • Weekly Reports: Generate and send automated reports

Implementation:

  1. Create JavaScript files in pb_jobs/ directory
  2. Use $jobs.register() to define scheduled jobs
  3. Deploy to your PocketBase instance
  4. Monitor execution in PocketBase logs
// Example: Session cleanup every minute
$jobs.register('session-cleanup', '0 * * * * *', async (cron) => {
  const expiredSessions = await $app
    .db()
    .newQuery('sessions')
    .filter('expires < {:now}', { now: new Date() })
    .all();

  for (const session of expiredSessions) {
    await $app.db().delete('sessions', session.id);
  }
});

PocketBase JavaScript VM Integration

PocketVex provides comprehensive support for all PocketBase JavaScript features, making it truly Convex-like:

πŸ“ Project Structure

your-project/
β”œβ”€β”€ schema/                 # Schema definitions (TypeScript)
β”‚   β”œβ”€β”€ users.schema.ts
β”‚   └── posts.schema.ts
β”œβ”€β”€ pb_hooks/              # Event hooks (JavaScript)
β”‚   β”œβ”€β”€ user-hooks.js
β”‚   └── post-hooks.js
β”œβ”€β”€ pb_jobs/               # Scheduled jobs (JavaScript)
β”‚   β”œβ”€β”€ daily-cleanup.js
β”‚   └── analytics.js
β”œβ”€β”€ pb_commands/           # Console commands (JavaScript)
β”‚   β”œβ”€β”€ user-management.js
β”‚   └── db-maintenance.js
β”œβ”€β”€ pb_queries/            # Raw database queries (JavaScript)
β”‚   β”œβ”€β”€ analytics.js
β”‚   └── migrations.js
└── generated/             # Auto-generated TypeScript types
    β”œβ”€β”€ types.ts
    └── api-client.ts

Event Hooks

Define custom business logic with event hooks:

// pb_hooks/user-hooks.js
$hooks.onRecordAfterCreateSuccess((e) => {
  console.log(`New user registered: ${e.record.get('email')}`);

  // Send welcome email
  const mailer = $app.newMailClient();
  mailer.send({
    from: 'noreply@example.com',
    to: [e.record.get('email')],
    subject: 'Welcome!',
    html: `<h1>Welcome ${e.record.get('name')}!</h1>`,
  });
}, 'users');

$hooks.onRecordValidate((e) => {
  if (e.record.collectionName === 'posts') {
    const title = e.record.get('title');
    if (!title || title.length < 5) {
      throw new Error('Post title must be at least 5 characters');
    }
  }
  e.next();
}, 'posts');

Scheduled Jobs (CRON)

Create automated background tasks:

// pb_jobs/daily-cleanup.js
$jobs.register({
  name: 'daily_cleanup',
  cron: '0 2 * * *', // Every day at 2 AM
  handler: async (e) => {
    // Clean up old sessions
    await $app
      .db()
      .newQuery(
        `
      DELETE FROM _sessions
      WHERE created < datetime('now', '-30 days')
    `,
      )
      .execute();

    console.log('Daily cleanup completed');
    e.next();
  },
});

πŸ’» Console Commands

Add custom CLI commands:

// pb_commands/user-management.js
$commands.register({
  name: 'user:create',
  description: 'Create a new user',
  handler: async (e) => {
    const user = $app.db().newRecord('users');
    user.set('email', 'newuser@example.com');
    user.set('name', 'New User');
    await $app.save(user);

    console.log('User created successfully');
    e.next();
  },
});

Raw Database Queries

Perform advanced database operations:

// pb_queries/analytics.js
const getUserStats = async () => {
  const stats = await $app
    .db()
    .newQuery(
      `
    SELECT
      COUNT(*) as total_users,
      COUNT(CASE WHEN created > datetime('now', '-30 days') THEN 1 END) as new_users
    FROM users
  `,
    )
    .one();

  return stats;
};

TypeScript Type Generation

Automatic type generation from your schema:

// generated/types.ts (auto-generated)
export interface UsersRecord extends AuthRecord {
  name: string;
  email: string;
  bio?: string;
  role?: string;
}

export interface PostsRecord extends BaseRecord {
  title: string;
  content: string;
  author: string;
  published: boolean;
}

// generated/api-client.ts (auto-generated)
export interface PocketBaseAPI {
  users: {
    getList: (
      params?: PocketBaseListParams,
    ) => Promise<PocketBaseResponse<UsersRecord>>;
    getOne: (id: string) => Promise<UsersRecord>;
    create: (data: UsersCreate) => Promise<UsersRecord>;
    update: (id: string, data: UsersUpdate) => Promise<UsersRecord>;
    delete: (id: string) => Promise<boolean>;
  };
  // ... other collections
}

Development Workflow

  1. Define Schema: Create TypeScript schema files
  2. Add Business Logic: Write event hooks in JavaScript
  3. Create Automation: Add scheduled jobs and console commands
  4. Start Development Server: npx pocketvex dev (repo-dev from source: bun run dev-js)
  5. Real-time Sync: Files are watched and synced automatically
  6. Type Safety: TypeScript types are generated from schema

Available JavaScript VM APIs

  • $app - Main application instance
  • $hooks - Event hook registration
  • $jobs - Scheduled job registration
  • $commands - Console command registration
  • $http - HTTP request client
  • $realtime - Realtime messaging
  • $filesystem - File operations
  • $log - Logging utilities
  • $app.db() - Database operations
  • $app.newMailClient() - Email client

This comprehensive integration makes PocketBase development as smooth and type-safe as Convex! πŸŽ‰

License

MIT License - see LICENSE file for details

Acknowledgments

  • Inspired by Convex's developer experience
  • Built for the PocketBase community
  • Uses Bun for fast development experience

About

PocketVex provides a Convex-style development experience for PocketBase, allowing you to define your schema as code and automatically apply changes during development.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors