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

@caspian-vega/transfer-state

v0.2.1

Published

SSR→client transfer state for Astro. Collects values in an AsyncLocalStorage scope during rendering and replays them on the client, so work done on the server is not repeated in the browser.

Readme

@caspian-vega/transfer-state

SSR to client transfer state for Astro. Values produced while rendering are collected per request, emitted with the document, and read back on the client instead of being recomputed.

Part of caspian-vega-astro-libs.

Install

npm install @caspian-vega/transfer-state
# or
pnpm add @caspian-vega/transfer-state

Astro is an optional peer dependency, needed only for the .astro component.

Setup

Two server-side steps.

  1. Open a scope per request in src/middleware.ts:
import { sequence } from 'astro/middleware';

import { transferStateMiddleware } from '@caspian-vega/transfer-state/server';

export const onRequest = sequence(transferStateMiddleware, ...yourOtherMiddleware);
  1. Emit the payload as the last element of the layout, after </body> and inside </html>:
---
import TransferStateClient from '@caspian-vega/transfer-state/TransferStateClient.astro';
---

<html>
    <head></head>
    <body>
        <slot />
    </body><TransferStateClient />
</html>

The component drains the bucket when it renders. Anything collected by a component rendered after it does not reach the client.

Usage

import { transferAction } from '@caspian-vega/transfer-state';

export const loadPost = transferAction('post:byId', (id: string) => api.getPost(id));

// Server: calls api.getPost and stores the result.
// Client: returns the stored result, api.getPost is not called.
const post = await loadPost('42');

Manual store and read:

import { addTransferState, getTransferStateValue, makeStateKey } from '@caspian-vega/transfer-state';

const key = makeStateKey({ resource: 'posts', page: 1 });

addTransferState(key, posts);
const restored = getTransferStateValue(key);

Resolution order

flowchart TD
  Call[transferAction call] --> Scope{Server request scope open?}
  Scope -->|yes| Run[Run callback]
  Run --> Store[Write bucket under id + args hash]
  Store --> Ret1[Return value]
  Scope -->|no| Snap{Key present in snapshot?}
  Snap -->|yes| Read[Read and consume]
  Read --> Ret2[Return stored value]
  Snap -->|no| Run2[Run callback]
  Run2 --> Ret3[Return value]

Entry points

node:async_hooks cannot be bundled for the client, so the package is split.

| Import | Contains | Client safe | | -------------------------------------------------------- | ----------------------------------------------------- | -------------------- | | @caspian-vega/transfer-state | transferAction, read and write helpers, key hashing | yes | | @caspian-vega/transfer-state/server | scope, middleware, drain | no | | @caspian-vega/transfer-state/TransferStateClient.astro | payload component | server rendered only |

Import /server in middleware.ts and .astro frontmatter only. Importing it in a client build fails at import time with a message naming the fix.

API

transferAction(id, callback)

Wraps a callback so its server result is reused on the client.

  • id (string, required): stable name, combined with the call arguments to form the key.
  • callback ((...args) => TResult, required): sync or async. Its result must be JSON-serializable.
  • Returns: TransferAction<TArgs, TResult>, an async function with the callback's parameters.
const loadFeed = transferAction('feed:list', (topic: string) => api.getFeed(topic));
const feed = await loadFeed('astro');

makeStateKey(key)

Builds a stable short key.

  • key (string | object, required): JSON-serializable description. Property order is part of the result, so build it identically on both sides.
  • Returns: string

getTransferState()

  • Returns: TransferStateBucket | undefined. The collecting bucket on the server, the snapshot on the client, undefined when neither exists.

getTransferStateValue(key, options?)

Reads one value.

  • key (string, required): key returned by makeStateKey.
  • options.consumeOnce (boolean, default true): remove the entry after reading.
  • Returns: unknown, or undefined when the key is absent.
const value = getTransferStateValue(key, { consumeOnce: false });

addTransferState(key, value)

Stores a value for the current request. No-op on the client, so callers do not branch on the environment.

  • key (string, required)
  • value (unknown, required): must be JSON-serializable.
  • Returns: void

isCollecting()

  • Returns: boolean, true inside a server request scope.

createTransferState()

  • Returns: TransferStateAccess, an object carrying isCollecting, makeStateKey, getTransferState, getTransferStateValue, addTransferState and transferAction. Register it as a value in any container.
export class AccessTransferState {
    private readonly access = createTransferState();
}

TRANSFER_STATE_GLOBAL

string, name of the globalThis property holding the client snapshot.

transferStateMiddleware

Astro middleware opening one scope per request. Register it before any middleware that renders.

  • Signature: (context, next) => Promise<Response>

runWithTransferState(fn, bucket?)

Runs fn inside a fresh scope. Everything awaited within it reaches the same bucket.

  • fn (() => T, required)
  • bucket (TransferStateBucket, default {}): pre-seeded bucket.
  • Returns: T
const html = runWithTransferState(() => render(page));

getRequestTransferState()

  • Returns: TransferStateBucket | undefined, the bucket of the in-flight request.

isTransferStateActive()

  • Returns: boolean, true inside a scope opened by runWithTransferState.

drainTransferState()

Reads the collected state and clears the bucket, so a second render pass cannot emit it twice.

  • Returns: TransferStateBucket

Types

TransferAction

(...args: TArgs) => Promise<Awaited<TResult>>

TransferStateBucket

Record<string, unknown> holding one request's collected values.

GetOptions

  • consumeOnce (boolean, optional): remove the entry after reading. Default true.

TransferStateAccess

ReturnType<typeof createTransferState>

Constraints

  • Values travel through a <script> tag, so they must be JSON-serializable. No Date, Map, undefined inside objects, or class instances.
  • Reads consume by default. Pass { consumeOnce: false } for a value read more than once.
  • The payload is plain text in the HTML. It is per request, not shared between users, but fully visible to the recipient.
  • View transitions are supported. The snapshot is read on every access, so a <ClientRouter /> swap is picked up.
  • Requires a Node-compatible SSR runtime providing node:async_hooks.

Synergies

See SYNERGIES.md.

License

MIT