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

@statelyjs/files

v0.5.0

Published

File management plugin for Stately

Downloads

317

Readme

@statelyjs/files

File system integration plugin for Stately.

npm License

Provides file browsing, versioned file management, uploads, downloads, and relative path handling.

Overview

This package connects your application to a stately-files backend (Rust), giving you:

  • File Manager page - Browse files across cache, data, and upload directories
  • Versioned file support - View version history and download specific versions
  • Upload/download - Seamless file operations with progress feedback
  • RelativePath field - Custom form field for selecting files from your backend

Installation

pnpm add @statelyjs/files

Quick Start

1. Add Schema Plugin

import { stately } from '@statelyjs/stately/schema';
import { type FilesPlugin, filesPlugin } from '@statelyjs/files';

const schema = stately<MySchemas, readonly [FilesPlugin]>(openapiDoc, PARSED_SCHEMAS)
  .withPlugin(filesPlugin());

2. Add UI Plugin

import { statelyUi } from '@statelyjs/stately';
import { type FilesUiPlugin, filesUiPlugin } from '@statelyjs/files';

const runtime = statelyUi<MySchemas, readonly [FilesUiPlugin]>({
  schema,
  client,
  core: { api: { pathPrefix: '/entity' } },
  options: { api: { pathPrefix: '/api' } },
}).withPlugin(filesUiPlugin({
  api: { pathPrefix: '/files' },
}));

3. Wrap Your App

import { QueryClientProvider } from '@tanstack/react-query';
import { AppStatelyProvider, runtime } from './lib/stately';

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <AppStatelyProvider runtime={runtime}>
        <YourApp />
      </AppStatelyProvider>
    </QueryClientProvider>
  );
}

4. Configure Styles

Import the files styles and ensure your @source covers @statelyjs packages:

@import "tailwindcss";
@import "@statelyjs/stately/styles.css";
@import "@statelyjs/files/styles.css";

@source "./node_modules/@statelyjs";

Adding Routes

The plugin provides a FileManagerPage page component. Add it to your router:

import { FileManagerPage } from '@statelyjs/files/pages';

// Example with React Router
<Route path="/files" element={<FileManagerPage />} />

Hooks

All hooks use the files API through the plugin context:

useFileExplore

Browse files and directories:

import { useFileExplore } from '@statelyjs/files/hooks';

function FileBrowser() {
  const { data, isLoading } = useFileExplore({ path: 'data/configs' });

  return (
    <ul>
      {data?.entries.map(entry => (
        <li key={entry.name}>{entry.name}</li>
      ))}
    </ul>
  );
}

useFileVersions

Get version history for a file:

import { useFileVersions } from '@statelyjs/files/hooks';

const { data: versions } = useFileVersions({
  target: 'data',
  path: 'configs/settings.json',
});

useDownload

Download files (supports versioned downloads):

import { useDownload } from '@statelyjs/files/hooks';

function DownloadButton({ path }: { path: string }) {
  const { mutate: download, isPending } = useDownload();

  return (
    <button
      onClick={() => download({ target: 'data', path })}
      disabled={isPending}
    >
      {isPending ? 'Downloading...' : 'Download'}
    </button>
  );
}

useUpload

Upload files:

import { useUpload } from '@statelyjs/files/hooks';

const { mutate: upload, isPending } = useUpload({
  onSuccess: () => console.log('Upload complete'),
});

upload({ file, path: 'uploads/my-file.txt' });

useSaveFile

Save content to a file:

import { useSaveFile } from '@statelyjs/files/hooks';

const { mutate: save } = useSaveFile();

save({
  path: { dir: 'data', path: 'config.json' },
  content: JSON.stringify(config, null, 2),
});

Views

Reusable view components for building file interfaces:

FileExplorer

Directory browser with navigation:

import { FileExplorer } from '@statelyjs/files/views';

<FileExplorer
  path="data/configs"
  onNavigate={setCurrentPath}
  onSelect={handleFileSelect}
/>

FileSelector

File picker dialog:

import { FileSelector } from '@statelyjs/files/views';

<FileSelector
  value={selectedPath}
  onChange={setSelectedPath}
  filter={entry => entry.name.endsWith('.json')}
/>

FileDetails / VersionedFileDetails

Display file metadata and actions:

import { FileDetails, VersionedFileDetails } from '@statelyjs/files/views';

// For simple files
<FileDetails entry={fileEntry} currentPath={path} onClose={handleClose} />

// For versioned files (shows version history)
<VersionedFileDetails
  entry={fileEntry}
  currentPath={path}
  onClose={handleClose}
/>

FileManager

Full featured file manager, used in FileManagerPage:

