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.
- 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
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# Clone and install dependencies
git clone https://github.com/trentbrew/pocketvex.git
cd pocketvex
bun install
# Or with npm
npm installSet 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"# 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 devHost 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.
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
# 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"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)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 --onceAuto-Directory Creation: When you run npx pocketvex dev, it automatically creates:
pocketvex/schema/- For your TypeScript schema filespocketvex/jobs/- For CRON jobs and scheduled taskspocketvex/hooks/- For event hooks and middlewarepocketvex/commands/- For console commandspocketvex/queries/- For custom queriespocketvex/migrations/- For generated migration filesgenerated/- 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
-
Start the dev server (creates directories automatically):
npx pocketvex dev
-
Edit schema files in the
schema/directory or JavaScript VM files inpb_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 }, ], }, ], };
-
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/
-
Review and apply migrations:
npx pocketvex migrate up
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
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 collectionpocketvex/posts.tsβ Optional specialization built onrecords.ts(for demos)pocketvex/index.tsβ Barrel exports (optional)src/lib/pb.tsorpocketvex/client.tsβ Client factory/singleton using PB URL from envpocketvex/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:
- File discovery: PocketVex watches all JavaScript files in these directories
- No auto-upload: It prints which files are ready; you deploy them (local copy or pack)
- Local copy option: For self-hosted/local PB, copy files into
pb_*dirs and restart PB - 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:
-
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/
-
Restart PocketBase to load the new JavaScript files
-
Or use PocketBase Admin UI to upload files directly
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 demoNote: You'll need the actual admin credentials for the live PocketBase instance to run the full demo.
PocketVex includes interactive demos that show real-time schema migrations:
realtime-migration: Interactive demo that creates collections and applies schema changes in real-timeincremental-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
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-jsThis 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
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;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
# 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# 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# 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 statuspocketvex/
βββ 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
- β Create new collections
- β Add new fields
- β Update collection rules
- β Add indexes
- β Increase field limits
- β Add select values
- β Add editor fields
- β Change field types
- β Delete collections/fields
- β Make fields required without defaults
- β Make fields unique (may have duplicates)
- β Tighten validation constraints
- β Remove select values
- Edit Schema: Modify your TypeScript schema files
- Auto-Apply: Safe changes are applied immediately
- Generate Migrations: Unsafe changes generate migration files
- Review & Deploy: Review migrations before production deployment
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 schemaRules/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β typedPocketBaseClient(auth, fetch/apply, plan exec)pocketvex/devβ typedDevServer(watch, apply safe ops, emit types, VM mode)
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
}PocketVex uses PocketBase's powerful rule system for access control. Rules are JavaScript expressions that determine who can perform specific operations on your collections.
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})
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.)
Note: examples using
@request.auth.roleassume your auth collection has a customrolefield.
// 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'",
}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
},
},
],
};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
}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.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> & snowboarding
</p>Common use cases:
- User bios and profiles
- Article content
- Course descriptions
- Product descriptions
- Any content requiring rich text formatting
// Load your own schema
import { mySchema } from './schema/my-app.schema.js';
// Use in dev server
const config = {
// ... other config
schemaLoader: () => mySchema,
};// 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();
});
};# 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"- 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
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests
- Submit a pull request
PocketVex includes comprehensive CRON job examples and demos for PocketBase's JavaScript VM scheduling capabilities:
- 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
# Run CRON jobs demo (interactive)
npx pocketvex demo
# Then select "β° CRON Jobs Demo"- 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
- Create JavaScript files in
pb_jobs/directory - Use
$jobs.register()to define scheduled jobs - Deploy to your PocketBase instance
- 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);
}
});PocketVex provides comprehensive support for all PocketBase JavaScript features, making it truly Convex-like:
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
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');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();
},
});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();
},
});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;
};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
}- Define Schema: Create TypeScript schema files
- Add Business Logic: Write event hooks in JavaScript
- Create Automation: Add scheduled jobs and console commands
- Start Development Server:
npx pocketvex dev(repo-dev from source:bun run dev-js) - Real-time Sync: Files are watched and synced automatically
- Type Safety: TypeScript types are generated from schema
$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! π
MIT License - see LICENSE file for details
- Inspired by Convex's developer experience
- Built for the PocketBase community
- Uses Bun for fast development experience