-
v0.7.1 Stable
released this
2026-08-23 18:31:41 +00:00 | 0 commits to main since this releaseVSKI v0.7.1
Dashboard: database-scoped collections (web UI)
The standalone dashboard now works with the per-database collection
registry introduced in v0.7.0. Browsing a collection that lives outside
thedefaultdatabase previously failed with
{"error":"Collection not found"}— every dashboard request resolved in
default, where the collection does not exist.- Sidebar collection links carry their database
(/collections/<name>?db=<dbName>), so the same bare name in two
databases is addressable and never ambiguous. - Collection pages resolve the database from the link (falling back
to thedefaultdb when the URL is unqualified) and issue every
records operation — list, create, update, delete, bulk — through the
db-scoped SDK namespace. - Collection settings (schema/indexes/views/rules editor) and the
API Rules page PATCH the collection through its own database;
relation pickers load their options from the same db. - System collections (
_api_keys,_sql_snippets, …) keep resolving
from any namespace, unchanged.
SDK (TS/JS) v2.0.4
Published to the package registry.
client.db(name)now also exposes the
settings namespace (client.db(name).settings.collections.*) so
registry operations resolve within the named database; records namespaces
were already scoped. This is the version the dashboard ships against.Compatibility
No API or storage changes — v0.7.1 is the dashboard + SDK companion
release for the v0.7.0 per-database registry.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Sidebar collection links carry their database
-
v0.7.0 Stable
released this
2026-08-23 17:17:00 +00:00 | 2 commits to main since this releaseVSKI v0.7.0
Per-Database Collection Names
Collection names are now scoped per database instead of globally unique.
Two databases may each have anorderscollection — they are fully
independent namespaces with independent schemas, records, views, indexes,
realtime topics, and tombstones. Multi-tenant consumers no longer need to
prefix collection names to avoid collisions.Breaking Changes
- Bare-name cross-database access no longer falls back to a global
lookup. Collection names and IDs resolve strictly within the database
addressed by thex-dbnameheader (defaultdefault); a wrong or
missing header is a 404. Cross-database access must qualify the
database (client.db(name).collection(...),collection(name, db), or
thex-dbnameheader). - Registry uniqueness is now
(dbName, name)instead ofname
alone. Creating a collection that duplicates a live name in the same
database still fails withCollection name already exists; the same
name in another database is allowed. - Realtime topics are keyed
(db, name)and the connection's?db=
parameter is authoritative: SUBSCRIBE resolves the collection in that
database and a miss is a loud ERROR frame. EVENT/SUBSCRIBED frames
carry a new additivedbfield. - Duplicate relation targets must be qualified. Relation fields may
declare an optionaloptions.dbfor cross-database targets; relations
without it resolve in the source collection's own database (then a
unique global match). A name present in several databases with no
qualifier is a 400 naming the candidates.
Compatibility
- System collections (
_users,_stats_*, workflow queues) are
platform-global and keep resolving from any namespace — stats and
workflow surfaces are unchanged. - Legacy database files migrate automatically on boot: the inline
UNIQUE(name)constraint is replaced by theuq_collections_db_name
composite index via an idempotent table rebuild; rows are preserved. - Create-over-tombstone now revives the tombstoned row under its
original id instead of a blind name-keyed resurrection followed by an
error. - Options-based views in non-default databases now resolve (the options
lookup previously ran against the target file, which never holds
registry rows).
SDKs
- client (TS/JS): registry ops accept a database scope
(client.db(name).settings), realtime subscriptions re-key to the
db:nametopic and dispatch on the frame'sdb. - client-python: every
settings.collectionsmethod takes an
optionaldb; realtime dispatch filters subscription entries by the
frame'sdb.client.collection(name, db)was already scoped.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Bare-name cross-database access no longer falls back to a global
-
v0.6.9 Stable
released this
2026-08-14 10:36:13 +00:00 | 5 commits to main since this releaseVSKI v0.6.9
Maintenance Release
Python SDK Release
Breaking Changes
None
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.6.8 Stable
released this
2026-03-02 17:44:24 +00:00 | 8 commits to main since this releaseVSKI v0.6.8
Maintenance Release
Mostly fixing links in documentation and references in package.
Migrate Domain Name
Previously packages and containers were hosted on
git.vski.sh/xnow it's justvski.sh/xBreaking Changes
None
Downloads
-
Source code (ZIP)
3 downloads
-
Source code (TAR.GZ)
2 downloads
-
Source code (ZIP)
-
v0.6.7 Stable
released this
2026-02-27 10:47:04 +00:00 | 8 commits to main since this releaseVSKI 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
statsandworkflows) - SQL cron jobs on non-replicated databases: Execute normally (e.g., cleanup jobs on
statsorworkflows)
This ensures that:
- Replicas don't trigger duplicate HTTP webhooks
- SQL jobs don't conflict with master's data (replicated DBs are read-only)
- 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 replicasinternal/services/cron.go- Added replica mode support, skip jobs on replicated databasesinternal/realtime/gateway.go- AddedSetDisabled()method, reject WS connections when disabledinternal/services/webhooks.go- AddedSetDisabled()method, skip triggers when disabled
Breaking Changes
None
Migration
No migration required.
Downloads
-
Source code (ZIP)
3 downloads
-
Source code (TAR.GZ)
4 downloads
-
v0.6.6 Stable
released this
2026-02-27 08:51:22 +00:00 | 9 commits to main since this releaseVSKI 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
- Replica Identification: Replicas send
X-Replica-IdandX-Replica-Public-Urlheaders to master during sync - Master Tracking: Master stores known replicas in
_known_replicastable with their public URLs - Response Headers: Replicas include
X-Replica-Idheader in all response - Client SDK: Client automatically captures
X-Replica-Idfrom responses and includes it in subsequent request - Workflow Routing: Workers can specify
replicaIdwhen 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
- Initial Sync: Fresh replicas download the full database from master
- Session Reset: After full download, replica resets master's session to ensure clean changeset tracking
- Schema Comparison: Replicas compute and compare schema hashes with master before attempting incremental sync
- Schema Migration: If schemas differ, replica fetches master schema and applies migrations (CREATE TABLE, ALTER TABLE, indexes, triggers, views)
- Changeset Application: After schema sync, replicas apply changesets for data changes (INSERT, UPDATE, DELETE)
- 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:
- Hash-Based Detection: Schema hashes are computed from
sqlite_master(tables, indexes, triggers, views) - Schema Fetch: Replica fetches full schema from master via
/api/replica/schema-sync - Diff Computation: Replica computes schema diff (new tables, altered tables, new/dropped indexes, triggers, views)
- Migration Application: Replica applies schema migrations:
CREATE TABLEfor new collectionsALTER TABLE ADD COLUMNfor new fields (columns are never deleted or renamed)CREATE INDEX,CREATE TRIGGER,CREATE VIEWfor new objectsDROP INDEX,DROP TRIGGER,DROP VIEWfor 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=60New Admin Endpoints
Endpoint Description GET /api/admin/known-replicasList all known replicas that have synced with master GET /api/admin/known-replicas?with_public_url=trueFilter 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
statsandworkflows) - SQL cron jobs on non-replicated databases: Execute normally (e.g., cleanup jobs on
statsorworkflows)
This ensures that:
- Replicas don't trigger duplicate HTTP webhooks
- SQL jobs don't conflict with master's data (replicated DBs are read-only)
- 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-syncGET Get full schema from master for migration (internal) /api/replica/reset-sessionPOST 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 detectioninternal/replica/session_manager.go- Session management,ResetSession()method, schema hash computation,GetFullSchema()internal/api/replica.go-ResetSessionendpoint,SchemaSyncendpoint, changeset generationinternal/config/config.go- AddedReplicaIDandPublicURLconfig fieldsinternal/replica/middleware.go- AddedX-Replica-IdtoReplicaStatusMiddlewareinternal/replica/types.go- AddedKnownReplicastructinternal/replica/registry.go- AddedUpsertKnownReplica,ListKnownReplicaswith filterinternal/db/db.go- Added_known_replicastable schemainternal/app/bootstrap.go- Passcfg.ReplicaIDto syncer and middleware, configure cron replica modeinternal/services/cron.go- Added replica mode support, skip jobs on replicated databasesclient/src/client.ts- AddedreplicaIdproperty,health()method, capture from response headersclient/src/types.ts- AddedreplicaIdtoWorkflowOptionsclient/src/api/replicas.ts- New namespace for known replicas managementweb/lib/sdk.tsx- Use/healthendpoint for connectivity checkweb/components/layout/ConnectionBadge.tsx- Display known replicas in server list
Breaking Changes
None
Migration
No migration required. The
_known_replicastable is created automatically on startup.Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
2 downloads
- Replica Identification: Replicas send
-
v0.6.5 Stable
released this
2026-02-26 13:45:57 +00:00 | 10 commits to main since this releaseVSKI v0.6.5
Features
Workflow Execution on Replicas
Replicas can now execute workflows locally using their own
workflows.dbdatabase. Sinceworkflows.dbandstats.dbare excluded from replication, each replica maintains independent workflow state while still having access to synced user data fromdefault.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 endpointFile 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
- File Journal: Master server tracks file operations (add/delete) in a
_file_journaltable - Automatic Sync: After each database sync, replicas fetch and apply file changes
- 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=60New Replica Endpoints
Endpoint Description GET /api/replica/files?since=<id>List file journal entries since the given ID GET /api/replica/file/*pathDownload a file from the master server POST /api/replica/files/ackAcknowledge synced files (triggers cleanup) Technical Details
- File journal is stored in
default.dband 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_DAYS7Days to retain file journal entries (time-based cleanup) Technical Details
Files Changed
internal/config/config.go- AddedFileSyncRetentionDaysconfig optioninternal/db/db.go- Added_file_journaltable and indexinternal/replica/file_journal.go- New file journal service with cleanup schedulerinternal/replica/syncer.go- Added file sync methodsinternal/replica/types.go- Added file sync request/response typesinternal/api/replica.go- Added file sync endpointsinternal/services/storage.go- Added journal recording for file operationsinternal/api/records.go- Record deletion now triggers file cleanupinternal/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_journaltable is created automatically on startup for master servers using local storage.Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
3 downloads
-
v0.6.0 Stable
released this
2026-02-25 21:02:55 +00:00 | 12 commits to main since this releaseVSKI 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
defaultdatabase. 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.dbsystem 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
workflowsdatabase is automatically omitted from replication
This follows the same pattern as the existing
statsdatabase 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 onlyReplica 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/tenant1Technical Details
- Added SQLite session extension support in vski-sqlite driver
- New system tables:
_replicas,_replication_state - New system database:
workflows.dbfor 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.dbis created automatically on startup.Migration
No migration required. New system tables and the
workflows.dbare created automatically on startup. Existing workflow data indefault.dbwill need to be manually migrated if needed.Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
-
v0.5.6 Stable
released this
2026-02-23 18:02:48 +00:00 | 12 commits to main since this releaseVSKI 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-dbnameheader 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_ORIGINSenvironment variable. -
JWT Secret Warning - In production mode (when
DEVis not set totrue), a red warning is now printed ifJWT_SECRETis 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_afterwhen 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_HOUR5Max auth attempts per email per hour AUTH_RATE_LIMIT_PER_DAY24Max auth attempts per email per 24 hours Technical Details
Files Changed
internal/config/config.go- AddedAllowedOrigins,AuthRateLimitPerHour,AuthRateLimitPerDayconfig options and production warningsinternal/db/db.go- AddedsanitizeDBName()function to prevent path traversalinternal/middleware/middleware.go- AddedRateLimiter,RateLimitMiddleware, updatedCORSMiddlewareto accept allowed origins, fixed user enumerationinternal/utils/query.go- ImprovedSqlEscapeIdentifier()to escape double quotesinternal/realtime/gateway.go- Added origin validation for WebSocket connectionsinternal/workflow/gateway.go- Added origin validation for WebSocket connectionsinternal/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_ORIGINSenvironment variable for CORS configurationAUTH_RATE_LIMIT_PER_HOURenvironment variable (default: 5)AUTH_RATE_LIMIT_PER_DAYenvironment variable (default: 24)- Production warning for missing
JWT_SECRET - Production warning for
ALLOWED_ORIGINS=*
Changed
SqlEscapeIdentifier()now properly escapes double quotesCORSMiddleware()now accepts allowed origins parameter- WebSocket gateways now validate Origin header
- Generic "Unauthorized" error message for auth failures
Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
2 downloads
- Path Traversal Prevention - Database names from
-
v0.5.5 Stable
released this
2026-02-23 09:19:11 +00:00 | 13 commits to main since this releaseVSKI 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 ininternal/utils/query.go- E2E test suite for filter functionality (
52_filters_test.ts)
Changed
- Regex operator alternation reordered to match longer operators first
Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
1 download
- Better filter operator parsing - Reordered regex alternation to match longer operators first (