• v0.6.7 fcbf5fd20c

    v0.6.7 Stable

    x released this 2026-02-27 10:47:04 +00:00 | 0 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.6.7

    Features

    Cron Job Handling on Replicas

    Cron jobs are now properly disabled on replicas to prevent duplicate execution and data conflicts.

    Behavior on Replicas

    • HTTP cron jobs: Always skipped on replicas (would cause duplicate external calls)
    • SQL cron jobs: Skipped for replicated databases (all databases except stats and workflows)
    • SQL cron jobs on non-replicated databases: Execute normally (e.g., cleanup jobs on stats or workflows)

    This ensures that:

    1. Replicas don't trigger duplicate HTTP webhooks
    2. SQL jobs don't conflict with master's data (replicated DBs are read-only)
    3. System maintenance jobs on non-replicated databases still run

    Realtime and Webhooks Disabled on Replicas

    On replica servers, the following are now disabled to prevent conflicts:

    • Realtime WebSocket: Connections are rejected with 503 status
    • Collection Webhooks: Trigger webhooks are not emitted on replicas

    This ensures all events originate from the master server, preventing duplicate webhooks and maintaining data consistency.

    For distributed realtime across multiple regions, see the Distributed Realtime Service documentation for implementing a Redis-backed realtime service.

    Technical Details

    Files Changed

    • internal/app/bootstrap.go - Configure cron replica mode, disable realtime/webhooks on replicas
    • internal/services/cron.go - Added replica mode support, skip jobs on replicated databases
    • internal/realtime/gateway.go - Added SetDisabled() method, reject WS connections when disabled
    • internal/services/webhooks.go - Added SetDisabled() method, skip triggers when disabled

    Breaking Changes

    None

    Migration

    No migration required.

    Downloads
  • v0.6.6 48794c0dee

    v0.6.6 Stable

    x released this 2026-02-27 08:51:22 +00:00 | 1 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.6.6

    Features

    Replica Identification and Routing

    Replicas can now identify themselves with a unique ID and public URL, enabling proper routing by load balancer and workflow executors.

    How It Works

    1. Replica Identification: Replicas send X-Replica-Id and X-Replica-Public-Url headers to master during sync
    2. Master Tracking: Master stores known replicas in _known_replicas table with their public URLs
    3. Response Headers: Replicas include X-Replica-Id header in all response
    4. Client SDK: Client automatically captures X-Replica-Id from responses and includes it in subsequent request
    5. Workflow Routing: Workers can specify replicaId when subscribing to workflows to route job to specific replicas

    Incremental Sync

    Replicas now use SQLite changesets for incremental synchronization instead of full database downloads, dramatically reducing sync time and bandwidth.

    How it Works

    1. Initial Sync: Fresh replicas download the full database from master
    2. Session Reset: After full download, replica resets master's session to ensure clean changeset tracking
    3. Schema Comparison: Replicas compute and compare schema hashes with master before attempting incremental sync
    4. Schema Migration: If schemas differ, replica fetches master schema and applies migrations (CREATE TABLE, ALTER TABLE, indexes, triggers, views)
    5. Changeset Application: After schema sync, replicas apply changesets for data changes (INSERT, UPDATE, DELETE)
    6. Automatic Fallback: If replica has no replication state, it downloads full database

    Schema Synchronization

    When schema changes are detected (new collections, field changes, indexes), replicas automatically migrate their schema:

    1. Hash-Based Detection: Schema hashes are computed from sqlite_master (tables, indexes, triggers, views)
    2. Schema Fetch: Replica fetches full schema from master via /api/replica/schema-sync
    3. Diff Computation: Replica computes schema diff (new tables, altered tables, new/dropped indexes, triggers, views)
    4. Migration Application: Replica applies schema migrations:
      • CREATE TABLE for new collections
      • ALTER TABLE ADD COLUMN for new fields (columns are never deleted or renamed)
      • CREATE INDEX, CREATE TRIGGER, CREATE VIEW for new objects
      • DROP INDEX, DROP TRIGGER, DROP VIEW for removed objects
      • Tables are never dropped on replica

    Configuration

    # Replica server configuration
    REPLICA_MODE=replica
    REPLICA_ID=my-replica-001        # Unique identifier for this replica
    PUBLIC_URL=https://replica.example.com  # Public URL for routing
    MASTER_URL=https://master.example.com
    SYNC_INTERVAL=60
    

    New Admin Endpoints

    Endpoint Description
    GET /api/admin/known-replicas List all known replicas that have synced with master
    GET /api/admin/known-replicas?with_public_url=true Filter replicas by presence of public URL

    Client SDK

    // Health check endpoint
    const isHealthy = await client.health();
    
    // Get list of known replicas from master
    const replicas = await client.replicas.listKnown();
    
    // Filter by those with public URLs
    const publicReplicas = await client.replicas.listKnown({ withPublicUrl: true });
    
    // Client automatically captures replicaId from responses
    const records = await client.collection('posts').getList(1, 10);
    console.log(client.replicaId); // Set from X-Replica-Id header
    
    // Route workflow to specific replica
    await client.workflow.start('my-workflow', { arg1: 'value' }, { replicaId: 'replica-001' });
    

    Web Dashboard

    • Connection modal displays known replicas with their IDs for allowing quick switching between master and replica servers
    • Health check endpoint used for connectivity verification

    Cron Job Handling on Replicas

    Cron jobs are now properly disabled on replicas to prevent duplicate execution and data conflicts.

    Behavior on Replicas

    • HTTP cron jobs: Always skipped on replicas (would cause duplicate external calls)
    • SQL cron jobs: Skipped for replicated databases (all databases except stats and workflows)
    • SQL cron jobs on non-replicated databases: Execute normally (e.g., cleanup jobs on stats or workflows)

    This ensures that:

    1. Replicas don't trigger duplicate HTTP webhooks
    2. SQL jobs don't conflict with master's data (replicated DBs are read-only)
    3. System maintenance jobs on non-replicated databases still run

    New Environment Variables

    Variable Default Description
    REPLICA_ID (none) Unique identifier for this replica instance
    PUBLIC_URL (none) Public URL for routing requests to this replica

    Technical Details

    New Replica API Endpoints

    Endpoint Method Description
    /api/replica/schema-sync GET Get full schema from master for migration (internal)
    /api/replica/reset-session POST Reset master's SQLite session after full download (internal)

    Files Changed

    • internal/replica/syncer.go - Incremental sync logic, schema comparison and migration, hash-based change detection
    • internal/replica/session_manager.go - Session management, ResetSession() method, schema hash computation, GetFullSchema()
    • internal/api/replica.go - ResetSession endpoint, SchemaSync endpoint, changeset generation
    • internal/config/config.go - Added ReplicaID and PublicURL config fields
    • internal/replica/middleware.go - Added X-Replica-Id to ReplicaStatusMiddleware
    • internal/replica/types.go - Added KnownReplica struct
    • internal/replica/registry.go - Added UpsertKnownReplica, ListKnownReplicas with filter
    • internal/db/db.go - Added _known_replicas table schema
    • internal/app/bootstrap.go - Pass cfg.ReplicaID to syncer and middleware, configure cron replica mode
    • internal/services/cron.go - Added replica mode support, skip jobs on replicated databases
    • client/src/client.ts - Added replicaId property, health() method, capture from response headers
    • client/src/types.ts - Added replicaId to WorkflowOptions
    • client/src/api/replicas.ts - New namespace for known replicas management
    • web/lib/sdk.tsx - Use /health endpoint for connectivity check
    • web/components/layout/ConnectionBadge.tsx - Display known replicas in server list

    Breaking Changes

    None

    Migration

    No migration required. The _known_replicas table is created automatically on startup.

    Downloads
  • v0.6.5 0b67d2ead8

    v0.6.5 Stable

    x released this 2026-02-26 13:45:57 +00:00 | 2 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.6.5

    Features

    Workflow Execution on Replicas

    Replicas can now execute workflows locally using their own workflows.db database. Since workflows.db and stats.db are excluded from replication, each replica maintains independent workflow state while still having access to synced user data from default.db.

    This enables:

    • Distributed workflow processing - Workers can run on any replica
    • Load distribution - Offload workflow execution from master
    • Local stats collection - Each replica tracks its own statistics

    Configuration

    No additional configuration required. Workflows automatically work on replicas when:

    • Replica has its own workflows.db (created automatically)
    • Workflow endpoints are excluded from read-only middleware
    # Replica configuration
    REPLICA_MODE=replica
    MASTER_URL=https://master.example.com
    SYNC_INTERVAL=60
    
    # Workflows run locally on replica
    # Workers connect to replica's /api/workflow/ws endpoint
    

    File Replication for Replicas

    When using local storage (not S3), replicas can now automatically sync uploaded files from the master server. This ensures file attachments are available on replica servers alongside the database data.

    How It Works

    1. File Journal: Master server tracks file operations (add/delete) in a _file_journal table
    2. Automatic Sync: After each database sync, replicas fetch and apply file changes
    3. Dual Cleanup Strategy:
      • Ack-based: Journal entries are removed after replicas confirm sync
      • Time-based: Entries older than the retention period are automatically cleaned up

    Configuration

    # Master server - file journal is created automatically when:
    # - Not in replica mode
    # - Not using S3 storage
    FILE_SYNC_RETENTION_DAYS=7  # Default: 7 days
    
    # Replica server - file sync is enabled automatically when:
    # - REPLICA_MODE=replica
    # - Not using S3 storage
    REPLICA_MODE=replica
    MASTER_URL=https://master.example.com
    SYNC_INTERVAL=60
    

    New Replica Endpoints

    Endpoint Description
    GET /api/replica/files?since=<id> List file journal entries since the given ID
    GET /api/replica/file/*path Download a file from the master server
    POST /api/replica/files/ack Acknowledge synced files (triggers cleanup)

    Technical Details

    • File journal is stored in default.db and replicates with the database
    • Sync happens immediately after each database sync cycle
    • Files are verified with SHA256 checksum during transfer
    • Journal cleanup runs every 24 hours on the master

    New Environment Variables

    Variable Default Description
    FILE_SYNC_RETENTION_DAYS 7 Days to retain file journal entries (time-based cleanup)

    Technical Details

    Files Changed

    • internal/config/config.go - Added FileSyncRetentionDays config option
    • internal/db/db.go - Added _file_journal table and index
    • internal/replica/file_journal.go - New file journal service with cleanup scheduler
    • internal/replica/syncer.go - Added file sync methods
    • internal/replica/types.go - Added file sync request/response types
    • internal/api/replica.go - Added file sync endpoints
    • internal/services/storage.go - Added journal recording for file operations
    • internal/api/records.go - Record deletion now triggers file cleanup
    • internal/app/bootstrap.go - Wired up file journal and cleanup scheduler

    Breaking Changes

    None. File replication is automatic when using local storage in replica mode. S3 storage continues to handle file replication independently.

    Migration

    No migration required. The _file_journal table is created automatically on startup for master servers using local storage.

    Downloads
  • v0.6.0 0251ae8bea

    v0.6.0 Stable

    x released this 2026-02-25 21:02:55 +00:00 | 4 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.6.0

    Features

    Cron Jobs: Target Database Selection

    SQL-based cron jobs can now specify a target database to execute queries against:

    await client.cron.create({
      name: "cleanup-tenant-logs",
      schedule: "0 2 * * *",
      type: "sql",
      database: "tenant1",  // Execute against specific database
      sql: "DELETE FROM logs WHERE created < datetime('now', '-30 days')",
    });
    

    Previously, all SQL cron jobs executed against the default database. Now you can select any database from the dropdown in the dashboard or specify it via the API.

    Workflows System Database

    Workflow data is now stored in a dedicated workflows.db system database:

    • Isolation: Workflow runs, events, jobs, hooks, and waits are separated from user data
    • Performance: Workflow operations don't impact user database performance
    • Replication-safe: The workflows database is automatically omitted from replication

    This follows the same pattern as the existing stats database for system data.

    Replication (Beta)

    Note

    : Replication is currently in beta. The feature is production-ready but may undergo changes based on feedback.

    • Master/Replica Modes: Run VSKI in master (default) or replica (read-only) mode
    • Pull-Based Sync: Replicas pull data from master via HTTP endpoints
    • Incremental Sync: Uses SQLite session extension for efficient changeset replication
    • Auto-Discovery: Replicas automatically discover and sync all databases
    • Simple Auth: Master and replica share the same JWT_SECRET - no additional keys needed

    Configuration

    # Master server (default)
    JWT_SECRET=your-shared-secret
    REPLICA_MODE=master  # optional, master is default
    
    # Replica server
    REPLICA_MODE=replica
    MASTER_URL=https://master.example.com
    JWT_SECRET=your-shared-secret  # MUST match master
    SYNC_INTERVAL=60  # seconds, 0 = manual only
    

    Replica Endpoints

    Replicas authenticate using JWT tokens generated from the shared JWT_SECRET:

    # Get master status (requires replica JWT)
    curl -H "Authorization: Bearer <replica-jwt>" https://master.example.com/api/replica/status
    
    # Download a database
    curl -H "Authorization: Bearer <replica-jwt>" https://master.example.com/api/replica/db/tenant1
    

    Technical Details

    • Added SQLite session extension support in vski-sqlite driver
    • New system tables: _replicas, _replication_state
    • New system database: workflows.db for all workflow-related data
    • Read-only middleware blocks all mutations when in replica mode
    • Schema versioning for detecting when full sync is required
    • Replica generates its own JWT token using shared JWT_SECRET

    Breaking Changes

    None. Replication is opt-in via environment variables. The workflows.db is created automatically on startup.

    Migration

    No migration required. New system tables and the workflows.db are created automatically on startup. Existing workflow data in default.db will need to be manually migrated if needed.

    Downloads
  • v0.5.6 0251ae8bea

    v0.5.6 Stable

    x released this 2026-02-23 18:02:48 +00:00 | 4 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.5.6

    Security Release

    This release addresses multiple security vulnerabilities identified in a security audit. All users are strongly encouraged to upgrade.

    Security Fixes

    Critical

    • Path Traversal Prevention - Database names from x-dbname header are now sanitized to only allow alphanumeric characters, underscores, and hyphens. This prevents path traversal attacks that could access arbitrary files.

    High

    • SQL Identifier Escaping - SqlEscapeIdentifier() now properly escapes double quotes by doubling them, preventing potential SQL injection via identifier names.

    • CORS Origin Validation - WebSocket connections (realtime and workflow) now validate the Origin header against an allowlist. Configure via ALLOWED_ORIGINS environment variable.

    • JWT Secret Warning - In production mode (when DEV is not set to true), a red warning is now printed if JWT_SECRET is not configured.

    • Rate Limiting on Auth Endpoints - Authentication endpoints now have configurable rate limiting to prevent brute force attacks:

      • Default: 5 attempts per hour per email
      • Default: 24 attempts per 24 hours per email
      • Returns HTTP 429 with retry_after when exceeded

    Medium

    • User Enumeration Fix - Authentication error messages are now generic ("Unauthorized") to prevent user enumeration attacks.

    New Environment Variables

    Variable Default Description
    ALLOWED_ORIGINS * Comma-separated list of allowed CORS origins. Use * for all origins (not recommended for production)
    AUTH_RATE_LIMIT_PER_HOUR 5 Max auth attempts per email per hour
    AUTH_RATE_LIMIT_PER_DAY 24 Max auth attempts per email per 24 hours

    Technical Details

    Files Changed

    • internal/config/config.go - Added AllowedOrigins, AuthRateLimitPerHour, AuthRateLimitPerDay config options and production warnings
    • internal/db/db.go - Added sanitizeDBName() function to prevent path traversal
    • internal/middleware/middleware.go - Added RateLimiter, RateLimitMiddleware, updated CORSMiddleware to accept allowed origins, fixed user enumeration
    • internal/utils/query.go - Improved SqlEscapeIdentifier() to escape double quotes
    • internal/realtime/gateway.go - Added origin validation for WebSocket connections
    • internal/workflow/gateway.go - Added origin validation for WebSocket connections
    • internal/app/bootstrap.go - Wired up rate limiting and CORS with config

    Usage Examples

    # Production configuration
    JWT_SECRET=your-secure-random-string-at-least-32-chars
    ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
    AUTH_RATE_LIMIT_PER_HOUR=5
    AUTH_RATE_LIMIT_PER_DAY=24
    
    # Development (defaults)
    DEV=true
    # ALLOWED_ORIGINS defaults to * (all origins)
    # JWT_SECRET defaults to dev-secret (with warning)
    

    Changelog

    v0.5.6 (2026-02-23)

    Security

    • Fixed path traversal vulnerability in database name handling
    • Fixed SQL injection via unescaped double quotes in identifiers
    • Added WebSocket origin validation (CSWSH prevention)
    • Added rate limiting on authentication endpoints
    • Fixed user enumeration via authentication error messages

    Added

    • ALLOWED_ORIGINS environment variable for CORS configuration
    • AUTH_RATE_LIMIT_PER_HOUR environment variable (default: 5)
    • AUTH_RATE_LIMIT_PER_DAY environment variable (default: 24)
    • Production warning for missing JWT_SECRET
    • Production warning for ALLOWED_ORIGINS=*

    Changed

    • SqlEscapeIdentifier() now properly escapes double quotes
    • CORSMiddleware() now accepts allowed origins parameter
    • WebSocket gateways now validate Origin header
    • Generic "Unauthorized" error message for auth failures
    Downloads
  • v0.5.5 08379c51c7

    v0.5.5 Stable

    x released this 2026-02-23 09:19:11 +00:00 | 5 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.5.5

    Improvements

    • Better filter operator parsing - Reordered regex alternation to match longer operators first (>=, <=, !=) before shorter ones (>, <, =).
    • Parentheses handling - Added stripParens() function to properly remove parentheses from grouped filter conditions.

    Technical Details

    Files Changed

    • internal/services/records.go - Fixed filter being ignored in field rules bypass path (line 131)
    • internal/utils/query.go - Fixed regex operator order and added parentheses stripping

    Filter Examples Now Working

    // Comparison operators
    client.collection("posts").getList(1, 30, { filter: "views >= 100" });
    client.collection("posts").getList(1, 30, { filter: "views <= 500" });
    
    // Logical operators
    client.collection("posts").getList(1, 30, { 
      filter: "published = true AND views > 100" 
    });
    client.collection("posts").getList(1, 30, { 
      filter: "category = 'drafts' OR views >= 1000" 
    });
    
    // Parentheses grouping
    client.collection("posts").getList(1, 30, { 
      filter: "(category = 'tech' OR category = 'news') AND published = true" 
    });
    
    // LIKE operator
    client.collection("posts").getList(1, 30, { 
      filter: "title ~ 'Post'" 
    });
    

    Testing

    New e2e test added: 52_filters_test.ts - Comprehensive test coverage for:

    • Equality operators (=, !=)
    • Comparison operators (>, <, >=, <=)
    • Logical operators (AND, OR)
    • LIKE operator (~)
    • Parentheses grouping
    • Combined filters with sorting
    • Combined filters with pagination

    Changelog

    v0.5.5 (2026-02-23)

    Fixed

    • Filters now work correctly when field rules are active (admin or defined field rules)
    • Filter parser correctly handles >= and <= operators
    • Parentheses in filter expressions are properly handled

    Added

    • stripParens() helper function in internal/utils/query.go
    • E2E test suite for filter functionality (52_filters_test.ts)

    Changed

    • Regex operator alternation reordered to match longer operators first
    Downloads
  • v0.5.0 2b927864d5

    v0.5.0 Stable

    x released this 2026-02-22 21:59:37 +00:00 | 6 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.5.0

    Features

    Complete OpenAPI Documentation for Migrations

    • Added OpenAPI Specs for All Migration Endpoints:

      • All migration endpoints now have complete OpenAPI 3.0 specifications
      • Properly documented with tags, summaries, operation IDs, and response schemas
      • Security requirements (BearerAuth) properly defined for all admin endpoints
      • Query parameters, request bodies, and responses fully specified
    • Endpoints Documented:

      • GET /api/migrations - List all migrations (applied and pending)
      • GET /api/migrations/status - Get the current status of the migration system
      • GET /api/migrations/generate - Generate a new migration file from schema diff
      • POST /api/migrations/save - Save a generated migration to a file
      • GET /api/migrations/load - Load a migration from a file
      • POST /api/migrations/upload - Upload a migration file
      • GET /api/migrations/export - Export all migrations as a ZIP file
      • POST /api/migrations/import - Import migrations from a ZIP file
      • GET /api/migrations/{version}/download - Download a specific migration file as YAML
      • POST /api/migrations/{version}/apply - Apply a migration
      • POST /api/migrations/{version}/rollback - Rollback a migration
      • DELETE /api/migrations/{version} - Delete a migration file and record

    New Migrations Management UI

    • Complete Web Interface for Migration Management:

      • New Migrations page accessible at /migrations in the web dashboard
      • Full CRUD operations for database migrations
      • Visual status indicators (pending, applied, failed)
    • Key Features:

      • Migration Status Overview: Shows applied and pending migration counts, last applied version
      • Generate Migrations: One-click generation from schema diffs
      • Apply/Rollback Migrations: Easy controls to apply or undo migrations
      • View Migration Details: View migration YAML files in a modal, with loading feedback
      • Download Migrations: Export individual migrations as YAML files
      • Export All Migrations: Bundle all migrations as a ZIP file (includes full YAML content)
      • Upload Migrations: Import migration files from local system
      • Delete Migrations: Remove unwanted migrations with confirmation
    • User Experience:

      • Pending migrations shown at the top with prominent Apply buttons
      • Applied migrations show rollback options and expandable details view with YAML content
      • Real-time status updates after actions
      • Loading states for all async operations
      • Confirmation dialogs for destructive actions (apply, rollback, delete)
      • Error and success notifications for all operations
    • Navigation:

      • Migrations link added to SQL section in sidebar navigation
      • Accessible via SQL → Migrations in the dashboard menu

    Code Refactoring

    Unified App Initialization

    • Common App Framework:

      • Created internal/app/bootstrap.go with shared initialization logic
      • NewApp(cfg *config.Config) function returns configured *gin.Engine and a cleanup function
      • Eliminated ~90% code duplication between cmd/server and cmd/standalone
      • Both entry points now use the same initialization
    • NewApp Function Output:

      • NewApp returns (*gin.Engine, func(), error)
      • The *gin.Engine provides access to the Gin router for custom routes.
      • The func() is a cleanup function to gracefully shut down services (e.g., cron, stats, database connections).
    • Mode-Specific Differences:

      • Server Mode (cmd/server/main.go): Uses NewApp and starts the Gin server.
      • Standalone Mode (cmd/standalone/main.go): Uses NewApp, then registers an router.NoRoute() handler to serve the embedded web UI. All API routes are available in both modes.

    Installation

    Binary

    Download binary for your platform:

    # Linux (standalone with embedded UI)
    wget https://git.vski.sh/x/platform/releases/download/v0.5.0/vski-standalone
    chmod +x vski-standalone
    ./vski-standalone
    
    # Linux (API-only)
    wget https://git.vski.sh/x/platform/releases/download/v0.5.0/vski
    chmod +x vski
    ./vski
    

    Docker

    Pull and run official Docker image:

    # Light version (API only, no embedded UI)
    docker pull git.vski.sh/x/vski:latest
    
    # Standalone version (with embedded web UI)
    docker pull git.vski.sh/x/vski:latest-standalone
    
    # Pull specific version
    docker pull git.vski.sh/x/vski:v0.5.0
    docker pull git.vski.sh/x/vski:v0.5.0-standalone
    
    # Run light version with default configuration
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest
    
    # Run standalone version with embedded web UI
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest-standalone
    

    Configuration

    Create a .env file:

    DATA_DIR=./data
    SERVER_PORT=3000
    JWT_SECRET=your-secret-key
    

    Usage

    Accessing OpenAPI Documentation

    The complete OpenAPI 3.0 specification is available at:

    GET /api/openapi.json
    

    This includes all endpoints including the newly documented migration APIs.

    Managing Migrations via Web UI

    Access the Migrations management page by navigating to:

    • URL: http://localhost:3000/migrations
    • Navigation: SQL → Migrations in the sidebar

    Common Workflows:

    1. Generate a new migration:

      • Click the "Generate" button to create a migration from current schema diff.
      • The new migration will appear in the "Pending Migrations" section and be saved to the data/migrations directory.
    2. Apply a pending migration:

      • Find the migration in the "Pending Migrations" list.
      • Click the "Apply" button.
      • Confirm the action when prompted.
      • The migration will move to "Applied Migrations" after successful application.
    3. Rollback an applied migration:

      • Find the migration in the "Applied Migrations" list.
      • Click the "Rollback" button.
      • Confirm the rollback action.
      • The migration will be reverted to its previous state.
    4. View migration details:

      • Click the JSON icon (view details) on any migration to display its YAML content in a modal.
      • For applied migrations, click the expand chevron (>) to view the YAML content inline.
    5. Export migrations:

      • Single migration: Click "Download" on a specific migration to save its YAML content.
      • All migrations: Click "Export All" to download all migrations as a ZIP file containing individual YAML files.
    6. Import migrations:

      • Click "Upload" button.
      • Select a migration YAML file or a ZIP file containing multiple migration YAMLs.
      • Click "Upload" to import the migration(s).

    Custom Router Integration

    You can now extend the VSKI server with custom routes using the NewApp function:

    package main
    
    import (
        "fmt"
        "git.vski.sh/x/vski/internal/app"
        "git.vski.sh/x/vski/internal/config"
        "github.com/gin-gonic/gin"
        "log"
        "os"
        "os/signal"
        "syscall"
    )
    
    func main() {
        cfg := config.Load()
        router, cleanup, err := app.NewApp(cfg)
        if err != nil {
            log.Fatalf("Failed to initialize app: %v", err)
        }
        defer cleanup()
        
        // Add custom routes
        router.GET("/custom", func(c *gin.Context) {
            c.JSON(200, gin.H{"message": "Custom endpoint"})
        })
        
        log.Printf("Starting custom server on port %s", cfg.ServerPort)
        go func() {
            if err := router.Run(fmt.Sprintf(":%s", cfg.ServerPort)); err != nil {
                log.Fatalf("Failed to start server: %v", err)
            }
        }()
    
        quit := make(chan os.Signal, 1)
        signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
        <-quit
    
        log.Println("Shutting down server...")
    }
    

    Migration Notes

    Database Migration Required

    Adding fieldRules Column to collections Table

    For existing deployments, you need to manually add the fieldRules column to the collections table. Run the following SQL in your database:

    ALTER TABLE collections ADD COLUMN fieldRules TEXT;
    

    Note: This change is backward compatible. Existing collections will have fieldRules set to NULL, which the application treats as "no field rules defined" (default open behavior).

    Adding fieldEditRules Column to collections Table

    For existing deployments, you need to manually add the fieldEditRules column to the collections table. Run the following SQL in your database:

    ALTER TABLE collections ADD COLUMN fieldEditRules TEXT;
    

    Note: This change is backward compatible. Existing collections will have fieldEditRules set to NULL, which the application treats as "no field edit rules defined" (default open behavior).

    Alternative: Automatic Schema Update

    If you prefer automatic schema updates, you can restart the VSKI server which will apply any missing columns during database initialization. The fieldRules column will be automatically added on first startup.

    Breaking Changes

    None.

    API Changes

    New OpenAPI Endpoints Documented

    The following migration endpoints now have complete OpenAPI specifications:

    • GET /api/migrations - List all migrations (applied and pending)
    • GET /api/migrations/status - Get migration system status
    • GET /api/migrations/generate - Generate new migration from schema diff
    • POST /api/migrations/save - Save migration to file (admin utility)
    • GET /api/migrations/load - Load migration from file path (admin utility)
    • POST /api/migrations/upload - Upload migration YAML file
    • GET /api/migrations/export - Export all migrations as ZIP
    • POST /api/migrations/import - Import migrations from ZIP file
    • GET /api/migrations/{version}/download - Download migration YAML
    • POST /api/migrations/{version}/apply - Apply migration
    • POST /api/migrations/{version}/rollback - Rollback migration
    • DELETE /api/migrations/{version} - Delete migration

    Migration File Format Changes

    Collection Changes Model (internal/models/migrations.go):

    • Added drop_columns: []string field to track removed columns
    • Supports soft delete - columns removed from metadata but kept in database

    Schema Diff Service (internal/services/schema_diff.go):

    • detectDropColumns() function identifies removed fields
    • isEmptyCollectionChanges() filters unchanged collections
    • Empty collection changes are excluded from generated migrations

    Migration Service (internal/services/migrations.go):

    • applyCollectionChanges() now handles drop_columns via metadata update only
    • Better error handling for rollback data parsing

    Code Structure Changes

    Before (separate main.go files):

    cmd/server/main.go    # ~220 lines
    cmd/standalone/main.go # ~205 lines
    

    After (shared initialization):

    internal/app/bootstrap.go   # New file with common logic
    cmd/server/main.go          # ~35 lines (uses app.NewApp(cfg))
    cmd/standalone/main.go      # ~40 lines (uses app.NewApp(cfg) + standalone handler)
    

    Testing

    Run E2E Tests

    cd vski
    make build
    make e2e TEST=50_migrations_test.ts  # Test migrations
    make e2e                             # Run all E2E tests
    

    Verify OpenAPI Spec

    # Build the server
    make build
    
    # Start the server
    ./bin/vski-prod
    
    # Fetch OpenAPI spec
    curl http://localhost:3000/api/openapi.json | jq '.paths | keys | .[] | select(contains("migrations"))'
    

    Expected output (example, actual list may vary slightly depending on other APIs):

    "/api/migrations"
    "/api/migrations/status"
    "/api/migrations/generate"
    "/api/migrations/save"
    "/api/migrations/load"
    "/api/migrations/upload"
    "/api/migrations/export"
    "/api/migrations/import"
    "/api/migrations/{version}/download"
    "/api/migrations/{version}/apply"
    "/api/migrations/{version}/rollback"
    "/api/migrations/{version}"
    

    Test Custom Router Integration

    package main
    
    import (
        "fmt"
        "git.vski.sh/x/vski/internal/app"
        "git.vski.sh/x/vski/internal/config"
        "github.com/gin-gonic/gin"
        "log"
        "os"
        "os/signal"
        "syscall"
        "net/http" // Added for example
    )
    
    func main() {
        cfg := config.Load()
        router, cleanup, err := app.NewApp(cfg)
        if err != nil {
            log.Fatalf("Failed to initialize app: %v", err)
        }
        defer cleanup()
        
        // Add custom routes
        router.GET("/custom", func(c *gin.Context) {
            c.JSON(200, gin.H{"message": "Custom endpoint"})
        })
        
        serverPort := cfg.ServerPort // Get the configured port
        
        log.Printf("Starting custom server on port %s", serverPort)
        go func() {
            if err := router.Run(fmt.Sprintf(":%s", serverPort)); err != nil {
                log.Fatalf("Failed to start server: %v", err)
            }
        }()
    
        // Example of calling a custom route
        time.Sleep(2 * time.Second) // Give server time to start
        resp, err := http.Get(fmt.Sprintf("http://localhost:%s/custom", serverPort))
        if err != nil {
            log.Printf("Error calling custom route: %v", err)
        } else {
            defer resp.Body.Close()
            if resp.StatusCode == http.StatusOK {
                log.Println("Custom route works!")
            } else {
                log.Printf("Custom route returned status: %d", resp.StatusCode)
            }
        }
    
        quit := make(chan os.Signal, 1)
        signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
        <-quit
    
        log.Println("Shutting down server...")
    }
    

    Run this code:

    go run your_custom_main.go
    

    Expected output will show "Custom route works!" if successful.

    Documentation

    Full documentation available at: https://git.vski.sh/x/platform

    Changelog

    v0.5.0 (2026-02-22)

    Added

    • Complete OpenAPI 3.0 specifications for all implemented migration endpoints.
    • internal/app/bootstrap.go with NewApp(cfg *config.Config) for unified app initialization.
    • Full UI for migration CRUD operations (generate, apply, rollback, view, download, upload, export, delete) in web/src/pages/Migrations.tsx.
    • Migration status overview, pending/applied migration counts, and last applied version in UI.
    • Inline display of migration YAML content for applied migrations via expandable details.
    • Loading feedback for asynchronous UI operations in migration management.
    • Dropped column tracking in migrations (drop_columns field in collection changes).
    • Soft delete for columns - dropped columns are untracked from metadata but remain in database (SQLite limitation workaround).
    • Filtering of empty collection changes - generated migrations no longer include empty collection entries.
    • Better error handling when parsing rollback data and generating migrations.
    • Field-level view rules (fieldRules) - Control READ visibility of individual fields by user groups.
    • Field-level edit rules (fieldEditRules) - Control CREATE/UPDATE access to individual fields by user groups.
    • Field-level permissions override global viewRule/createRule/updateRule when defined.
    • Split field permissions UI - Separate "View Rules" and "Edit Rules" sections in collection settings with clear visual indicators.
    • Required field warnings - UI shows warnings when required fields are not in a group's edit allowed list.

    Changed

    • Refactored cmd/server/main.go and cmd/standalone/main.go to use app.NewApp, significantly reducing code duplication.
    • Moved RecoveryMiddleware to internal/middleware/middleware.go for shared use.
    • DownloadMigrationsZip in internal/services/migrations.go now correctly includes full YAML content of migration files in the ZIP archive and uses proper version matching.
    • Corrected display logic for "View" migration YAML in web UI modal.
    • Removed duplicated UI buttons in web/src/pages/Migrations.tsx.

    Technical

    • Implemented backend migration APIs for list, status, generate, save, load, upload, export, import, download, apply, rollback, delete (global operations, not collection-specific).
    • App initialization is centralized in internal/app/bootstrap.go.
    • NewApp returns (*gin.Engine, func(), error) allowing flexible router extension and proper resource cleanup.
    • Standalone mode (cmd/standalone/main.go) integrates embedded web UI via router.NoRoute().
    • Server mode (cmd/server/main.go) serves to API without embedded UI.
    • Frontend MigrationsPage uses sdk.settings.migrations API for all operations with improved user feedback.
    • Added DropColumns field to CollectionChanges struct in internal/models/migrations.go.
    • Added detectDropColumns() function in internal/services/schema_diff.go to identify removed fields.
    • Added isEmptyCollectionChanges() helper function to filter out unchanged collections.
    • Updated applyCollectionChanges() in internal/services/migrations.go to handle dropped columns via soft delete.
    • Improved GenerateMigrationWithVersion() error handling for rollback data parsing.
    Downloads
  • v0.4.0 285040a691

    v0.4.0 Stable

    x released this 2026-02-21 17:12:57 +00:00 | 7 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.4.0

    Bug Fixes

    Cross-Database Relations Expand

    • Backend Expand Operation Fixed:
      • The expand operation now correctly handles relations created via the frontend dashboard
      • Backend previously only checked for collectionId in relation field options
      • Now falls back to toCollection which is used by the frontend when creating collections
      • Fixes silent failures where expand would return just the ID instead of expanded objects

    Frontend Relation Field Improvements

    • Relation Field Selector Display:

      • Relation fields in the add/edit record modal now display as a small table
      • Shows the first non-system field value (excluding id, created, updated)
      • Displays last 4 characters of the record ID for easy identification
      • No longer relies on defaultSearchField which may not be set
    • Edit Record Relation Fields:

      • Fixed crash when editing records with expanded relation fields
      • Relation fields are now properly normalized when loading records for editing
      • Expanded objects are converted to IDs
      • Array relations extract IDs from each expanded item
      • Resolves "Objects are not valid as a React child" error

    Installation

    Binary

    Download binary for your platform:

    # Linux (standalone with embedded UI)
    wget https://git.vski.sh/x/platform/releases/download/v0.4.0/vski-standalone
    chmod +x vski-standalone
    ./vski-standalone
    
    # Linux (API-only)
    wget https://git.vski.sh/x/platform/releases/download/v0.4.0/vski
    chmod +x vski
    ./vski
    

    Docker

    Pull and run official Docker image:

    # Light version (API only, no embedded UI)
    docker pull git.vski.sh/x/vski:latest
    
    # Standalone version (with embedded web UI)
    docker pull git.vski.sh/x/vski:latest-standalone
    
    # Pull specific version
    docker pull git.vski.sh/x/vski:v0.4.0
    docker pull git.vski.sh/x/vski:v0.4.0-standalone
    
    # Run light version with default configuration
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest
    
    # Run standalone version with embedded web UI
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest-standalone
    

    Configuration

    Create a .env file:

    DATA_DIR=./data
    SERVER_PORT=3000
    JWT_SECRET=your-secret-key
    

    Usage

    Expanding Cross-Database Relations

    // Query records with expanded relations (now works correctly)
    const response = await fetch('http://localhost:3000/api/collections/posts/records?expand=author,comments', {
      headers: {
        'Authorization': 'Bearer YOUR_JWT_TOKEN'
      }
    });
    
    const data = await response.json();
    console.log(data.items[0].expand.author); // Expanded object, not just ID
    

    Creating Collections with Relations (Frontend)

    // Frontend creates collections with toCollection option
    const collection = {
      name: 'posts',
      schema: [
        {
          name: 'author',
          type: 'relation',
          options: {
            toCollection: 'users',  // Now correctly handled by backend
            maxSelect: 1
          }
        }
      ]
    };
    
    await fetch('http://localhost:3000/api/collections', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_JWT_TOKEN'
      },
      body: JSON.stringify(collection)
    });
    

    Building from Source

    # Clone repository
    git clone https://git.vski.sh/x/platform.git
    cd platform/vski
    
    # Build production binary
    make build
    
    # Build release binary (with UPX compression)
    make build-release
    
    # Run binary
    ./bin/vski
    

    Migration Notes

    No Migration Required

    All changes are backward compatible. Existing collections and records will work without any changes.

    Breaking Changes

    None.

    API Changes

    Expanded Records Response

    The expand parameter now correctly returns expanded objects for cross-database relations:

    Before (buggy):

    {
      "id": "post1",
      "author": "user123",  // Just the ID
      "expand": {
        "author": "user123"  // Still just the ID, not expanded
      }
    }
    

    After (fixed):

    {
      "id": "post1",
      "author": "user123",
      "expand": {
        "author": {
          "id": "user123",
          "email": "user@example.com",
          "name": "John Doe",
          "created": "2026-02-21T12:00:00Z",
          "updated": "2026-02-21T12:00:00Z"
        }
      }
    }
    

    Testing

    Run E2E Tests

    cd vski
    make build
    make e2e TEST=44_crossdb_expand_test.ts  # Test cross-database expand
    make e2e                                  # Run all E2E tests
    

    Test Relation Expand Manually

    # Create two collections with relation
    curl -X POST http://localhost:3000/api/collections \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -d '{
        "name": "users",
        "schema": [
          {"name": "id", "type": "text", "system": true, "primary": true},
          {"name": "name", "type": "text"}
        ]
      }'
    
    curl -X POST http://localhost:3000/api/collections \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -d '{
        "name": "posts",
        "schema": [
          {"name": "id", "type": "text", "system": true, "primary": true},
          {"name": "title", "type": "text"},
          {"name": "author", "type": "relation", "options": {"toCollection": "users", "maxSelect": 1}}
        ]
      }'
    
    # Create records
    curl -X POST http://localhost:3000/api/collections/users/records \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -d '{"id": "user1", "name": "John"}'
    
    curl -X POST http://localhost:3000/api/collections/posts/records \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -d '{"id": "post1", "title": "Hello", "author": "user1"}'
    
    # Test expand (should return expanded user object)
    curl 'http://localhost:3000/api/collections/posts/records?expand=author' \
      -H "Authorization: Bearer YOUR_TOKEN"
    

    Documentation

    Full documentation available at: https://git.vski.sh/x/platform

    Changelog

    v0.4.0 (2026-02-21)

    Fixed

    • Backend expand operation now correctly handles toCollection option in relation fields
    • Fixed silent failures where expand would return just ID instead of expanded objects
    • Relation field selector in frontend now displays as small table with field value and ID
    • Fixed crash when editing records with expanded relation fields
    • Relation fields are now properly normalized (object to ID) when loading for editing
    • Resolved "Objects are not valid as a React child" error in record edit modal

    Changed

    • Frontend relation picker no longer requires defaultSearchField to be set
    • Frontend automatically detects first non-system field for display
    • Last 4 characters of ID shown instead of full ID for better readability

    Technical

    • Modified internal/repositories/record_repository.go:634 to check both collectionId and toCollection
    • Updated web/components/ui/record-form.tsx to normalize relation fields on record load
    • Enhanced relation field display logic to work with any expanded object structure
    Downloads
  • v0.3.0 99d95aa494

    v0.3.0 Stable

    x released this 2026-02-21 13:34:29 +00:00 | 9 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.3.0

    Features

    OpenAPI Specification Support

    • OpenAPI 3.0.3 Specification:

      • Complete OpenAPI spec generation for all API endpoints
      • Served via /openapi.json and /openapi.html
    • Interactive API Documentation:

      • Embedded Redoc UI with index.html
      • Beautiful, responsive API documentation with Try It Now functionality
      • JWT Bearer authentication support documented
      • Custom theme matching VSKI branding
    • API Endpoint:

      • GET /openapi.json - Retrieve OpenAPI 3.0.3 specification
      • Can be imported into API clients (Postman, Insomnia, etc.)
      • Used for auto-generating SDKs and documentation

    Enhanced Database Management

    • Database Creation API:

      • POST /api/databases - Create new database files dynamically
      • Supports custom descriptions for user databases
      • Automatic registration in _databases table
      • Proper error handling and validation
    • Database Listing Improvements:

      • GET /api/databases - List all databases with metadata
      • Proper NULL handling for descriptions
      • Alphabetical sorting for better UX
      • Detailed error logging with structured logs (slog)
    • Database Management Service:

      • Physical database file creation in DATA_DIR
      • Automatic cleanup on creation failure
      • Integration with existing database provider
    • Reserved Database Protection:

      • default and stats databases are protected from deletion
      • Cannot create databases with reserved names
      • Clear error messages for reserved name violations

    Collection Creation Restrictions

    • Stats Database Protection:

      • Prevent collection creation in stats database
      • stats is reserved for system metrics and analytics
      • Returns clear error message when attempting to create collections
    • Enhanced Validation:

      • Collection creation service now validates database name
      • Prevents accidental data in system databases

    Build Process Improvements

    • Simplified Default Build:

      • make build now produces production binary without UPX compression
      • Faster build times for development iterations
      • Smaller binary footprint for production deployments
    • New Build Targets:

      • make build-release - Production build with UPX compression for smallest size
      • make build-prod - Production build without compression
      • Clear separation between development and release builds

    Installation

    Binary

    Download binary for your platform:

    # Linux (standalone with embedded UI)
    wget https://git.vski.sh/x/platform/releases/download/v0.3.0/vski-standalone
    chmod +x vski-standalone
    ./vski-standalone
    
    # Linux (API-only)
    wget https://git.vski.sh/x/platform/releases/download/v0.3.0/vski
    chmod +x vski
    ./vski
    

    Docker

    Pull and run official Docker image:

    # Light version (API only, no embedded UI)
    docker pull git.vski.sh/x/vski:latest
    
    # Standalone version (with embedded web UI)
    docker pull git.vski.sh/x/vski:latest-standalone
    
    # Pull specific version
    docker pull git.vski.sh/x/vski:v0.3.0
    docker pull git.vski.sh/x/vski:v0.3.0-standalone
    
    # Run light version with default configuration
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest
    
    # Run standalone version with embedded web UI
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest-standalone
    

    Configuration

    Create a .env file:

    DATA_DIR=./data
    SERVER_PORT=3000
    JWT_SECRET=your-secret-key
    

    Usage

    Accessing OpenAPI Specification

    # Get OpenAPI JSON spec
    curl http://localhost:3000/openapi.json > openapi.json
    
    # View interactive documentation
    open http://localhost:3000/
    

    Creating Databases via API

    // Create a new database
    const response = await fetch('http://localhost:3000/api/databases', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_JWT_TOKEN'
      },
      body: JSON.stringify({
        name: 'analytics',
        description: 'User analytics database'
      })
    });
    
    const database = await response.json();
    console.log(database);
    // { id: 'analytics', name: 'analytics', description: 'User analytics database' }
    

    Listing Databases

    // List all databases
    const response = await fetch('http://localhost:3000/api/databases', {
      method: 'GET',
      headers: {
        'Authorization': 'Bearer YOUR_JWT_TOKEN'
      }
    });
    
    const databases = await response.json();
    console.log(databases);
    // [
    //   { id: 'default', name: 'default', description: 'Main database', created: '...', updated: '...' },
    //   { id: 'stats', name: 'stats', description: 'Statistics database', created: '...', updated: '...' },
    //   { id: 'analytics', name: 'analytics', description: 'User analytics database', created: '...', updated: '...' }
    // ]
    

    Error Handling

    // Attempt to create reserved database name
    try {
      const response = await fetch('http://localhost:3000/api/databases', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer YOUR_JWT_TOKEN'
        },
        body: JSON.stringify({
          name: 'default'  // Reserved name!
        })
      });
    
      if (!response.ok) {
        const error = await response.json();
        console.error(error);
        // { error: 'Cannot create reserved database names (default, stats)' }
      }
    } catch (error) {
      console.error('Failed to create database:', error);
    }
    
    // Attempt to create collection in stats database
    try {
      const response = await fetch('http://localhost:3000/api/collections', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': 'Bearer YOUR_JWT_TOKEN',
          'x-dbname': 'stats'  // Reserved database!
        },
        body: JSON.stringify({
          name: 'my_collection',
          schema: {}
        })
      });
    
      if (!response.ok) {
        const error = await response.json();
        console.error(error);
        // { error: 'cannot create collection in reserved database "stats"' }
      }
    } catch (error) {
      console.error('Failed to create collection:', error);
    }
    

    Building from Source

    # Clone repository
    git clone https://git.vski.sh/x/platform.git
    cd platform/vski
    
    # Build production binary (no compression)
    make build
    
    # Build release binary (with UPX compression)
    make build-release
    
    # Run binary
    ./bin/vski
    

    Migration Notes

    Database Migration

    No manual migration required. The _databases table is automatically initialized with default entries on server startup.

    Breaking Changes

    Database Creation Restrictions:

    • Cannot create databases named default or stats (reserved for system use)
    • Cannot create collections in stats database
    • These restrictions were previously unenforced but always intended

    Build Process Changes:

    • make build no longer uses UPX compression by default
    • Use make build-release for compressed (smallest size) binaries
    • This change speeds up development builds while maintaining optimization for releases

    Configuration Updates

    No configuration changes required. All existing configurations work without modification.

    API Endpoints

    OpenAPI Specification

    Method Endpoint Description
    GET /openapi.json Retrieve OpenAPI 3.0.3 spec

    Database Management

    Method Endpoint Request Body Description
    GET /api/databases - List all databases
    POST /api/databases {name, description?} Create new database
    DELETE /api/databases/:name - Delete database

    Database Creation Response

    {
      "id": "analytics",
      "name": "analytics",
      "description": "User analytics database"
    }
    

    Database List Response

    [
      {
        "id": "default",
        "name": "default",
        "description": "Main database",
        "created": "2026-02-21T12:00:00Z",
        "updated": "2026-02-21T12:00:00Z"
      },
      {
        "id": "stats",
        "name": "stats",
        "description": "Statistics database",
        "created": "2026-02-21T12:00:00Z",
        "updated": "2026-02-21T12:00:00Z"
      }
    ]
    

    Testing

    Run E2E Tests

    cd vski
    make build
    make e2e TEST=19_webhooks_test.ts  # Test webhook triggers
    make e2e TEST=10_cron_test.ts       # Test cron integration
    

    Test OpenAPI Spec

    # Start server
    ./bin/vski
    
    # Get and validate OpenAPI spec
    curl http://localhost:3000/openapi.json | jq .
    
    # Validate spec with OpenAPI validator
    # (requires npx or global install)
    npx @apidevtools/swagger-cli validate http://localhost:3000/openapi.json
    

    Test Database Management

    # Create database
    curl -X POST http://localhost:3000/api/databases \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -d '{"name":"testdb","description":"Test database"}'
    
    # List databases
    curl http://localhost:3000/api/databases \
      -H "Authorization: Bearer YOUR_TOKEN"
    
    # Try to create reserved database (should fail)
    curl -X POST http://localhost:3000/api/databases \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer YOUR_TOKEN" \
      -d '{"name":"default"}'
    

    Documentation

    Full documentation available at: https://git.vski.sh/x/platform

    Interactive API documentation:

    • Local: http://localhost:3000/ (when running standalone binary)
    • OpenAPI Spec: http://localhost:3000/openapi.json

    Changelog

    v0.3.0 (2026-02-21)

    Added

    • OpenAPI 3.0.3 specification generation for all API endpoints
    • /openapi.json endpoint for retrieving machine-readable API spec
    • Embedded Redoc UI (index.html) with interactive documentation
    • Database creation API (POST /api/databases)
    • Database listing with metadata and descriptions
    • Database deletion API with reserved name protection
    • Reserved database protection (default, stats from deletion)
    • Collection creation restriction in stats database
    • build-release make target for UPX-compressed production builds
    • logs/ directory to .gitignore

    Changed

    • Default make build no longer applies UPX compression
    • Database list endpoint now sorts results alphabetically
    • Database creation now validates against reserved names
    • Collection creation service validates database name before creation
    • Database list now uses proper NULL handling for descriptions

    Fixed

    • Database creation now creates physical database files
    • Database listing returns proper error messages on failure
    • Clear error messages for reserved database violations
    • Proper error logging with structured logs (slog)

    Technical

    • Added github.com/getkin/kin-openapi v0.133.0 for OpenAPI spec generation
    • Updated related dependencies for OpenAPI support
    • Enhanced error handling throughout database management APIs
    • Improved documentation in AGENTS.md
    Downloads
  • v0.2.0 40c4540455

    v0.2.0 Stable

    x released this 2026-02-17 18:26:44 +00:00 | 10 commits to main since this release

    Signed by x
    GPG key ID: A14ACA8AB45A9C27

    VSKI v0.2.0

    Features

    Enhanced Webhook System

    • Retry Configuration: Configure retry limits (1-100 attempts, default 5) for all webhooks

      • Set retry limit per webhook for fine-grained control
      • Configurable via collection triggers, standalone webhooks, and cron jobs
      • Limits prevent runaway retry loops
    • Exponential Backoff Retry Mechanism:

      • Retry delay increases exponentially: 2^0=2s, 2^1=4s, 2^2=8s, 2^3=16s, 2^4=32s
      • Prevents hammering downstream services during outages
      • Reduces network load and server stress
    • JWT Authentication for Webhooks:

      • Optional JWT bearer tokens for webhook authentication
      • Tokens include roles: ["service", "webhook"] for easy server-side verification
      • Enable per-webhook for secure integrations
    • Restart-Proof Webhook Execution:

      • Webhook state persisted in _webhook_logs table
      • Automatic retry continuation after server restart
      • No lost webhook deliveries during deployments

    Standalone Webhook Management

    • CRUD API for Standalone Webhooks:

      • Create, read, update, delete standalone webhooks
      • Manage webhooks independent of collections
      • Useful for external integrations and scheduled tasks
    • Webhook Manager Service:

      • Dedicated service for webhook lifecycle management
      • Centralized webhook execution logic
      • Consistent behavior across all webhook sources
    • Webhook Registry in Database:

      • New webhooks table stores webhook configurations
      • Fields: name, url, method, headers, body, retryLimit, jwtEnabled, active
      • Persistent storage survives server restarts

    Cron Job Integration

    • Unified Webhook Execution:

      • Cron HTTP callbacks now use webhook service
      • Same retry logic and logging as collection webhooks
      • Consistent experience across all webhook types
    • Enhanced Cron Configuration:

      • Configure retry limit for HTTP cron jobs
      • Enable JWT auth for authenticated HTTP callbacks
      • Custom headers for request authentication
      • Request body for POST/PUT/PATCH methods
    • One-Time Execution:

      • New once flag for cron jobs
      • When once: true, job is automatically deleted after successful execution
      • Perfect for scheduled one-time tasks or delayed execution
      • Works with both SQL and HTTP cron jobs
    • Cron Job Updates:

      • New PATCH /api/cron/:name endpoint for updating existing cron jobs
      • Update schedule, command, and once flag without recreating the job
      • Cron scheduler automatically reloads updated job configuration
      • Frontend edit modal reuses the create form with pre-populated data

    Docker Enhancements

    • Standalone Docker Image:

      • New Docker image with embedded web UI (vski:standalone)
      • Multi-stage build process using denoland/deno:2.6.6 official image
      • Web frontend built and embedded during Docker build
      • No need to build web frontend separately
    • Docker Build Automation:

      • New docker-build-standalone make target for building standalone image
      • New docker-push-standalone make target for pushing standalone image
      • Automatic copying of web source from ../web/ before build
      • Versioned and latest tags automatically applied
    • Release Process Integration:

      • Both light and standalone images built and pushed during release
      • Consistent tagging across image variants ({VERSION}, latest)
      • Automated via release.ts script

    Frontend Enhancements

    • Updated Cron Settings UI:

      • Retry limit configuration (1-100, default 5)
      • JWT auth toggle for HTTP jobs
      • Custom headers input (JSON format)
      • Request body configuration
      • "Delete after successful execution" (once) checkbox
    • Webhooks Manager Page (/settings/webhooks):

      • List all standalone webhooks
      • Create/edit/delete webhooks
      • Manual webhook trigger for testing
      • Status indicators (active/inactive)

    Client SDK Updates

    • Enhanced Trigger Config Types:

      • retryLimit: number (optional)
      • jwtEnabled: boolean (optional)
      • method: string (optional)
      • headers: Record<string, string> (optional)
    • New Webhook Types:

      • Webhook: Standalone webhook configuration
      • CreateWebhookRequest: Input for creating webhooks
      • UpdateWebhookRequest: Input for updating webhooks
    • Webhooks Namespace:

      • sdk.webhooks.list(): List all webhooks
      • sdk.webhooks.get(id): Get webhook by ID
      • sdk.webhooks.create(data): Create new webhook
      • sdk.webhooks.update(id, data): Update webhook
        -sdk.webhooks.delete(id)`: Delete webhook
      • sdk.webhooks.trigger(id): Manually trigger webhook

    Backend API Endpoints

    • Standalone Webhook Management:

      • GET /api/webhooks - List all webhooks
      • GET /api/webhooks/:id - Get webhook details
      • POST /api/webhooks - Create webhook
      • PUT/PATCH /api/webhooks/:id - Update webhook
      • DELETE /api/webhooks/:id - Delete webhook
      • POST /api/webhooks/:id/trigger - Manually trigger webhook
    • Cron Job Management:

      • GET /api/cron - List all cron jobs
      • POST /api/cron - Create new cron job
      • PATCH /api/cron/:name - Update existing cron job
      • DELETE /api/cron/:name - Delete cron job
    • JWT Token Generation:

      • New GenerateTokenWithRoles(id, roles, tokenKey) method
      • Supports custom role claims (e.g., ["service", "webhook"])
      • Used by webhook service for authenticated requests

    Database Schema Changes

    • New webhooks Table:

      CREATE TABLE webhooks (
        id TEXT PRIMARY KEY,
        name TEXT UNIQUE NOT NULL,
        url TEXT NOT NULL,
        method TEXT DEFAULT 'POST',
        headers TEXT,
        body TEXT,
        retryLimit INTEGER DEFAULT 5,
        jwtEnabled BOOLEAN DEFAULT 0,
        active BOOLEAN DEFAULT 1,
        created TEXT DEFAULT (datetime('now')),
        updated TEXT DEFAULT (datetime('now'))
      );
      
    • Updated cron_jobs Table:

      ALTER TABLE cron_jobs ADD COLUMN once BOOLEAN DEFAULT FALSE;
      
      • Added once column for one-time execution flag
      • Default value is false (recurring jobs)

    Installation

    Binary

    Download binary for your platform:

    # Linux (standalone with embedded UI)
    wget https://git.vski.sh/x/platform/releases/download/v0.2.0/vski-standalone
    chmod +x vski-standalone
    ./vski-standalone
    
    # Linux (API-only)
    wget https://git.vski.sh/x/platform/releases/download/v0.2.0/vski
    chmod +x vski
    ./vski
    

    Docker

    Pull and run official Docker image:

    # Light version (API only, no embedded UI)
    docker pull git.vski.sh/x/vski:latest
    
    # Standalone version (with embedded web UI)
    docker pull git.vski.sh/x/vski:latest-standalone
    
    # Pull specific version
    docker pull git.vski.sh/x/vski:v0.2.0
    docker pull git.vski.sh/x/vski:v0.2.0-standalone
    
    # Run light version with default configuration
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest
    
    # Run standalone version with embedded web UI
    docker run -p 3000:3000 -v $(pwd)/data:/app/data git.vski.sh/x/vski:latest-standalone
    
    # Run with custom environment variables
    docker run -p 3000:3000 \
      -v $(pwd)/data:/app/data \
      -e SERVER_PORT=3000 \
      -e DATA_DIR=/app/data \
      git.vski.sh/x/vski:latest-standalone
    

    Configuration

    Create a .env file:

    DATA_DIR=./data
    SERVER_PORT=3000
    JWT_SECRET=your-secret-key
    

    Usage

    Configuring Collection Triggers with Enhanced Options

    await client.settings.collections.update(collectionId, {
      options: JSON.stringify({
        triggers: [
          {
            action: "create",
            url: "https://your-endpoint.com/webhook",
            method: "POST",
            headers: {
              "Authorization": "Bearer token",
              "X-Custom-Header": "value"
            },
            retryLimit: 5,        // Maximum retry attempts
            jwtEnabled: true       // Include JWT bearer token
          }
        ]
      })
    });
    

    Creating Standalone Webhooks

    // Create a new webhook
    const webhook = await client.webhooks.create({
      name: "notification-service",
      url: "https://api.example.com/notify",
      method: "POST",
      headers: JSON.stringify({
        "Authorization": "Bearer your-token",
        "X-Source": "rocketbase"
      }),
      body: JSON.stringify({
        "message": "Hello from webhook"
      }),
      retryLimit: 3,
      jwtEnabled: true,
      active: true
    });
    
    // List all webhooks
    const webhooks = await client.webhooks.list();
    
    // Update a webhook
    await client.webhooks.update(webhook.id, {
      retryLimit: 10,
      active: false
    });
    
    // Delete a webhook
    await client.webhooks.delete(webhook.id);
    
    // Manually trigger a webhook for testing
    await client.webhooks.trigger(webhook.id);
    

    Configuring Cron Jobs with Webhook Options

    // Create HTTP cron job with retry and JWT
    await client.cron.create({
      name: "daily_report",
      schedule: "0 9 * * *",      // 9 AM daily
      type: "http",
      url: "https://api.example.com/reports/daily",
      method: "POST",
      body: '{"report_type": "daily"}',
      headers: JSON.stringify({
        "X-Report-Auth": "secret-key"
      }),
      retryLimit: 5,
      jwtEnabled: true
    });
    
    // Create one-time cron job (deletes after successful execution)
    await client.cron.create({
      name: "send_welcome_email",
      schedule: "*/5 * * * *",     // Run in 5 minutes
      type: "http",
      url: "https://api.example.com/send-email",
      method: "POST",
      body: JSON.stringify({
        "to": "user@example.com",
        "template": "welcome"
      }),
      once: true                   // Delete after execution
    });
    
    // Update an existing cron job
    await client.cron.update("daily_report", {
      schedule: "0 8 * * *",       // Change schedule to 8 AM
      retryLimit: 10,              // Increase retry limit
      jwtEnabled: true             // Enable JWT authentication
    });
    

    Webhook Retry Behavior

    Webhooks now retry with exponential backoff:

    Attempt Delay Description
    1 0s Initial attempt
    2 2s First retry
    3 4s Second retry
    4 8s Third retry
    5 16s Fourth retry
    6 32s Fifth retry (if limit 5)

    After exceeding retryLimit, webhook status is set to failed.

    JWT Token Format

    When jwtEnabled: true, webhooks include a JWT bearer token:

    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5c2VzdmUzIiwid2ViZCIsInR5c2VyX3dlYmhvb2siLCJleHAiOjE3OTY1MzY5MywiaWF0IjoxNzA4NDM2NjkzfQ.abc123...
    

    Token claims:

    {
      "id": "webhook-service",
      "roles": ["service", "webhook"],
      "tokenKey": "webhook-token",
      "exp": 1739843693
    }
    

    Querying Webhook Logs

    // Filter by collection and status
    const logs = await client.webhookLogs.list({
      collection: "posts",
      status: "success"
    });
    
    // Logs include retry information
    const log = logs[0];
    console.log(`Tries: ${log.tries} / ${log.retryLimit || 5}`);
    console.log(`Status: ${log.status}`);
    

    Migration Notes

    Database Migration

    The webhooks table is automatically created on server startup. No manual migration required.

    Breaking Changes

    None. This release is fully backward compatible.

    Configuration Updates

    Existing webhook configurations will continue to work with default values:

    • retryLimit: Defaults to 5 if not specified
    • jwtEnabled: Defaults to false if not specified
    • method: Defaults to POST if not specified

    API Endpoints

    Standalone Webhooks

    Method Endpoint Description
    GET /api/webhooks List all webhooks
    GET /api/webhooks/:id Get webhook by ID
    POST /api/webhooks Create new webhook
    PUT /api/webhooks/:id Update webhook
    PATCH /api/webhooks/:id Partially update webhook
    DELETE /api/webhooks/:id Delete webhook
    POST /api/webhooks/:id/trigger Manually trigger webhook

    Webhook Logs

    Method Endpoint Query Params Description
    GET /api/webhooks/logs collection, status, page, perPage Query webhook delivery logs

    Testing

    Run E2E Tests

    cd vski
    make build
    make e2e TEST=19_webhooks_test.ts  # Test webhook triggers
    make e2e TEST=10_cron_test.ts       # Test cron integration
    

    Documentation

    Full documentation available at: https://git.vski.sh/x/platform

    Changelog

    v0.2.0 (2026-02-17)

    Added

    • Retry limit configuration (1-100, default 5)
    • Exponential backoff retry mechanism (2^n seconds)
    • JWT authentication for webhooks with service,webhook roles
    • Standalone webhook CRUD API and management UI
    • Cron HTTP job integration with webhook service
    • Webhook Manager Service for unified webhook handling
    • Webhooks client namespace for SDK
    • One-time execution flag (once) for cron jobs
    • Automatic deletion of cron jobs with once: true after successful execution
    • Cron job update API (PATCH /api/cron/:name) for modifying existing jobs
    • Cron job edit UI in frontend with pre-populated form data
    • Docker standalone image with embedded web UI (vski:standalone)
    • Multi-stage Docker build process for standalone version
    • Docker build automation targets (docker-build-standalone, docker-push-standalone)

    Changed

    • Cron HTTP jobs now use webhook service instead of direct HTTP calls
    • Enhanced retry logic from fixed 5 attempts to configurable limit
    • Retry delay increased from 5s to 2s initial with exponential growth
    • Docker build process now supports both light and standalone variants

    Fixed

    • Webhook state now persists across server restarts
    • Consistent logging for all webhook types (triggers, cron, standalone)
    Downloads