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

@velajs/studio

v1.34.0

Published

Vela Studio admin module: reserved /_vela/admin surface, @AdminRpc ops, time-travel port

Readme

@velajs/studio

The edge-safe Vela Studio admin module: mounts the reserved /_vela/admin surface, hosts @AdminRpc operations, and exposes the time-travel port. Panels join through one contract, StudioModule.forRoot({ plugins: [...] }), each from its own subpath so optional peers stay optional:

import { StudioModule } from '@velajs/studio';
import { crudPanel } from '@velajs/studio/crud';
import { queuesPanel } from '@velajs/studio/queue';
import { livePanel } from '@velajs/studio/live';
import { logsPanel } from '@velajs/studio/logging';

StudioModule.forRoot({
  editable: { ops: true },
  plugins: [
    crudPanel({ managedModels: { include: ['todo'] } }),
    queuesPanel(),
    livePanel({ rooms: ['default'] }),
    logsPanel({ timings: true }),
  ],
});

| Panel | Subpath | Lights | | --- | --- | --- | | crudPanel({ managedModels?, runAsIdentity? }) | ./crud | data, transfer | | timeTravelPanel({ store?, changeSource?, perPage?, imports? }) | ./timetravel | timeTravel (portable snapshots) | | cloudflareTimeTravelPanel({ binding, shardKey? }) | ./cloudflare | timeTravel (Durable Object PITR) | | authPanel() | ./auth | auth, authOrganizations | | flagsPanel() | ./flags | flags | | queuesPanel() | ./queue | queue | | schedulePanel() | ./schedule | schedule | | livePanel({ rooms?, source? }) | ./live | live, presence | | logsPanel({ timings? }) | ./logging | logs capture |

Each plugin's providers register in StudioModule's own scope, so they reach its signers and buffers without re-importing the configured module. plugins is structural: forRootAsync({ plugins, useFactory }) takes it next to the factory. Options that depend on the runtime environment take a function of the application's ENV (livePanel({ source: (env) => … }), timeTravelPanel({ store: (env) => … })), called once per application; the Cloudflare time-travel panel names its Durable Object binding and reads it from ENV when a call first needs it. defineStudioPlugin({ name, providers, imports }) builds a custom panel; each name appears once.

Studio stays closed until it has a master token: StudioModule.forRoot({ token }), or a VELA_STUDIO_TOKEN variable or secret in the application's ENV. The VELA_STUDIO_DATA_EDITABLE, VELA_STUDIO_SCHEMA_EDITABLE, VELA_STUDIO_OPS_EDITABLE, VELA_STUDIO_TIMETRAVEL_EDITABLE and VELA_STUDIO_TRANSFER_EDITABLE flags open write categories the same way; module options win over environment values, and non-string values are ignored. On Workers @velajs/cloudflare seeds ENV, so a Wrangler secret takes effect without extra wiring; elsewhere pass VelaFactory.create(App, { env }). Studio reads the ENV the runtime seeds, else the one a @Global() module exports to every module; an ENV a module registers for itself or its importers leaves Studio closed, as does a @Global() module listing ENV among its exports without providing it or importing a module that exports one. readStudioEnv(env) parses these values and resolveStudioConfig(envConfig, options) merges them under module options.

The protocol exposes the usable operation catalog through studio.capabilities. Only configured Studio handlers enable their features. Queue depth/DLQ/replay remain unavailable until their handlers are implemented. Live and presence inspection are enabled by livePanel({ rooms: ['default'] }), which reads each named room through LiveModule's LiveInspector: on Cloudflare the room's Durable Object, through the gateway binding the live driver delivers to; elsewhere the application's own engine. There is no global room list, so name each room; the sockets of a gateway without roomParam join its path, so name that path for them. livePanel({ source }) takes a custom source whose inspect() returns explicitly scoped subscription and room snapshots instead, or a function that builds it from the application's ENV. With neither, the features remain disabled. CRUD reads and single writes use the adapter's request scope. Bulk mutations require transaction support, exposed as supports.bulkWrites in model descriptors.

