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

@scalar/workspace-store

v0.51.0

Published

Store interface for openapi documents

Readme

@scalar/workspace-store

A powerful data store for managing OpenAPI documents. This package provides a flexible solution for handling multiple OpenAPI documents in a workspace environment

Server-Side workspace store

Server side data store which enables document chunking to reduce initial loading time specially when working with large openapi documents

Usage

Create a new store in SSR mode

// Create the store
const store = await createServerWorkspaceStore({
  baseUrl: 'example.com',
  mode: 'ssr',
  meta: { 'x-scalar-active-document': 'document-name' },
  documents: [
    {
      name: 'document-name',
      meta: {},
      document: {
        openapi: '3.1.1',
        info: {
          title: 'Hello World',
          version: '1.0.0',
        },
        components: {
          schemas: {
            Person: {
              type: 'object',
              properties: {
                name: { type: 'string' },
              },
            },
            User: {
              $ref: '#/components/schemas/Person',
            },
          },
        },
      },
    },
  ],
})

// Add a new document to the store
await store.addDocument(
  {
    openapi: '3.1.1',
    info: {
      title: 'Hello World',
      version: '1.0.0',
    },
    components: {
      schemas: {
        Person: {
          type: 'object',
          properties: {
            name: { type: 'string' },
          },
        },
        User: {
          $ref: '#/components/schemas/Person',
        },
      },
    },
  },
  {
    'name': 'document-2',
    'x-scalar-selected-server': 'server1',
  },
)

// Get the workspace
// Workspace is going to keep all the sparse documents
const workspace = store.getWorkspace()

// Get chucks using json pointers
const chunk = store.get('#/document-name/components/schemas/Person')

Create a new store in static mode

// Create the store
const store = await createServerWorkspaceStore({
  directory: 'assets',
  mode: 'static',
  meta: { 'x-scalar-active-document': 'document-name' },
  documents: [
    {
      name: 'document-name',
      meta: {},
      document: {
        openapi: '3.1.1',
        info: {
          title: 'Hello World',
          version: '1.0.0',
        },
        components: {
          schemas: {
            Person: {
              type: 'object',
              properties: {
                name: { type: 'string' },
              },
            },
            User: {
              $ref: '#/components/schemas/Person',
            },
          },
        },
      },
    },
  ],
})

// Add a new document to the store
await store.addDocument(
  {
    openapi: '3.1.1',
    info: {
      title: 'Hello World',
      version: '1.0.0',
    },
    components: {
      schemas: {
        Person: {
          type: 'object',
          properties: {
            name: { type: 'string' },
          },
        },
        User: {
          $ref: '#/components/schemas/Person',
        },
      },
    },
  },
  {
    'name': 'document-2',
    'x-scalar-selected-server': 'server1',
  },
)

// Generate the workspace file system
// This will write in the filesystem the workspace and all the chucks
// which can be resolved by the consumer
const workspace = await store.generateWorkspaceChunks()

Load documents from external sources

// Initialize the store with documents from external sources
const store = await createServerWorkspaceStore({
  mode: 'static',
  documents: [
    {
      name: 'remoteFile',
      url: 'http://localhost/document.json',
    },
    {
      name: 'fsFile',
      path: './document.json',
    },
  ],
})

// Output: { openapi: 'x.x.x', ... }
console.log(store.getWorkspace().documents.remoteFile)

// Output: { openapi: 'x.x.x', ... }
console.log(store.getWorkspace().documents.fsFile)

Client-Side Workspace Store

A reactive workspace store for managing OpenAPI documents with automatic reference resolution and chunked loading capabilities. Works seamlessly with server-side stores to handle large documents efficiently.

Usage

The client-side store starts empty and exposes addDocument for loading documents in. Workspace metadata, plugins, fetch overrides, and a file loader plugin can be supplied at construction time.

