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

@softwarity/store

v1.19.2

Published

Angular store module by softwarity

Readme

@softwarity/store

CI Publish to NPM npm version

@softwarity/store lets you persist UI preferences (visible columns, sort order, page size, filters, sidebar state…) directly in the browser — no server-side persistence needed. No API endpoints, no database tables, no extra round-trips: everything stays client-side, where UI preferences belong.

A single decorator or function call is all it takes. Annotate a property — or wrap an object with localStored() / sessionStored() — and every mutation (including nested properties and array methods) is automatically persisted to browser storage and restored on reload. No manual getItem / setItem, no boilerplate serialization logic — @softwarity/store handles it all for you.

Live demo

Features

  • Decorator API@LocalStored / @SessionStored with deep mutation tracking and $prop() reactive signals
  • Stored APIlocalStored() / sessionStored() — same deep tracking and $prop() signals, without annotations
  • Deep mutation tracking — Native Proxy-based: nested properties, array methods, direct index assignment (arr[0] = 'x'), and new properties all trigger saves automatically
  • Versioning — Increment the version to discard stale data when the structure or defaults change
  • User-scoped storage — Prefix storage keys with a user ID
  • Cross-tab synconStorageChange() for localStorage changes from other tabs
  • SSR compatibleFakeStorage fallback when window is undefined
  • ~~Zoneless~~ — No dependency on zone.js

Installation

npm install @softwarity/store

Setup

Standalone (recommended)

import { provideStore } from '@softwarity/store';

export const appConfig = {
  providers: [provideStore()]
};

Module-based (legacy)

import { StoreModule } from '@softwarity/store';

@NgModule({ imports: [StoreModule] })
export class AppModule {}

Decorator API

Annotate class fields for automatic persistence. Nested property changes, array methods, and direct index assignments trigger saves automatically.

import { LocalStored, SessionStored } from '@softwarity/store';

@Component({ ... })
export class MyComponent {

  // localStorage — version 1
  // Increment to discard stale data when the structure or defaults change.
  @LocalStored(1)
  tableConfig = {
    columns: ['name', 'age'],
    sort: { active: 'name', direction: 'asc' },
    pageSize: 25
  };

  // sessionStorage (no version needed, data is short-lived)
  @SessionStored()
  filters = { search: '', category: 'all' };

  // Custom storage key (instead of auto-generated ClassName.property)
  @LocalStored(1, 'shared-config')
  sharedConfig = { theme: 'dark' };

  // Note: decorators auto-generate the key as ClassName.property.
  // Use the optional storageKey parameter to override it.
}

Stored API — localStored / sessionStored

Same deep mutation tracking as decorators, but without annotations. Returns a StoredSignal<T> with plain property access and $prop() reactive signals. Requires Angular injection context.

import { localStored, sessionStored } from '@softwarity/store';

@Component({ ... })
export class MyComponent {

  // localStorage + deep tracking + versioning
  // storageKey is required: functions don't have access to ClassName.property,
  // unlike decorators which auto-generate it.
  config = localStored(
    { columns: ['name', 'age'], sort: { active: 'name', direction: 'asc' } },
    { storageKey: 'table-config', version: 1 }
  );

  // sessionStorage + deep tracking
  wizard = sessionStored(
    { step: 1, draft: '' },
    { storageKey: 'wizard-state' }
  );
}

Deep mutation tracking

All stored objects are wrapped with native Proxy. All mutations trigger automatic saves:

// All of these trigger a save:
this.config.sort.direction = 'desc';         // nested property
this.config.columns.push('email');            // array push
this.config.columns.splice(0, 1);            // array splice
this.config.columns.reverse();               // array reverse
this.config.columns.sort();                  // array sort
this.config.columns[0] = 'email';            // direct index assignment
this.config.sort.newProp = 'value';          // new property on object

// This does NOT trigger a save:
this.config = { ... };                        // root reassignment (use property setters)

$prop() reactive signals

Both APIs expose $-prefixed signals at every level of depth, ideal for template bindings:

<!-- Top-level signals -->
<mat-header-row *matHeaderRowDef="config.$columns()"></mat-header-row>
<mat-paginator
  [pageSize]="config.$pageSize()"
  [pageIndex]="config.$pageIndex()"
  (page)="onPage($event)">
</mat-paginator>

<!-- Nested object signals -->
<mat-table matSort
  [matSortActive]="config.sort.$column()"
  [matSortDirection]="config.sort.$direction()"
  (matSortChange)="onSort($event)">
</mat-table>

Nested signals are available at any depth (config.a.b.$c()). Each signal returns the plain value of the property (via toPlain), and updates reactively whenever the property or any of its descendants is mutated.

Version management

Increment the version whenever the object structure or the default values change. Old stored data is discarded and replaced with the new defaults.

// Version 1: initial structure
@LocalStored(1)
config = { columns: ['name', 'age'], sort: 'asc' };

// Version 2: added filter — stale browser data is discarded
@LocalStored(2)
config = { columns: ['name', 'age'], sort: 'asc', filter: null };

// Version 3: default columns changed — users get the new defaults
@LocalStored(3)
config = { columns: ['name', 'age', 'email'], sort: 'asc', filter: null };

Only @LocalStored and localStored() support versioning. @SessionStored and sessionStored() do not need it (data is short-lived).

User-scoped storage

Browser storage (localStorage / sessionStorage) is shared across all users on the same browser profile. On shared workstations or kiosk machines, multiple people may log in to the same app one after another. Without scoping, user A's persisted preferences (columns, filters, sort order…) would leak to user B.

By providing a userId signal, every storage key is automatically prefixed with the current user's identifier (userId_storageKey). When the user changes (login / logout / switch), the library reloads each stored object from the new user's namespace — so preferences follow the user, not the machine.

Standalone

import { provideStore } from '@softwarity/store';
import { inject } from '@angular/core';

export const appConfig = {
  providers: [
    provideStore({ userId: () => inject(AuthService).userId })
  ]
};

Module-based (legacy)

import { StoreModule, USER_ID } from '@softwarity/store';
import { inject } from '@angular/core';

@NgModule({
  imports: [StoreModule],
  providers: [
    { provide: USER_ID, useFactory: () => inject(AuthService).userId }
  ]
})
export class AppModule {}

Storage key format

| API | Without userId | With userId | |---|---|---| | Decorators (auto) | ClassName.property | userId_ClassName.property | | Decorators (custom storageKey) | storageKey | userId_storageKey | | localStored / sessionStored | storageKey | userId_storageKey |

Cross-tab sync

Listen for localStorage changes from other browser tabs:

import { onStorageChange } from '@softwarity/store';

@Component({ ... })
export class MyComponent {
  private destroyRef = inject(DestroyRef);

  ngOnInit() {
    onStorageChange<{ theme: string }>('app-config', newValue => {
      console.log('Changed in another tab:', newValue);
    }, this.destroyRef); // auto-cleanup on destroy
  }
}

Note: Only works with localStorage (StorageEvent spec limitation).

Utilities

import { clearLocalStorage, clearSessionStorage } from '@softwarity/store';

// Clear all
clearLocalStorage();

// Clear only keys with a specific prefix (e.g. user-scoped data)
clearLocalStorage('alice_');
clearSessionStorage('alice_');

SSR support

The library provides a FakeStorage class used automatically when window is undefined (SSR context). You can also import it directly:

import { FakeStorage } from '@softwarity/store';

License

MIT