api.authorizeTryIt checks permissions and audits authorization; the local host then sends the API request over actual Worker HTTP. Studio does not dispatch that request through an in-process Hono instance. The host and browser validate the protocol at their boundaries, including all operation-specific RPC response fields.

The portable time-travel panel uses the model source a crudPanel() binds in the same Studio; timeTravelPanel({ imports: [SourceModule] }) reaches a model source another module exports. Each port is bound by one panel: timeTravelPanel() and cloudflareTimeTravelPanel() both bind TIME_TRAVEL_PORT, so forRoot fails when a Studio lists both, as it does for any token two plugins provide (application-wide enhancers such as APP_INTERCEPTOR excepted), for a plugin providing a token StudioModule provides itself, such as STUDIO_RESOLVED_CONFIG or AdminAuditLog, and for one providing a framework token Studio reads from the application (ENV, APP_LOGGER, ROOT_MODULE, Container, DiscoveryService or EntrypointRegistry). Studio and its panels read those tokens application-wide, as app.get() does, so what the modules a plugin imports export to their importers never answers for them when the application registers the token. Where it registers none, such as APP_LOGGER without LoggingModule or an ENV neither seeded nor exported by a @Global() module, the lookup falls back to another module's registration exactly as app.get() does; Studio's own VELA_STUDIO_* settings never read such an ENV. An explicit application registration, such as a seeded ENV, answers first; a @Global() module exporting ROOT_MODULE or DiscoveryService, one a plugin imports included, overrides that framework default for the whole application, app.get() and Studio alike. Bootstrap fails when a @Global() module exports another Container, since Studio would read that container's ENV and admin token. The admin routes read Studio's own tokens, such as STUDIO_RESOLVED_CONFIG, in StudioModule's scope, so another module's registration of one is never used. A custom panel's provider that injects a framework token directly resolves it in Studio's scope, where every plugin's imports are visible; moduleRef.get(TOKEN, { strict: false }) reads the application's. An async Studio factory with parameters supplies them through inject; one without parameters may omit it.

Diagnostic snapshots

Application inspection reads public module, route and entrypoint snapshots. It does not enumerate provider internals. Entrypoint metadata is bounded to depth 8, 64 items per collection, 256 visited values and 16 KiB of text (2 KiB per string). Bigints become strings such as 42n; cycles, accessors, functions, instances and truncated data use explicit markers. Getters and toJSON are never called. These are diagnostic summaries, not a data export format. Captured route descriptions are copied so inspection cannot mutate the stored descriptions.

Structured logs and timings

Add logsPanel({ timings: true }) from @velajs/studio/logging to Studio's plugins next to LoggingModule.forRoot(); without the logging module the application fails to boot, naming it. Capture reads only that application's APP_LOGGER records after normalization and redaction; it unsubscribes on application shutdown. It never patches global console methods. Timing is optional and defaults off. Rows measure handler/inner-interceptor completion, excluding guards, argument validation, streaming, and deferred work. They include module ownership and managed invocation IDs when available.

AdminLogBuffer copies input and output snapshots, bounds fields and messages, and accepts capacity zero to disable retention. Records are per application instance and ephemeral. The Modules and Entrypoints panels also display available ownership and effective class-token scopes without constructing providers.

See the debugging guide for setup and debugger recipes.

Named databases

The CRUD binding uses the same database selection as Vela CRUD. Named resources appear as encodeURIComponent(database)::encodeURIComponent(resourceKey); a resource key defaults to the model name. Use that complete identity in row, transfer, snapshot and crudPanel({ managedModels }) requests. Unique unnamed models retain their existing names. Model/table include or exclude rules still match all namespaces; use a qualified identity to select one. Missing named databases and colliding identities fail closed, without borrowing the default adapter.

Descriptors retain the physical table and include optional database metadata. Relation targets and generated foreign keys stay within the selected namespace. Snapshots can restore qualified resources, but the current CDC source contract accepts only physical table names. Named-database time-based replay is therefore unavailable and rejected before restore writes; use an explicit snapshot mark.

License

MIT