import { FileManager } from '@statelyjs/files/views';

<FileManager />

Pages

FileManagerPage

Full-featured file management page with:

  • Directory navigation breadcrumbs
  • File/folder listing with icons
  • File details sidebar
  • Version history for versioned files
  • Download buttons
import { FileManagerPage } from '@statelyjs/files/pages';

<Route path="/files/*" element={<FileManagerPage />} />

Codegen Integration

The package includes a codegen plugin that detects RelativePath schemas from your OpenAPI spec. Add it to your stately.config.js:

// stately.config.js
import { filesCodegenPlugin } from '@statelyjs/files/codegen';

export default {
  plugins: [filesCodegenPlugin()],
};

Then run:

pnpm exec stately ./openapi.json -o ./src/generated -c ./stately.config.js

This transforms OpenAPI oneOf schemas matching the RelativePath pattern into FilesNodeType.RelativePath nodes, enabling the custom path selector field in forms.

Backend Requirements

This plugin expects a stately-files compatible backend with these endpoints:

| Endpoint | Method | Description | |----------|--------|-------------| | /list | GET | List files (query: path) | | /save | POST | Save file content | | /upload | POST | Upload file (multipart) | | /file/cache/{path} | GET | Download from cache | | /file/data/{path} | GET | Download from data | | /file/upload/{path} | GET | Download from uploads |

All download endpoints support an optional version query parameter for versioned files.

Plugin Architecture

For plugin authors, this package demonstrates the Stately plugin pattern:

Schema Plugin

Extends the node type system, expected types, additional data properties, and files utilities:

export const FILES_PLUGIN_NAME = 'files' as const;

export type FilesPlugin = DefinePlugin<
  typeof FILES_PLUGIN_NAME,
  FilesNodeMap,
  FilesTypes,
  FilesData,
  FilesUtils
>;

export function filesPlugin<S extends Schemas<any, any> = Schemas>(): PluginFactory<S> {
  return runtime => {
    return {
      ...runtime,
      data: { ...runtime.data },
      plugins: { ...runtime.plugins, [FILES_PLUGIN_NAME]: {} },
    };
  };
}

UI Plugin

Registers components, navigation, and API bindings:

export type FilesOptions = DefineOptions<{
  /** API configuration for Files endpoints */
  api?: { pathPrefix?: string };
  /** Navigation configuration for Files routes */
  navigation?: { routes?: UiNavigationOptions['routes'] };
}>;

export type FilesUiPlugin = DefineUiPlugin<
  typeof FILES_PLUGIN_NAME,
  FilesPaths,
  typeof FILES_OPERATIONS,
  FilesUiUtils,
  FilesOptions,
  typeof filesRoutes
>;

export function filesUiPlugin<
  Schema extends Schemas<any, any> = Schemas,
  Augments extends readonly AnyUiPlugin[] = [],
>(options?: FilesOptions): UiPluginFactory<Schema, Augments> {
  return runtime => {
    ...

    // Register components
    registry.components.set(
      baseRegistry.makeRegistryKey(FilesNodeType.RelativePath, 'edit'),
      props => <RelativePathEdit {...props} standalone />,
    );
    registry.components.set(
      baseRegistry.makeRegistryKey(FilesNodeType.RelativePath, 'view'),
      RelativePathView,
    );

    // Register transformers
    registry.transformers.set(
      baseRegistry.makeRegistryKey(CoreNodeType.Primitive, 'edit', 'transformer', 'string'),
      primitiveStringTransformer,
    );

    ...

    const pathPrefix = runtime.utils.mergePathPrefixOptions(basePathPrefix, corePathPrefix);
    const api = createOperations<FilesPaths, typeof FILES_OPERATIONS>(
      client,
      FILES_OPERATIONS,
      pathPrefix,
    );

     // Files only supports a top level route, only provides a single page.
    const routes = { ...filesRoutes, ...(options?.navigation?.routes || {}) };
    const plugin = { [FILES_PLUGIN_NAME]: { api, options, routes, utils: filesUiUtils } };
    return { ...runtime, plugins: { ...runtime.plugins, ...plugin } };
  };
}

The UI plugin:

  1. Creates an API client bound to your base URL
  2. Registers the RelativePathField component for the RelativePath node type
  3. Provides a prop transformer for core's PrimitiveNode::PrimitiveString fields
  4. Provides the files context to child components

Context Access

Access the files runtime from components:

import { useFilesStatelyUi } from '@statelyjs/files';

function MyComponent() {
  const runtime = useFilesStatelyUi();
  const filesApi = runtime.plugins.files?.api;
  // Use filesApi for custom operations
}

License

Apache-2.0