Skip to content

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

License

Notifications You must be signed in to change notification settings

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

No packages published