npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

velocious

v1.0.692

Published

* Concurrent multi threadded web server * Database framework with familiar MVC concepts * Database models with migrations and validations * Database models that work almost the same in frontend and backend, including online CRUD for [composite primary key

Readme

README

  • Concurrent multi threadded web server
  • Database framework with familiar MVC concepts
  • Database models with migrations and validations
  • Database models that work almost the same in frontend and backend, including online CRUD for composite primary keys
  • Connection-scoped advisory locks with automatic cleanup before pooled connections are reused or closed (see docs/advisory-locks.md)
  • Built-in record auditing for model lifecycle changes (see docs/auditing.md)
  • Declarative state machines for models, with typed event methods generated into the base model (see docs/state-machine.md)
  • Migrations for schema changes and UTC datetime storage, including caller-selected pre-runtime and post-publication execution sets and recorded changeTable batches that combine operations into one ALTER on bulk-capable drivers (see docs/database-migrations.md, docs/migration-execution-phases.md, and docs/change-table.md)
  • Tenant-selected base-model and structure generation with one immutable, fail-closed physical database context; tenant-only model metadata initializes only after that context is active (see docs/tenant-selected-database-generation.md)
  • Read-only tenant migration deploy preflight with stable JSON output and fail-closed ledger reads (see docs/tenant-migration-deploy-preflight.md)
  • External packages (engines) that contribute data models, frontend-model resources and migrations to a consuming app (see docs/packages.md)
  • Optional Rampway-owned durable deployment control plane mounted through the standard routes DSL on Velocious 1.0.577 or newer (see docs/rampway-integration.md)
  • Controllers and views for HTTP endpoints
  • Frontend-model transport for creating, updating, querying, and subscribing to query-filtered lifecycle events over HTTP/WebSocket, including committed counter-cache parent updates, structured per-attribute validation error responses, immutable per-operation remote request context, registration-local tenant subscription partitioning, and one-budget WebSocket startup controls (see docs/frontend-models.md, docs/remote-request-context.md, and docs/websocket-channels.md)
  • Client-side offline sync mutation logs and frontend-model optimistic queueing primitives (see the shared-resource sync developer guide and offline sync architecture)
  • Declarative client sync scopes with per-scope cursors, a reusable observable single-flight coordinator with bounded retry and deterministic teardown, generation-fenced identity replacement and selected-scope cache reset, automatic mutation tracking, opt-in durable base-version conflict replay, realtime delivery and reconnect gap closure, computed server-publish scope metadata with atomic null-safe publisher row identities, a shared public maintenance upsert helper and legacy-duplicate reconciliation, and immutable-handle project clients whose local database state plus remote pull/replay/realtime request context stay tenant-bound through reconnect (see docs/sync-client.md, docs/sync-coordinator.md, docs/remote-request-context.md, and docs/offline-sync.md)
  • Reactive useLiveQuery(Model.where(...)) queries for default databases plus immutable-handle tenant live-query sources whose committed events and refreshes stay on the captured physical tenant (see docs/live-queries.md)
  • Server-side sync envelope replay orchestration for app-owned sync receivers, including allowlisted authoritative conflict snapshots that retain submitted aliases in conflict metadata while keying serverModel by canonical model attributes (see docs/sync-envelope-replay-service.md)
  • Self-sustaining sync feeds: upstream imports triggered by the changes pull itself, with framework-owned coalescing and throttling (see docs/sync-upstream-imports.md)
  • AwesomeTasks-shaped offline sync proof using routed resources, domain commands, signed offline grants, and peer-forwarded mutations (see the developer guide and proof)
  • SQLite web persistence that automatically prefers OPFS, then IndexedDB, and migrates legacy persisted bytes when possible (see docs/sqlite-web-persistence.md)
  • Bounded frontend tenant SQLite handles with independently deduplicated per-database migrations/model readiness, React lifecycle integration, durable flush/close, backend-complete deletion, clean-only LRU eviction, scoped pins, and real-browser two-project persistence coverage (see docs/frontend-tenant-sqlite-lifecycle.md)
  • Expo / Metro compatibility guidance and an all-platform export fixture that exercises two real tenant SQLite replicas in exported web and resolves native bundles to expo-sqlite (see docs/expo-metro-compatibility.md)
  • Gap-less positional lists with automatic reordering via actsAsList, including models with numeric, string, or UUID primary keys (see docs/acts-as-list.md)
  • Rails-style nested-attribute writes on frontend-model save() (see docs/nested-attributes.md)
  • Async-aware test-data factories with inherited traits, graph-first native association autosave, metadata-aware override precedence, callbacks, sequences, linting, and a process-global reload-retention budget that bounds cache-busted re-import memory (see docs/factories.md)
  • Opt-in Benchmark-style test profiling with privacy-safe rich JSON, directly reusable duration-aware shard manifests, and strict generic shard-profile aggregation (see docs/test-profiling.md)
  • Per-row association counts via .withCount(...), including cohort-safe intersected filters, safe batching of structurally identical aggregates, and automatic IN-list chunking for large parent sets, on frontend and backend queries (see docs/with-count.md)
  • Consumer-defined per-row SQL aggregates/computations via .queryData(...), with compatible projections sharing a roundtrip while preserving declared alias-overwrite order and automatic IN-list chunking for large parent sets, on frontend and backend queries (see docs/query-data.md)
  • Per-record ability checks via .abilities(...) on frontend queries + record.can(action) (see docs/abilities.md)
  • Translated model attributes with current-locale relationship sorting (see docs/translations.md)
  • Cross-process broadcast bus for broadcastToChannel via velocious beacon, including background job runner processes (see docs/beacon.md)
  • Websocket channel primitives for 1:N pub/sub over the shared session socket (channel-subscribe protocol): matches()-routed streams, interest-gated event-log persistence with 10-minute retention, stream-scoped lastEventId replay with channel-replay-gap gap reporting, and {liveOnly: true} channels that never reach the replay log (see docs/websocket-channels.md)
  • Rails-style application process initializer teardown with immutable process identity, reverse idempotent shutdown, and explicit HTTP/background-job ownership (see docs/application-process-lifecycle.md)
  • Configurable HTTP server worker handlers plus backpressured, descriptor-only file responses with completion callbacks, and a length-determined serveFile static-asset helper for proxies that cannot handle chunked transfer encoding (see docs/http-server.md)
  • Explicit database-free in-process HTTP applications with optional request and buffered-response byte limits (see docs/http-server.md)
  • Client-disconnect hooks for buffered (request.onClientDisconnect / request.clientDisconnected) alongside the streaming response.onStreamClose so long-running handlers can settle in-flight work when the socket goes away (see docs/http-server.md)
  • Default-on buffered HTTP response compression with Brotli/gzip content negotiation, global and per-response opt-outs, and HEAD-correct representation headers (see docs/http-server.md)
  • Background jobs with Node SQL/TCP workers plus a Browser/Expo local SQLite store and in-process dispatcher, including failure events, authorized database-scoped dashboard counts, bounded drain-stall detection/recovery for the main dispatcher (drainStoreOperationTimeoutMs, reported as background-jobs-drain-stall), and an opt-in release-scoped main/worker generation protocol with acknowledged activation, asynchronous retirement, and retired-main recovery. Production compliance additionally requires downstream supervisor retention/activation ordering and release pins (see docs/background-jobs.md, docs/local-background-jobs.md, and docs/background-jobs-dashboard.md)
  • Durable one-off background-job scheduling with exact epoch timestamps plus stable replacement, cancellation, normalized readback, and duplicate-free wake across Node SQL/TCP and Browser/Expo local SQLite adapters (see docs/scheduled-background-job-enqueue.md)
  • Rails-style request and database query logging with structured credential redaction (see docs/logging.md)
  • EJS-backed mailers with delivery, queueing, and payload rendering support (see docs/mailers.md)
  • Trusted reverse proxy handling for request.remoteAddress() (see docs/trusted-proxies.md)
  • In-process driver schema metadata caching (see docs/schema-metadata-cache.md)
  • Planned local-first shared-resource sync architecture (see docs/offline-sync.md)
  • Selective named database connection checkouts, bounded pool waits, debugging held connections, and checkout-scoped MySQL/MariaDB raw session state that resets in place without reconnecting (see docs/database-connections.md)
  • Explicit singular-database operation transactions whose model scopes preserve ownership through records, relationships, lifecycle work, nested savepoints, pre-commit guards, and commit callbacks (see docs/operation-scoped-transactions.md)
  • AbortSignal-driven MySQL/MariaDB query cancellation for raw, model, and cross-tenant aggregate queries (see docs/database-query-cancellation.md)
  • Optional built-in debug endpoint for inspecting server and database connection state (see docs/debug-endpoint.md)
  • Optional built-in API manifest endpoint describing every registered frontend-model resource as human- and machine-readable JSON (see docs/api-manifest-endpoint.md)
  • Backend record attachments with filesystem, S3, native callback, bounded Node path-input persistence, and model-declared client sync policy (see docs/attachments.md)

Setup

Make a new NPM project.

mkdir project
cd project
npm install velocious
npx velocious init

Pinned Git commits can be installed without lifecycle scripts by using a GitHub commit archive. Velocious checks in generated build/ output for this purpose; see Git dependency installation.

By default, Velocious looks for your configuration in src/config/configuration.js. If you keep the configuration elsewhere, make sure your app imports it early and calls configuration.setCurrent().

Application initializers may implement teardown() and inspect their frozen getProcessContext() value. Long-lived process owners call configuration.shutdown() before framework connection cleanup; see the application process lifecycle guide for promise identity, errors, process types, and pooled/forked runner semantics.

Node SQLite driver

Projects using velocious/build/src/database/drivers/sqlite/index.js must install its optional peer dependencies:

npm install sqlite sqlite3

Projects that do not use the Node SQLite driver do not need these packages. Browser and Expo SQLite drivers use their platform-specific dependencies instead.

Operation-scoped transactions

Use configuration.withTransaction for an atomic unit of model work on one database:

await configuration.withTransaction({databaseIdentifier: "default", name: "accept ticket"}, async (operation) => {
  const ticket = await operation.forModel(Ticket).find(ticketId)

  ticket.setAccepted(true)
  await ticket.save()

  await operation.beforeCommit(async ({operation: guardedOperation}) => {
    const currentTicket = await guardedOperation
      .forModel(Ticket)
      .findByOrFail({id: ticketId})

    if (!currentTicket.acceptanceStillOwnedBy(workerId)) {
      throw new Error("Ticket acceptance ownership changed")
    }
  })

  await operation.afterCommit(async () => {
    await publishAcceptedTicket(ticket.id())
  })
})

Use operation-bound model scopes and their loaded records throughout the callback. operation.beforeCommit runs a final operation-owned guard after callback success but before outer commit or nested savepoint release; a rejection rolls back that frame. operation.transaction adds a nested savepoint, and operation.connection() is the deliberate escape hatch for owned raw SQL. Cross-database models, same-identifier tenant switches to another physical database, and operation handles used after the callback are rejected. On shared SQLite/SQL.js pools, unrelated work waits for the operation lease, while admission during an already-open ordinary transaction is rejected. See operation-scoped transactions for guard, pool, after-commit failure, and migration semantics.

Development

When working on Velocious itself, npm scripts are cross-platform (Windows cmd/PowerShell and POSIX shells):

npm run build
npm run test
npm run test:expo

Maintainers cutting a package release must follow the Velocious release runbook; npm run release:patch commits, pushes, and publishes rather than acting as a local-only version command.

Docker development environment

The checked-in root Dockerfile and compose.yml define one canonical dev service used by humans, CI, and agent systems alike (see docs/docker-development-environment.md). The image is Ubuntu 26.04 LTS (pinned by digest) with Node.js 24.x from signed NodeSource, the universal apt coding/debugging baseline, the newest published retained provider CLIs, and owner-pinned native Qwen Code 0.23.3; it is source-independent — no project source is copied and no project dependencies are installed at image build time.

Prerequisites: Docker with the Compose v2 plugin, and this repository checked out at $DEV_HOME_PATH/velocious (default DEV_HOME_PATH: /home/dev).

First-use setup: copy .env.example to the git-ignored .env, set GH_CONFIG_SOURCE_PATH to an existing host GitHub CLI config directory, set AI_PROVIDER_RUNTIME_SOURCE_PATH to the dedicated writable provider runtime, and replace AGENT_CONTEXT_SOURCE_PATH with one exact immutable bundle directory:

cp .env.example .env

$DEV_HOME_PATH must be a dedicated development home that already exists, holds no credentials or secrets, and is owned by (or at least writable by) UID/GID 1000 — the in-container dev user. Do not point it at a general host home directory, and do not recursively chown an existing home; the external environment owns safe initial provisioning.

Normal usage:

docker compose up --build --detach dev
docker compose exec dev bash
scripts/docker-run.sh npm ci   # one-off command in a disposable container

The dev service preserves the complete $DEV_HOME_PATH bind at /home/dev, so dependencies, lane-local caches, settings, and node_modules persist naturally across runs. Codex, Kimi, and OpenCode authentication survives lane recreation through the dedicated provider-runtime bind, while /opt/hermes-agent-context supplies one read-only reviewed guide/skills bundle. Install dependencies with the normal package commands inside the service (for example docker compose exec dev npm ci), never at image build time.

Concurrent isolated instances use the standard Compose project-name contract plus a distinct development home per instance:

COMPOSE_PROJECT_NAME=velocious-review DEV_HOME_PATH=/srv/dev-homes/review \
  docker compose up --build --detach dev

The authorized credential boundaries are the writable provider runtime and the read-only GitHub CLI config. The normal dev service mounts no npm credentials, SSH keys, /opt/data, mutable agent-context discovery links, or broad shared roots. At startup, the provider bootstrap requires real .local and .local/share runtime directories, validates and preserves the provider runtime's canonical relative aliases, serializes shared provider-link migration, then creates exact lane-home provider and /opt/hermes-agent-context discovery links, preserving conflicts only at those managed targets under ~/.provider-runtime-migration-backups/<timestamp>-<unique-suffix>/. The preflight rejects resolved provider-runtime and agent-context sources that contain one another. Threadwire remains parent orchestration, with its provider executable overrides pointed directly at /usr/local/bin/codex, /usr/local/bin/kimi, and /usr/local/bin/opencode. See the setup guide for source-path preflight and the exact lane-local OpenCode state contract.

After changing the Docker artifacts, run the checked-in static contract verifier:

npm run verify:docker-dev-environment

Code quality (fallow)

fallow analyzes the codebase for unused/dead code, duplication, and complexity. CI runs it as a regression gate: it fails only on findings beyond the committed baseline in fallow-baselines/, so existing backlog never blocks a PR but new issues do.

# Regression gate (what CI runs) — fails on new dead code / dupes / complexity beyond the baseline
npm run fallow

# Refresh the baseline after intentionally adding/removing code (commit the updated fallow-baselines/*.json)
npm run fallow:baseline

Baselines are generated against a fresh checkout (no generated dummy configuration.js), so the gate is deterministic in CI and locally. Tuning (entry points, ignores) lives in .fallowrc.json.

Testing

Application tests may import the testing DSL from the independent public package. @velocious/testing 0.0.17 owns framework-neutral expectations, matchers, equality, change observation, mocks, discovery, filtering, execution, reporting, deterministic weighted grouping, profiling, and timing manifests. Compatible installed copies share one protocol-1/schema-3 default registry. Velocious retains application/configuration startup, database and transaction lifecycle, browser execution, environment handling, framework profile spans, and report translation. Existing Velocious import paths remain supported as thin delegating compatibility facades. Package diagnostics are canonical when legacy facades differ only in wording or formatting.

import {describe, expect, it} from "@velocious/testing"

describe("Tasks", () => {
  it("adds a task", () => expect(1 + 1).toEqual(2))
})

The dummy database configurations require MSSQL_SA_PASSWORD whenever they include the shared MSSQL test database. Set it in the local process environment rather than writing the password into spec/dummy/src/config/configuration*.js. TensorBuzz CI provides one shared test value to both the build and MSSQL service environments.

Tag tests to filter runs.

describe("Tasks", {tags: ["db"]}, () => {
  it("creates a task", {tags: ["fast"]}, async () => {})
})
# Only run tagged tests (focused tests still run)
npx velocious test --tag fast
npx velocious test --include-tag fast,api

# Exclude tagged tests (always wins)
npx velocious test --exclude-tag slow

*.browser-spec.js files remain eligible for both the Node database matrix and the browser runner. Add the reserved browser-only tag only when a suite requires the real browser environment; the Node runner discovers the file but does not execute its tagged tests.

describe("Browser integration", {tags: ["browser-only"]}, () => {
  it("uses browser runtime behavior", async () => {})
})

Target a test by line number or description.

npx velocious test spec/path/to/test-spec.js:34
npx velocious test --example "filters on nested relationship attributes"
npx velocious test --example "/nested.*attributes/i"
npx velocious test --name "filters on nested relationship attributes"

Exclude tags via your testing config file.

// src/config/testing.js
import {configureTests} from "velocious/build/src/testing/test.js"

export default async function configureTesting() {
  configureTests({excludeTags: ["mssql"]})
}

Retry flaky tests by setting a retry count on the test args, or set CLI defaults for retries and lifecycle timeout. Import repeatable global setup before the testing configuration and test declarations with --setup.

describe("Tasks", () => {
it("retries a flaky check", {retry: 2}, async () => {})
})
npx velocious test --retry=2 --timeout=30000 --setup=spec/setup.js spec/

Velocious prints the slowest tests after every run so suite hotspots are easy to spot. Each line shows the duration, full description and file:line.

# Default: the 10 slowest tests are printed after the run summary.
npx velocious test

# Report the 25 slowest tests instead.
VELOCIOUS_SLOW_TEST_COUNT=25 npx velocious test

# Disable the report.
VELOCIOUS_SLOW_TEST_COUNT=0 npx velocious test
Slowest 10 tests:
    1145ms  Background jobs - store backfills execution modes for legacy queued jobs (spec/background-jobs/store-spec.js:984)
     913ms  Background jobs - store prunes completed rows past the retention window (spec/background-jobs/store-spec.js:825)
    ...

The report is skipped for single-test runs. See docs/testing-guidelines.md.

Add --profile for a compact Benchmark-style phase and pool summary. Use --profile-json <path> for versioned, privacy-safe detail or --timing-manifest-output <path> to generate a sorted per-file duration map for the existing --timing-manifest shard input; either output flag implies profiling.

npx velocious test --profile-json tmp/test-profile.json \
  --timing-manifest-output tmp/test-timings.json
npx velocious test --groups=4 --group-number=1 \
  --timing-manifest tmp/test-timings.json

# After every shard wrote rich JSON, merge the complete set for the next run
npx velocious test:timing-manifest:merge --output tmp/test-timings.json \
  tmp/profile-1.json tmp/profile-2.json tmp/profile-3.json tmp/profile-4.json

See test profiling for lifecycle accounting, custom activity spans, schema, and privacy guarantees.

Prefer waiting for a real signal or condition over sleeping a fixed duration. waitForEvent(emitter, eventName, {timeoutMs, filter}) resolves the instant a matching event fires (a background job finishing, a model update, a websocket message) and rejects on timeout; for polling an arbitrary condition, use awaitery's waitFor. Both @velocious/testing and the backward-compatible velocious/build/src/testing/test.js facade are supported imports; the Velocious runner consumes public-package declarations and adds the framework-specific startup, database, request, browser, profile-span, and cleanup behavior.

import {waitForEvent} from "velocious/build/src/testing/test.js"
import waitFor from "awaitery/build/wait-for.js"

// Event-driven: resolves as soon as the matching event fires (no polling latency).
const {result} = await waitForEvent(jobRunner, "jobFinished", {filter: (event) => event.jobId === jobId})

// Condition polling: retries the callback until it stops throwing.
await waitFor(() => expect(await Message.count()).toEqual(1))

Velocious captures console output emitted while each test executes, but does not print passing-test output by default. When a test fails, Velocious prints a truncated Console output: block for that failed test and saves the full captured log under tmp/screenshots next to failure screenshots/browser logs/HTML. Each failed test summary prints the saved console log path.

Configure console output behavior in your testing config file.

// src/config/testing.js
import {configureTests} from "velocious/build/src/testing/test.js"

export default async function configureTesting() {
  configureTests({
    consoleOutput: "failure", // default: print captured output only for failed tests
    failedConsoleOutputMaxLines: 200 // default: print the last 200 lines inline
  })
}

Use consoleOutput: "live" to preserve the previous passthrough behavior where test console output is printed while tests run.

Listen for attempt and retry events if you need to reset shared state after a failed attempt or log retry lifecycle details. testAttemptFailed fires after every failed attempt, including the final failed attempt when no retries remain. testRetrying only fires before a retry, and testFailed only fires after retries are exhausted.

A shared resource owner may throw an error carrying terminalResource: {scope: "run", name: "shared-resource"}. With a compatible @velocious/testing runner, Velocious preserves this contract through causes and aggregate errors, reports the originating failure with full causal stacks, suppresses retries, and emits testNotRun for later selected cases instead of running their callbacks. testNotRun receives {configuration, test, testRunner}; test has status: "not-run", fullName, location, and the originating reason. testRunner.getNotRunTests() and getNotRunTestDetails() expose this separate accounting. Entered suites clean up once; the CLI exits unsuccessfully even for terminal setup with zero executed cases. Unmatched filters retain their explanatory message and no-tests profile status, independently of terminal setup failures. This does not restart a browser or cancel already-issued work. Upgrade the shared runner and this reporter adapter together before enabling terminal resource owners; earlier shared runners ignore the marker. See terminal resource reporting.

import {testEvents} from "velocious/build/src/testing/test.js"

testEvents.on("testAttemptFailed", async ({testDescription, attemptNumber, willRetry}) => {
  console.log(`Failed ${testDescription} attempt ${attemptNumber}`)

  if (willRetry) {
    await resetBrowserOrExternalServices()
  }
})

testEvents.on("testRetrying", ({testDescription, nextAttempt}) => {
  console.log(`Retrying ${testDescription} (attempt ${nextAttempt})`)
})

testEvents.on("testRetried", ({testDescription, attemptNumber}) => {
  console.log(`Retry attempt finished for ${testDescription} (attempt ${attemptNumber})`)
})

Parallel test splitting

Split test files across parallel CI jobs using --groups and --group-number.

# Run group 1 of 4
npx velocious test --groups=4 --group-number=1

# Run group 2 of 4
npx velocious test --groups=4 --group-number=2

# Combine with tags
npx velocious test --groups=3 --group-number=1 --tag fast

# Prefer recorded file durations when balancing groups
npx velocious test --groups=4 --group-number=1 --timing-manifest=tmp/test-timings.json

Files are distributed using a greedy load-balancing algorithm. Each file is primarily weighted by a positive finite duration from the optional timing manifest. Manifest keys are normalized project-relative test paths using / separators:

{
  "spec/system/sign-in-spec.js": 42.7,
  "spec/controller/accounts-spec.js": 8.1
}

Files absent from the manifest and zero-duration entries use the existing deterministic heuristic; stale entries are ignored. When --timing-manifest is explicitly supplied, its file must be readable and contain a plain JSON object with canonical relative paths and finite non-negative durations. Invalid input fails the command even without sharding flags. A compact measured/heuristic/stale summary reports coverage. The heuristic weights files by spec directory (system/ = 20, frontend-models/ = 10, controller/ = 3, default = 1) with a 2x multiplier for .browser-spec.js files. The heaviest files are assigned first to the group with the least accumulated weight, producing balanced wall-clock times across groups.

The algorithm is deterministic: the same file list always produces the same group assignments.

Browser system tests

Run browser compatibility tests via System Testing:

npm run test:browser

Browser system tests must be named *.browser-test.js or *.browser-spec.js (override with VELOCIOUS_BROWSER_TEST_PATTERN). The runner validates and persists the exact Chrome/ChromeDriver pair selected by scripts/prewarm-chromedriver.js, then owns ChromeDriver and Chrome as a managed process group. Startup failures report the runtime paths and versions, service URL, retained logs under tmp/browser-test-chrome/, and Chrome process state before and after cleanup.

Use beforeAll/afterAll for suite-level setup/teardown.

// src/config/testing.js
export default async function configureTesting() {
  beforeAll(async () => {
    // setup shared resources
  })

  afterAll(async () => {
    // teardown shared resources
  })
}

Expectations

Common matchers:

expect(value).toBeTruthy()
expect(value).toMatchObject({status: "success"})
expect({a: 1, b: 2}).toEqual(expect.objectContaining({a: 1}))
expect([1, 2, 3]).toEqual(expect.arrayContaining([2, 3]))

Mailers

Mailers live under src/mailers, with a mailer.js and matching .ejs templates.

import VelociousMailer, {deliveries, setDeliveryHandler} from "velocious/build/src/mailer.js"

class TasksMailer extends VelociousMailer {
  newNotification(task, user) {
    this.task = task
    this.user = user
    this.assignView({task, user})
    return this.mail({to: user.email(), subject: "New task"})
  }
}

Velocious infers the action name from the mailer action method when this.mail(...) is called from that method. Pass actionName explicitly when rendering a different action template or when a shared helper should override the inferred action.

<b>Hello <%= mailer.user.name() %></b>
<p>
  Task <%= task.id() %> has just been created.
</p>

Deliver immediately or enqueue via background jobs:

await new TasksMailer().newNotification(task, user).deliverNow()
await new TasksMailer().newNotification(task, user).deliverLater()

For a provider that advertises duplicate suppression, a producer can require one stable mail operation across outbox replay and native-job retries:

await new TasksMailer().newNotification(task, user).deliverLater({
  deliveryOperation: {
    id: `project-command:${command.id()}`,
    idempotency: "required"
  }
})

Required delivery fails before enqueue on unsupported backends, rejects the same id with changed rendered content, and fails closed after the provider retention window. Direct required deliverPayload() calls also fail before provider I/O when the background-jobs database connection is already inside a caller-owned transaction, because an outer rollback could erase the first-attempt marker. Generic SMTP remains at-least-once. Velocious includes a dedicated ResendSmtpMailerBackend for Resend's 24-hour Resend-Idempotency-Key contract; see Mailers for setup, expiry, reconciliation, and non-exactly-once guarantees.

Build the rendered payload without sending when the app needs to store an audit copy or hand delivery to its own transport:

const payload = await new TasksMailer().newNotification(task, user).buildPayload()

If your mailer needs async setup, keep the action sync and pass actionPromise:

resetPassword(user) {
  return this.mail({
    to: user.email(),
    subject: "Reset your password",
    actionPromise: (async () => {
      this.token = await user.resetToken()
      this.assignView({user, token: this.token})
    })()
  })
}

Configure a delivery handler for non-test environments:

setDeliveryHandler(async ({to, subject, html}) => {
  // send the email via your provider
})

Mailer backends can also be configured via your app configuration.

import {SmtpMailerBackend} from "velocious/build/src/mailer.js"

export default new Configuration({
  mailerBackend: new SmtpMailerBackend({
    connectionOptions: {
      host: "smtp.example.com",
      port: 587,
      secure: false,
      auth: {user: "smtp-user", pass: "smtp-pass"}
    },
    defaultFrom: "[email protected]"
  })
})

Install the SMTP peer dependency in your app:

npm install smtp-connection

When connectionOptions.auth is present, the SMTP backend authenticates before sending the message.

Test deliveries are stored in memory:

const sent = deliveries()

Translations

Velocious uses gettext-universal by default. Configure your locales and fallbacks in the app configuration:

export default new Configuration({
  locale: () => "en",
  locales: ["en"],
  localeFallbacks: {en: ["en"]}
})

Load compiled translations for gettext-universal (for example, JS files generated from .po files):

import gettextConfig from "gettext-universal/build/src/config.js"
import en from "./locales/en.js"

Object.assign(gettextConfig.getLocales(), {en})

Use translations in mailer views with _:

<b><%= _("Hello %{userName}", {userName}) %></b>

If you want a different translation backend, set a custom translator:

configuration.setTranslator((msgID, args) => {
  // return translated string
})

Models

npx velocious g:model Account
npx velocious g:model Task

Frontend models from backend resources

You can generate lightweight frontend model classes from resource definitions in your configuration.

import FrontendModelBaseResource from "velocious/build/src/frontend-model-resource/base-resource.js"

class UserResource extends FrontendModelBaseResource {
  static resourceConfig() {
    return {
      attributes: ["id", "name", "email"],
      relationships: {
        projects: {type: "hasMany", model: "Project"}
      }
    }
  }
}

export default new Configuration({
  // ...
  backendProjects: [
    {
      path: "/path/to/backend-project",
      frontendModels: {
        User: UserResource
      }
    }
  ]
})

frontendModels entries must be FrontendModelBaseResource subclasses. Built-in CRUD/find/index/serialize behavior lives in the base class, and app resources override only the pieces they actually need. Resource-level index customization should prefer indexQuery() or the pagination/search/sort hooks over replacing records(), so built-in pluck and aggregate count support can keep using the same query. See docs/frontend-model-resources.md for the resource extension points.

Custom class- and instance-level commands are declared via collectionCommands / memberCommands. Each entry is a plain camelCase method name, or a {name, args?, returnType?} object that types the command's arguments and response — e.g. memberCommands: ["suspend", {name: "refresh", args: [{name: "age", type: "number"}], returnType: "string"}]. Plain string commands derive args and return types from the backend resource method JSDoc; shared DTO @import types outside the backend src tree are preserved in generated frontend models, while backend-local helper types such as ReturnType<typeof serializePayload> are rejected. A command whose args are a single object literal with only optional fields generates an omittable parameter (record.command() works without passing {}); any required field keeps the argument mandatory. See docs/frontend-model-resources.md#custom-commands.

Resources expose the full CRUD ability set (create, destroy, read, update) by default. To restrict the API surface — for example to a read-only resource — declare an explicit subset:

class AuditLogResource extends FrontendModelBaseResource {
  static abilities = ["read"]
  static attributes = ["id", "message", "createdAt"]
}

Generate classes:

npx velocious g:frontend-models

Frontend-model attributes can usually be declared by name. The generator infers JSDoc typedefs and nullability from backend model columns and translated attribute columns. When an attribute entry needs resource-specific options such as selectedByDefault: false, keep only that option in the resource config, for example {name: "archivedAt", selectedByDefault: false}; the column type and nullability are still inferred. For computed resource attributes, add a typed ${attributeName}Attribute(model) method with an @returns tag in the backend project's src tree. Resource attribute return types take precedence over column types because the resource method controls the serialized value. If Velocious cannot infer a read attribute from a column, generated model accessor, resource method JSDoc, or explicit metadata, generation fails with a clear error instead of emitting a broad fallback type.

This creates src/frontend-models/user.js (and one file per configured resource). Import each model directly by its file path (e.g. import User from ".../frontend-models/user.js"); named and default exports remain class declarations, so they can be constructed and referenced as instance types. src/frontend-models/setup.js side-effect-imports every model file so they self-register (import it once at app startup). No barrel/index.js is generated. Every generated file — the per-model files and setup.js here, and the base-model files from g:base-models — starts with an auto-generated banner stating it must not be edited manually because changes are overwritten on the next regeneration, and naming the command that regenerates it. Apply changes at their source (resource/model definitions or the generator) and regenerate. Generated classes support:

  • await User.find(5)
  • await User.findBy({email: "[email protected]"})
  • await User.findByOrFail({email: "[email protected]"})
  • await User.toArray()
  • await User.create({name: "John"})
  • await Task.sort("-createdAt").toArray()
  • await Task.order("-createdAt").toArray()
  • await Task.limit(10).offset(20).toArray()
  • await Task.page(2).perPage(25).toArray()
  • await Task.where({project: {creatingUser: {reference: "owner-b"}}}).toArray()
  • await Task.joins({project: {creatingUser: true}}).where({project: {creatingUser: {reference: "owner-b"}}}).toArray()
  • await Task.sort({project: {creatingUser: ["reference", "desc"]}}).toArray()
  • await Task.sort({project: {account: [["name", "desc"], ["createdAt", "asc"]]}}).toArray()
  • await Task.group({project: {account: ["id"]}}).toArray()
  • await Task.sort({comments: ["body", "asc"]}).distinct().toArray()
  • await Task.count()
  • await Task.pluck("id")
  • await Task.pluck({project: ["id"]})
  • await User.preload({projects: ["tasks"]}).toArray()
  • await Task.load()
  • await Project .preload(["tasks"]) .select({Project: ["id", "createdAt"], Task: ["updatedAt"]}) .toArray()
  • await user.update({...})
  • await user.save() (persists new records and updates existing records; also carries dirty nested children through the single request when the parent opts in — see docs/nested-attributes.md)
  • await user.destroy()
  • user.markForDestruction() to queue a loaded child for destruction on the next parent save (see docs/nested-attributes.md)
  • State helpers like user.isNewRecord(), user.isPersisted(), user.isChanged(), and user.changes()
  • Attribute methods like user.name() and user.setName(...)
  • Relationship helpers (when relationships are configured), for example task.project(), await task.projectOrLoad(), await project.tasks().toArray(), await project.tasks().load(), and project.tasks().build({...})
  • Preload relationships onto records you already have with await record.preload(Model.preload({...}).select({...})) (or Preloader.preload(records, ...) for arrays), including selectsExtra(...) and a {force: true} reload option — see docs/frontend-models.md
  • Attachment helpers (when attachments are configured), for example await task.descriptionFile().attach(file), await task.descriptionFile().download(), await task.files().purgeAll(), and await task.update({descriptionFile: file})

React components can subscribe to lifecycle broadcasts without manual cleanup code:

import useModelClassEvent from "velocious/build/src/frontend-models/use-model-class-event.js"

useModelClassEvent(Subscription, ["create", "update"], () => {
  void loadSubscriptionStatus()
}, {
  query: Subscription.where({workspaceId}),
  requestContext: {workspaceId}
})

useCreatedEvent, useUpdatedEvent, and useDestroyedEvent are also available. useUpdatedEvent and useDestroyedEvent accept either a model class or model instance. Generated lifecycle callbacks retain the concrete generated model type; scalar event ids are strings and composite event ids are objects keyed by the configured primary-key attributes. Lifecycle subscriptions accept the same projection options as frontend-model queries for event records, including select, preload, withCount, abilities, and queryData. Pass a registration-local requestContext when several tenant routes for the same model can be mounted concurrently. Omitting it inherits the configured transport context; passing {} explicitly replaces that context with an unscoped registration. Velocious captures it immutably, sends it to the tenant resolver, and partitions server subscriptions by its value: equal contexts retain multiplexing, while distinct contexts never share an event-filter request. The backend must still authorize the resolved tenant; request context is not proof of access.

Frontend-model group(...) is attribute/path based and does not accept raw SQL fragments. Use model/relationship shapes (for example Task.group({project: {account: ["id"]}})) so grouping resolves through known relationships and mapped columns. Frontend-model where(...) supports nested relationship descriptors (for example Task.where({project: {creatingUser: {reference: "owner-b"}}})) and does not accept raw SQL fragments. Frontend-model joins(...) supports relationship-object descriptors only (for example Task.joins({project: {creatingUser: true}})) and rejects raw SQL join strings. Frontend-model distinct(...) only accepts booleans (true by default) and is applied server-side through the backend query API. Frontend-model pluck(...) validates attribute/path descriptors against configured resource/model metadata and does not accept SQL fragments or hidden raw model columns when the resource declares an explicit attribute list. Frontend-model query fields are limited to attributes exposed by the backend resource. Use {name: "attributeName", selectedByDefault: false} for fields that may be selected or filtered explicitly but should stay out of default payloads.

When backend payloads include __preloadedRelationships, nested frontend-model relationships are hydrated recursively. Relationship methods can use getRelationshipByName("relationship").loaded() and will throw when a relationship was not preloaded.

When queries include select(...), backend frontend-model actions only serialize selected attributes for each model class. Reading a non-selected attribute on a frontend model raises AttributeNotSelectedError.

You do not need to manually define frontend-index / frontend-find / frontend-create / frontend-update / frontend-destroy routes for those resources. Velocious can auto-resolve frontend model command paths from backendProjects.frontendModels.

For backend models, you can declare attachment helpers directly:

Task.hasManyAttachments("files")
Task.hasOneAttachment("descriptionFile")
Task.hasOneAttachment("archivedPdf", {driver: "s3"})
User.hasOneAttachment("profilePicture", {
  sync: {
    fetch: "eager",
    offlineRequirement: "optional",
    retention: "evictable"
  }
})

db:migrate provisions the framework-owned attachment table before runtime attachment work begins. See Backend record attachments for the complete input, storage-driver, lifecycle, and path-security contracts. Offline-capable clients can apply synchronized attachment descriptors through the platform-neutral Synchronized asset cache, while Expo and web packages own their respective byte-storage adapters.

You can also pass a driver class or instance directly on the attachment:

import NativeDriver from "./storage/native-driver.js"

Task.hasOneAttachment("mobileCache", {driver: NativeDriver})
// or:
Task.hasOneAttachment("mobileCache", {driver: new NativeDriver()})

Then use them from backend records:

await task.descriptionFile().attach({
  content: "my file content",
  filename: "file.doc"
})
await task.archivedPdf().attach({
  path: "/var/app/uploads/archive.pdf",
  contentType: "application/pdf"
})
const descriptionFileUrl = await task.descriptionFile().url()
await task.update({
  descriptionFile: {
    contentBase64: Buffer.from("my file content").toString("base64"),
    filename: "my-doc.doc"
  }
})

Purge a record's attachments — both the stored files and their rows — for example before destroying the owner record:

const purgedCount = await task.files().purgeAll()

purgeAll() deletes each attachment's backing storage and then its row, and removes only the attachments that existed when the purge started (a concurrent attach() for the same record/name is left intact). It throws without deleting anything if a storage driver has no delete operation, so a driver configured without deletion can never silently leak storage. It is a no-op for unpersisted records and returns the number of attachments purged.

Configure attachment storage drivers in Configuration:

export default new Configuration({
  attachments: {
    defaultDriver: "filesystem",
    // Path-based attachment input is disabled by default.
    // Enable explicitly only when backend-side file ingestion is needed.
    allowPathInput: false,
    // Optional allowlist when allowPathInput is true.
    allowedPathPrefixes: ["/var/app/uploads"],
    drivers: {
      filesystem: {
        directory: "/tmp/velocious-attachments"
      },
      native: {
        write: async ({attachmentId, contentBase64, filename}) => {
          // Persist using your native file API and return a storage key
          return {storageKey: `${attachmentId}-${filename}`}
        },
        read: async ({storageKey}) => {
          // Return Buffer, Uint8Array, ArrayBuffer or base64 string
          return await readNativeFile(storageKey)
        },
        url: async ({storageKey}) => {
          return `file://${storageKey}`
        }
      },
      s3: {
        bucket: "my-bucket",
        region: "eu-west-1",
        signedUrlExpiresIn: 3600
      }
    }
  }
})

If you want backend-side path ingestion, enable it explicitly:

new Configuration({
  attachments: {
    allowPathInput: true,
    allowedPathPrefixes: ["/var/app/uploads"]
  }
})

Then {path: "..."} inputs are only accepted when the path resolves inside one of the allowed prefixes and the once-opened handle identifies a regular file. Its exact byte size comes from that handle's stat snapshot. The filesystem driver copies it with a bounded, backpressured stream and the S3 driver sends a Node Readable; on current nullable schemas neither driver first materializes the whole file as a Buffer or Base64 value. Legacy schemas with a non-null content_base64 column instead materialize the opened snapshot once before driver persistence and reuse those exact bytes for storage and the database Base64. Path replacement cannot switch the opened source, truncation is rejected, later appends are ignored, and the source is closed after persistence.

The native driver's documented write({contentBase64, ...}) callback remains source-compatible. For path input only, that driver reads and Base64-encodes the opened source after driver selection on current schemas; legacy path input arrives pre-materialized with the same Base64 written to the database. Existing Buffer, string, browser, and UploadedFile inputs keep their in-memory behavior. See docs/attachments.md for the normalized input passed to custom drivers.

For a resource with a backing model, the model attachment declaration automatically generates resourceConfig().attachments; do not repeat it on the resource. Use the generated attachment handles normally:

await frontendTask.update({descriptionFile: file})
const descriptionFile = await frontendTask.descriptionFile().download()
const descriptionFileUrl = await frontendTask.descriptionFile().url()
const descriptionFileMetadata = await frontendTask.descriptionFile().first()
const filesMetadata = await frontendTask.files().toArray()
await frontendTask.attach(file)

Frontend model attachment input does not support {path: ...}. Use File/Blob/bytes/contentBase64 payloads instead. The optional model-level sync block is client-safe policy metadata for asset cache adapters. It distinguishes eager/on-demand fetching, durable/evictable retention, and optional/required offline availability. Required offline assets must be durable. Backend driver configuration never appears in generated frontend models or API manifests. A descriptor ID keeps its digest, byte size, and content type immutably. Cache descriptors that share a digest must agree on byte size and content type, and eager synchronization attempts each shared digest only once per reconciliation. On-demand resolution rechecks the backing blob after cleanup and returns null instead of a stale local URI when concurrent eviction removed it. Cleanup deferred by an active cached resolution runs again after that digest's final guard releases. Attachment metadata is exposed through the built-in VelociousAttachment frontend model with safe fields only: id, recordType, recordId, name, position, filename, contentType, byteSize, createdAt, and updatedAt. Storage internals such as driver, storageKey, and contentBase64 remain hidden and non-queryable. Metadata collection queries require owner filters: resourceName, recordType, recordId, and name. Composite recordId values retain the complete canonical tuple without a 255-character limit, and key-changing saves rekey attachment ownership in the record transaction. VelociousAttachment.find(id) uses the member endpoint and authorizes against configured resource aliases backed by the attachment owner type.

When your frontend app calls a backend on another host/port (or under a path prefix), configure transport once:

import FrontendModelBase from "velocious/build/src/frontend-models/base.js"

FrontendModelBase.configureTransport({
  requestContext: () => ({projectId: currentProject.id, routingEpoch: currentProject.routingEpoch}),
  url: "http://127.0.0.1:4501/frontend-models",
  timeZone: () => Intl.DateTimeFormat().resolvedOptions().timeZone
})

Available transport options:

  • url (can also be a relative path like "/frontend-models" on web)
  • requestContext (a scalar plain object or synchronous function returning one) captures immutable remote tenant-routing params independently for each CRUD/custom command and event subscription. See docs/remote-request-context.md.
  • timeZone (an IANA timezone string or a function returning one). Browser clients auto-detect this when it is not configured. Frontend-model datetime strings without an explicit timezone are interpreted in this request timezone and stored/queried as UTC instants.
  • timeout (milliseconds or a function returning milliseconds) bounds each request, while signal (an AbortSignal or a function returning one) supports caller cancellation. See docs/frontend-models.md for timeout and cancellation behavior.

Use await FrontendModelBase.waitForIdle() when a test harness or app lifecycle needs to wait for queued, scheduled, and active frontend-model transport requests to finish before resetting state.

Frontend-model HTTP requests always use credentials: "include" so shared custom commands can set session cookies without app-level transport overrides.

Unexpected frontend-model endpoint failures return their original message and full stack trace by default in every environment, including production. Responses use errorType: "internal_error", a server-generated correlationId shared with the matching framework-error report, and the established debugErrorClass, debugErrorMessage, and debugBacktrace fields. Expected application failures can use VelociousError.safe(message, {errorType, details, code}); generated frontend-model callers preserve the server's safe error fields without adding irrelevant debug fields. See docs/frontend-models.md. Invalid client query descriptors, such as unknown select, where, search, joins, preload, group, sort, pluck, or Ransack attributes, return the specific frontend-model query error message with velocious.code: "frontend-model-query-error" and are not emitted as framework errors. Shared index payloads reject unknown top-level keys per request, including wrapper nesting such as payload.query.where; use payload.where directly. Legacy shared index callers may send payload.authenticationToken, which is extracted into request context before validation; it is not a query option, requestContext.authenticationToken wins when both are present, and unrelated unknown keys still fail. See frontend-model transport. Invalid frontend-model write attributes and attachment names, including attributes rejected by permittedParams(), return the specific safe error message with velocious.code: "frontend-model-attribute-error" and are not emitted as framework errors. To mask unexpected internal details, explicitly opt out for the application configuration:

const configuration = new Configuration({
  exposeInternalErrorsToClients: false
})

With this opt-out, built-in commands, custom commands, and sync replay failures return errorMessage: "Request failed." and omit the debug message and stack fields in every environment. secureFrontendModelErrors: true remains a deprecated compatibility alias when exposeInternalErrorsToClients is omitted; an explicit exposeInternalErrorsToClients value always wins.

Backends can append client-safe metadata to frontend-model error responses with configuration.addClientErrorPayloadReporter(...). Reporters receive the caught error, the current request, a safe requestDetails snapshot, and a small context object, and should only return fields that are safe for clients to see. Frontend-model endpoint failures include context.frontendModelEndpoint, action, commandType, model, requestId, and expectedError. When exposure is disabled, Velocious strips the established debug fields even if a reporter supplies them. This is useful for attaching an error-reporting URL while keeping an opted-out error message generic:

configuration.addClientErrorPayloadReporter(async ({error, requestDetails, context}) => {
  const report = await reportErrorToService({error, requestDetails, context})

  return {bugReportUrl: report.url}
})

requestDetails includes httpMethod, path, and a parsed body snapshot when available. The body snapshot redacts common secret keys, truncates large strings and arrays, summarizes uploaded files and buffers without bytes, and compacts oversized frontend-model batches while preserving requestId, model, commandType / customPath, and payload shape.

For sqlite web databases, Velocious automatically picks the best browser persistence backend it can use: OPFS when a smoke test succeeds, then IndexedDB. Existing persisted bytes in a worse backend are migrated into the selected backend when possible. If neither OPFS nor IndexedDB is usable, Velocious keeps the legacy localStorage-style backend as a compatibility fallback. See docs/sqlite-web-persistence.md for the backend selection details.

Velocious defaults to https://sql.js.org/dist/<file> for sql.js wasm loading. You can override wasm resolution per database config with locateFile:

import SqliteDriver from "velocious/build/src/database/drivers/sqlite/index.web.js"

export default new Configuration({
  database: {
    test: {
      default: {
        driver: SqliteDriver,
        type: "sqlite",
        name: "app-db",
        locateFile: (file) => `/assets/sqljs/${file}`
      }
    }
  }
})

If you want to serve sql.js assets directly from your running Velocious backend, install the built-in sql.js asset route plugin and point locateFile to it:

import installSqlJsWasmRoute, {sqlJsLocateFileFromBackend} from "velocious/build/src/plugins/sqljs-wasm-route.js"
import SqliteDriver from "velocious/build/src/database/drivers/sqlite/index.web.js"

const configuration = new Configuration({
  // ...
  database: {
    development: {
      default: {
        driver: SqliteDriver,
        type: "sqlite",
        name: "app-db",
        locateFile: sqlJsLocateFileFromBackend({
          backendBaseUrl: "http://127.0.0.1:4501",
          routePrefix: "/velocious/sqljs"
        })
      }
    }
  }
})

installSqlJsWasmRoute({
  configuration,
  routePrefix: "/velocious/sqljs"
})

Frontend-model command transport preserves Date and undefined by encoding them as marker objects in JSON and decoding them on the other side:

  • Date -> {__velocious_type: "date", value: "<ISO string>"}
  • undefined -> {__velocious_type: "undefined"}
  • bigint -> {__velocious_type: "bigint", value: "<decimal string>"}
  • NaN / Infinity / -Infinity -> {__velocious_type: "number", value: "NaN" | "Infinity" | "-Infinity"}

Frontend-model commands raise an Error when the backend responds with {status: "error"} (using errorMessage when present), so unauthorized or missing-record update/find/destroy responses fail fast in frontend code.

Route resolver hooks

Libraries can hook unresolved routes and hijack them before Velocious falls back to the built-in 404 controller.

export default new Configuration({
  // ...
  routeResolverHooks: [
    ({currentPath}) => {
      if (currentPath !== "/special-route") return null

      return {controller: "hijacked", action: "index"}
    }
  ]
})

Hook return value:

  • null to skip
  • {controller, action} to resolve the request
  • Optional controllerClass to resolve without importing a controller path
  • Optional params object to merge into request params
  • Optional controllerPath string to resolve a controller file outside the app route directory
  • Optional viewPath string override for view rendering lookups

Plugin routes helper

For plugin-style integrations, you can register routes with a simple DSL:

configuration.routes((routes) => {
  routes.get("/velocious/sqljs/:sqlJsAssetFileName", {
    to: [SqlJsController, "downloadSqlJs"]
  })
})

Supported route helpers:

  • routes.get(path, {to: [ControllerClass, "action"], params?})
  • routes.post(path, {to: [ControllerClass, "action"], params?})

Rampway deployment control plane

Applications can install rampway@^0.4.0 and mount its package-owned Velocious control plane through the existing routes DSL. Keep bearer tokens in backend secrets and provide explicit allowlisted config paths and release branches:

npm install rampway@^0.4.0 velocious@^1.0.577

Rampway 0.4.0 declares velocious ^1.0.574 as its peer range, but applications mounting this API must use Velocious 1.0.577 or newer. Versions 1.0.574 through 1.0.576 attempted an app-local controller import before the package-supplied controllerClass, allowing a same-named app controller to shadow Rampway's authenticated controller.

import RampwayDeploymentApi from "rampway/velocious"
import deploymentSecrets from "./secrets/deployments.js"

routes.draw((route) => {
  route.mount(RampwayDeploymentApi, {
    accessTokens: deploymentSecrets.rampwayAccessTokens,
    at: "/rampway/deployments",
    projects: {
      "my-app": {
        stages: {
          production: {
            configPath: "/srv/my-app/control/rampway.config.mjs",
            releaseBranch: "main"
          }
        }
      }
    },
    workerBootstrapPath: "/srv/my-app/control/rampway-velocious-worker.mjs"
  })
})

Rampway owns authentication, deployment execution, idempotency, durable runs, audits, reconciliation, and the detached worker. Velocious supplies its normal route, request, error-event, and database abstractions. See docs/rampway-integration.md for bootstrap, persistence, security, and rollback requirements.

import Record from "velocious/build/src/database/record/index.js"

class Task extends Record {
}

Task.belongsTo("account")
Task.translates("description", "subTitle", "title")
Task.validates("name", {presence: true, uniqueness: true})

export default Task

presence: true rejects only null, undefined, and trimmed-empty strings; non-string values are present as-is, so a Date, a number (including 0), or a boolean (including false) never fails presence validation. See docs/validations.md for the full validator contract.

Generated belongs-to setters synchronize the loaded relationship and foreign key before save, so task.setProject(project) updates task.projectId(), task.changes(), callbacks, and scoped features such as actsAsList. Custom relationship primary keys are also used after autosaving an assigned new or dirty related record. Generated backend write attributes accept belongs-to relationship names for create and update payloads. See docs/relationships.md.

Translated models also get a currentTranslation hasOne relationship scoped to the first available row in the current locale fallback order. See docs/translations.md for preloading and frontend-model sorting behavior.

Async class APIs initialize record metadata on first use when a model has not already been initialized eagerly. See docs/model-initialization.md for the eager and lazy initialization behavior, including atomic shared bootstrap and complete recovery after an eager initialization failure or database-connection closure without overlapping stale and current bootstrap side effects.

Lifecycle callbacks

Records support implicit same-named lifecycle methods plus explicit function or string method-name registrations. Registrations run in order, so independent responsibilities can use multiple small named callbacks.

class Task extends Record {
  beforeSave() {
    this.setName(this.name().trim())
  }

  async validateSomething() {
    await doSomethingElse()
  }
}

Task.beforeValidation(async (task) => {
  await doSomething(task)
})

Task.beforeValidation("validateSomething")

Function registrations receive the record argument; arrow-function this is lexical and is not rebound to the record. Keep implicit hooks cohesive, use multiple explicit registrations when concerns or ordering should be visible, and put irreversible effects behind afterCommit rather than directly in afterSave. See [Record lifecycle