// Initialize a new (empty) workspace store
const store = createWorkspaceStore({
  meta: {
    'x-scalar-active-document': 'default',
  },
})

// Add the default document
await store.addDocument({
  name: 'default',
  document: {
    openapi: '3.1.0',
    info: {
      title: 'OpenApi document',
      version: '1.0.0',
    },
  },
})

// Add another OpenAPI document to the workspace
await store.addDocument({
  name: 'document',
  document: {
    openapi: '3.1.0',
    info: {
      title: 'Another document',
      version: '1.0.0',
    },
  },
})

// Get the currently active document
store.workspace.activeDocument

// Retrieve a specific document by name
store.workspace.documents['document']

// Update global workspace settings
store.update('x-scalar-color-mode', true)

// Update settings for the active document
store.updateDocument('active', 'x-scalar-selected-server', 'production')

// Resolve and load document chunks including any $ref references
await store.resolve(['paths', '/users', 'get'])

Load documents from external sources

The store can also load documents from a URL or, when a file loader plugin is configured, from the local filesystem. Each call returns a boolean indicating whether the document was added successfully.

const store = createWorkspaceStore()

// Load a document into the store from a remote url
await store.addDocument({
  name: 'default',
  url: 'http://localhost/document.json',
})

// Output: { openapi: 'x.x.x', ... }
console.log(store.workspace.documents.default)

Document Persistence and Export

The workspace store keeps two snapshots per document at runtime: the original (the last saved baseline that the user committed to with saveDocument) and the active document (the reactive in-memory state, which may include unsaved edits). Most persistence methods are anchored on those two snapshots.

Deprecated: an additional intermediateDocuments map and the helper methods getIntermediateDocument / promoteIntermediateToOriginal still exist for backward compatibility but are no longer authoritative. New code should rely on getOriginalDocument and the active document instead. The intermediate map is kept in sync on save / revert / rebase so existing consumers keep working until the layer is removed.

Export Document

Export the specified document in JSON or YAML format. The export reads from the saved baseline (the same content revertDocumentChanges would restore), so it always reflects the user's last save rather than any unsaved edits.

// Export the specified document as JSON
const jsonString = store.exportDocument('documentName', 'json')

// Export the specified document as YAML
const yamlString = store.exportDocument('documentName', 'yaml')

// Or export the currently active document directly
const activeJson = store.exportActiveDocument('json')
Save Document Changes

saveDocument promotes the current in-memory document to the new saved baseline. It serialises the reactive workspace document back into a plain shape (with bundler-internal keys stripped), writes it into the original-document map, and clears the document's x-scalar-is-dirty flag.

// Save the specified document state
const ok = await store.saveDocument('documentName')

if (!ok) {
  console.warn('Document does not exist or could not be serialised')
}

saveDocument returns true on success and false when the document does not exist or cannot be serialised back into the original map.

Revert Document Changes

Revert the specified document to its most recent saved baseline, discarding all unsaved in-memory changes.

// Revert the specified document to its last saved state
await store.revertDocumentChanges('documentName')

The revertDocumentChanges method restores the active document from the original-document map, which is whatever saveDocument last wrote (or the document as it was first loaded into the workspace if it has never been saved).

Warning: This operation will discard all unsaved changes to the specified document.

Complete Example
const store = createWorkspaceStore()
await store.addDocument({
  name: 'api',
  document: {
    openapi: '3.0.0',
    info: { title: 'My API', version: '1.0.0' },
    paths: {},
  },
})

// Make some changes to the document
store.workspace.documents['api'].info.title = 'Updated API Title'

// Restore the saved baseline since the changes were never saved
await store.revertDocumentChanges('api')

Workspace State Persistence

The workspace store provides methods to persist and restore the complete workspace state, including all documents, configurations, and metadata. This is useful for saving work between sessions or sharing workspace configurations.

const client = createWorkspaceStore()
// Get the current workspace state
const currentWorkspaceState = client.exportWorkspace()

