AbsoluteJS

Elysia Scoped State

An Elysia plugin for per-user session state management. Store and retrieve data tied to individual users across requests with automatic session handling.

View on GitHub

#Why Scoped State?

Per-User Isolation

Each user gets their own state slice. Button clicks and interactions only affect that user's data.

Automatic Sessions

A secure session cookie is automatically created on first visit. No manual session management required.

HTMX Perfect

Ideal for HTMX apps where server endpoints need to maintain user-specific state across partial page updates.

Type Safe

Full TypeScript support with typed state access through the scopedStore context property.

#Installation

BASH
bun add @absolutejs/scoped-state

#Getting Started

Initialize the plugin with your state schema. Each key defines a piece of state with an initial value:

TS
import Elysia from 'elysia';
import { scopedState } from '@absolutejs/scoped-state';

const app = new Elysia()
  .use(
    scopedState({
      count: { value: 0 },
      username: { value: '', preserve: true }
    })
  )
  .listen(3000);

#Accessing State

Access your scoped state through the scopedStore context property. Each user sees and modifies only their own state:

TS
// Access and modify scoped state in route handlers
new Elysia()
  .use(scopedState({ count: { value: 0 } }))
  .get('/api/count', ({ scopedStore }) => {
    // Each user sees their own count
    return scopedStore.count;
  })
  .post('/api/increment', ({ scopedStore }) => {
    // Increment only affects this user's state
    return ++scopedStore.count;
  })
  .post('/api/decrement', ({ scopedStore }) => {
    return --scopedStore.count;
  });

#Preserve Option

Mark state as preserve: true to keep it across page refreshes and navigation. Without this, state resets when users refresh the page or navigate away. Useful for user preferences and session data that should persist:

TS
// The preserve option keeps state across page refreshes and navigation
scopedState({
  // This resets when the user refreshes or navigates away
  count: { value: 0 },

  // This persists across page refreshes and switches
  theme: { value: 'light', preserve: true },
  username: { value: '', preserve: true }
})

#Resetting State

Use resetScopedStore() to programmatically reset the user's state to initial values. This respects preserve flags by default. Pass true to ignore preserve flags and reset everything:

TS
// Programmatically reset the user's scoped store
new Elysia()
  .use(scopedState({
    count: { value: 0 },
    theme: { value: 'light', preserve: true }
  }))
  .post('/api/reset', ({ resetScopedStore }) => {
    // Resets count to 0, but keeps theme (respects preserve)
    resetScopedStore();
    return 'State reset!';
  })
  .post('/api/full-reset', ({ resetScopedStore }) => {
    // Ignores preserve flags, resets everything including theme
    resetScopedStore(true);
    return 'Full state reset!';
  });

#HTMX Integration

Scoped state shines with HTMX. Each user's button clicks and interactions only affect their own count, cart, or other state:

TS
// Complete HTMX counter example with scoped state
import Elysia from 'elysia';
import { scopedState } from '@absolutejs/scoped-state';
import { handleHTMXPageRequest } from '@absolutejs/absolute';

new Elysia()
  .use(
    scopedState({
      count: { value: 0 }
    })
  )
  .get('/app', () => handleHTMXPageRequest('./build/pages/counter.html'))
  .get('/api/count', ({ scopedStore }) => {
    return `<span id="count">${scopedStore.count}</span>`;
  })
  .post('/api/increment', ({ scopedStore }) => {
    return `<span id="count">${++scopedStore.count}</span>`;
  })
  .post('/api/decrement', ({ scopedStore }) => {
    return `<span id="count">${--scopedStore.count}</span>`;
  })
  .listen(3000);

The HTML uses HTMX attributes to call these endpoints:

HTML
<!-- counter.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Counter App</title>
  <script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
  <h1>Personal Counter</h1>

  <div>
    Count: <span id="count" hx-get="/api/count" hx-trigger="load">0</span>
  </div>

  <button hx-post="/api/increment" hx-target="#count" hx-swap="outerHTML">
    +1
  </button>

  <button hx-post="/api/decrement" hx-target="#count" hx-swap="outerHTML">
    -1
  </button>
</body>
</html>
1
User AClicks increment 3 times
Sees count of 3
2
User BVisits the same page
Sees count of 0 (their own state)
3
User BClicks increment
Sees count of 1 (independent from User A)

#How It Works

The plugin uses a secure session cookie to identify users and maintain their state server-side:

1
First request
On first request, a secure user_session_id cookie is created
2
Subsequent requests
Each subsequent request uses this cookie to retrieve the user's state
3
Server-side storage
State is stored server-side, keyed by session ID

User A

Visits /api/count → sees 0
Calls /api/increment → sees 1
Calls /api/increment → sees 2

User B

Visits /api/count → sees 0 (own state)
Calls /api/increment → sees 1
Independent from User A

Automatic Session IDA user_session_id cookie is created on first request
Server-Side StorageState is stored in memory on the server, keyed by session ID
IsolationEach session ID maps to a completely separate state object

Current package surface

What ships today

@absolutejs/scoped-statev0.1.4 · betaFrontend & UXnpmSource
3entry points2symbols

Import surface · click to copy

1 symbol
scopedStatevaluePermalinkSource
TS
const scopedState: <Setup extends Record<string, ScopedStateConfig<unknown>>>(setup: Setup) => Elysia<"", {
    decorator: {};
    store: {
        scoped: ScopedRecord<{ [K in keyof ValueOnly<Setup>]: ValueOnly<Setup>[K]; }>;
    };
    derive: {
        readonly scopedStore: { [K in keyof ValueOnly<Setup>]: ValueOnly<Setup>[K]; };
        readonly resetScopedStore: (ignorePreserve?: boolean) => void;
    };
    resolve: {};
}, {
    typebox: {};
    error: {};
}, {
    schema: import("elysia").MergeSchema<import("elysia").MergeSchema<{}, {}, "">, {}, "">;
    standaloneSchema: {};
    macro: {};
    macroFn: {};
    parser: {};
}, {}, {
    derive: {};
    resolve: {};
    schema: {};
    standaloneSchema: {};
}, {
    derive: {};
    resolve: {};
    schema: {};
    standaloneSchema: {};
}>;
Exported from @absolutejs/scoped-state

Outcomes

What you can build

Installation

---

Key Features

Per-user scoped store

API

Function / Property — Description

Hardening checklist

Production guidance

Make every external boundary explicitPin the deployed @absolutejs/scoped-state version, replace example or memory-backed dependencies with durable implementations, bound external calls, protect credentials, and emit enough evidence to retry or recover safely.

Follow in order

Troubleshooting path

1
Trace from the first failed boundary
Reproduce the smallest canonical @absolutejs/scoped-state example, confirm the supported entry point and version in the API explorer, then inspect the first boundary that did not produce its documented result.