// Persist on some kind of storage

// Reload the workspace state
client.loadWorkspace(currentWorkspaceState)

Replace the Entire Document

When you have a new or updated OpenAPI document and want to overwrite the existing one—regardless of which parts have changed—you can use the replaceDocument method. This method efficiently and atomically updates the entire document in place, ensuring that only the necessary changes are applied for optimal performance.

const client = createWorkspaceStore()
await client.addDocument({
  name: 'document-name',
  document: {
    openapi: '3.1.0',
    info: {
      title: 'Document Title',
      version: '1.0.0',
    },
    paths: {},
    components: {
      schemas: {},
    },
    servers: [],
  },
})

// Update the document with the new changes
await client.replaceDocument('document-name', {
  openapi: '3.1.0',
  info: {
    title: 'Updated Document',
    version: '1.0.0',
  },
  paths: {},
  components: {
    schemas: {},
  },
  servers: [],
})

Create workspace from specification

Create the workspace from a specification object

await store.importWorkspaceFromSpecification({
  'workspace': 'draft',
  'info': { title: 'My Workspace' },
  'documents': {
    api: { $ref: '/examples/api.yaml' },
    petstore: { $ref: '/examples/petstore.yaml' },
  },
  'overrides': {
    api: {
      servers: [
        {
          url: 'http://localhost:9090',
        },
      ],
    },
  },
  'x-scalar-color-mode': true,
})

Override specific fields from the document and it's metadata

This feature is helpful when you want to override specific fields in a document without altering the original source. Overrides allow you to customize certain values in-memory, ensuring the original document remains unchanged.

const store = createWorkspaceStore()
await store.addDocument({
  name: 'default',
  document: {
    openapi: '3.1.0',
    info: {
      title: 'Document Title',
      version: '1.0.0',
    },
    paths: {},
    components: {
      schemas: {},
    },
    servers: [],
  },
  // Override the servers field
  overrides: {
    servers: [
      {
        url: 'http://localhost:8080',
        description: 'Default dev server',
      },
    ],
  },
})

When you override specific fields, those changes are applied only in-memory and will never be written back to the original document. The original source remains unchanged, and any modifications made through overrides are isolated to the current session.

Rebase document origin with the updated remote origin

rebaseDocument reconciles a workspace document with a new upstream origin. It performs a two-way merge between:

  • the incoming changesdiff(originalDocument, newOrigin)
  • the local changesdiff(originalDocument, activeDocument)

The call returns a discriminated result. On ok: false the type field describes why the rebase did not run (CORRUPTED_STATE, FETCH_FAILED, or NO_CHANGES_DETECTED). On ok: true it exposes the auto-mergeable changes, the conflicts that need user input, and an applyChanges callback that writes the merged result back into the workspace.

// Fetch the latest origin and start a rebase
const result = await store.rebaseDocument({
  name: 'api',
  // Any `WorkspaceDocumentInput` is accepted - inline document, url, or path
  url: 'https://example.com/api/openapi.json',
})

if (!result.ok) {
  console.warn(`Rebase did not run: ${result.type}`)
  return
}

if (result.conflicts.length === 0) {
  // No conflicts - just apply with an empty resolution set
  await result.applyChanges({ resolvedConflicts: [] })
  return
}

// Surface the conflicts to the user. Each conflict is a tuple of
// [incomingDiffs, localDiffs] - resolve by picking either side, or by
// providing a fully resolved document.
const resolvedConflicts = result.conflicts.flatMap(([incoming]) => incoming)
await result.applyChanges({ resolvedConflicts })

// Or, pass a complete document to use as-is (overrides the merge result):
await result.applyChanges({ resolvedDocument: newDocument })

After applyChanges returns, the merged document becomes both the new active document and the new saved baseline, so a subsequent revertDocumentChanges rolls back to the post-rebase state rather than the pre-rebase original.