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 🙏

© 2024 – Pkg Stats / Ryan Hefner

signalstory

v18.0.0

Published

Signal-based state management for Angular that grows with your project. Explore a versatile toolbox with enriching plugins for developers at all levels.

Downloads

1,312

Readme

signalstory

Check out the sample 🚀

Visit the docs 📚

For a full feature overview, visit the Website ✨

For Release notes and Changelog, visit Gtihub Releases 🧙‍♂️

Empower your angular state management with signals

signalstory is a state management library based on angular signals. It offers a range of architectural options, from simple repository-based state management (signal-in-a-service) to orchestrating decoupled commands, handling side effects through encapsulated objects, and facilitating inter-store communication using an event-driven approach. The ultimate goal is to provide a great user experience for all developers, whether junior or senior, while incorporating all the features you need to master your frontend state requirements.

Starting out? You can keep it nice and simple if you prefer to avoid exploring all the advanced features that a state management library can offer! Begin by checking out the store, and only dive into the rest if you're curious later on.

Here's a snapshot of some notable highlights:

✅  Signal-in-a-service approach
✅  Simple, non-intrusive and lightweight
✅  Optimized for Scalability
✅  Imperative-first with Declaritive capabilities
✅  Immutability on demand
✅  Rich plugin ecosystem
✅  Native IndexedDB support
✅  Transactional Undo/Redo
✅  Global State Snaphots and Rollbacks
✅  Devtools support
✅  Effect and Store status tracking
✅  Realtime store performance statistics
✅  Custom plugin support
✅  Built-in testing utilities
✅  SSR friendly
✅  Tree-shakeable

Guiding Principles

  • 🚀 Use class methods to provide controlled access and mutations to shared state.
  • 🌌 If your store becomes too complex and bloated, slice it into multiple stores.
  • ✨ Join and aggregate your state at the component level using signal mechanics.
  • 🌐 Need to sync states between stores synchronously? - Use events.
  • 🔮 Need to decouple actors and consumers as you do in redux? - Use events.
  • 🔄 Craving Immutability? - Just activate it.
  • 🏎️ Don't want full immutability because your store has to be super fast? - Don't activate it.
  • 🧙‍♂️ Seeking a way to encapsulate side effects in a reusable, maintainable, and testable way? - Use effect objects.
  • 🔍 Want a way to reuse and test queries spanning over multiple stores? - Use query objects.
  • 📦 Don't want to use a class for stores? - You don't have to.
  • 🛠️ Tired of debugging state changes in the console? - Enable redux devtools.
  • 🪄 Still want some good old logging magic? - Enable Store logger plugin
  • ⏳ Need to keep track of store history and perform undo/redo operations? - track the history.
  • 💾 Want to sync your state with local storage? - Enable the persistence plugin.
  • 🗄️ Need a more sophisticated store storage or building an offline app? - Use IndexedDB adapter
  • 📈 Need to get notified of whether your store is modified or currently loading? - Enable the Store Status plugin.
  • 📊 Wondering where your bottlenecks are? - Enable the performance counter plugin
  • 🎨 Something's missing? - Write a custom plugin.
  • 📖 Read the docs for more features and concepts.

Installation

Install the library using npm:

npm install signalstory

Sneak peek

import { produce } from 'immer';

// Immutable store class using immer.js for boosting immutable mutations
@Injectable({ providedIn: 'root' })
class BookStore extends ImmutableStore<Book[]> {
  constructor() {
    super({
        initialState: { ... },
        name: 'Books Store',
        mutationProducerFn: produce,
        plugins: [
          useDevtools(),
          usePerformanceCounter(),
          useLogger(),
          useStoreStatus(),
          useStorePersistence(
            configureIndexedDb({
              dbName: 'SharedDatabase',
          })),
        ],
    });

    // Handle store reset request events
    this.registerHandler(storeResetRequestEvent, store => {
      store.set([], 'Reset');
    });
  }

  // Query
  public get getBooksInCollection() {
    return computed(() => this.state().filter(x => x isInCollection));
  }

  // Command
  public addToCollection(bookId: string) {
    this.mutate(state => {
      const book = state.find(x => x.id === bookId);
      if (book) {
        book.isInCollection = true;
      }
    }, 'Add Book To Collection');
  }
}
// Encapsulated multi store query object
export const BooksAndPublishersByAuthorInSwitzerlandQuery = createQuery(
  [BookStore, PublisherStore],
  (books, publishers, authorId: string) => {
    const booksFromAuthor = books.state().filter(x => x.author === authorId);
    const publishersInSwitzerland = publishers
      .state()
      .filter(x => x.country === 'CH');

    return booksFromAuthor.map(book => ({
      book,
      publisher: publishersInSwitzerland.find(
        p => p.id === book.mainPublisherId
      ),
    }));
  }
);
// And then run it
const query = myBookStore.runQuery(
  BooksAndPublishersByAuthorInSwitzerlandQuery,
  'sapowski'
);
// Encapsulated effect object
export const fetchBooksEffect = createEffect(
  'Fetch Books',
  (store: BookStore) => {
    const service = inject(BooksService);
    const notification = inject(NotificationService);

    return service.fetchBooks().pipe(
      catchError(err => {
        notification.alertError(err);
        return of([]);
      }),
      tap(result => store.setBooks(result))
    );
  },
  {
    setLoadingStatus: true, // indicates that the store is loading while the effect runs
    setInitializedStatus: true, // it should mark the store as initialized upon completion
  }
);
// And then run it
myBookStore.runEffect(fetchBooksEffect).subscribe();
const loadingSignal = isLoading(myBookStore); // true while effect is running
const initializedSignal = initialized(myBookStore); // true after initializing effect completion
const modifiedSignal = modified(myBookStore); // true after store update
// Track history spanning multiple stores
const tracker = trackHistory(50, store1, store2);

// Undo single commands
store1.set({ value: 10 }, 'ChangeCommand');
tracker.undo();

tracker.beginTransaction('Transaction Label');
store1.set({ value: 42 }, 'ChangeCommand');
store2.set({ value: 23 }, 'AnotherCommand');
tracker.endTransaction();

// Undo both commands on store1 and store2 at once
tracker.undo();

// Redo the whole transaction
tracker.redo();

Sample Application

To set up and run the sample app locally, follow the steps below:

  1. Clone the repository: Clone the repository containing the signalstory library and the sample app.

  2. Install dependencies: Navigate to the root directory of the repository and run the following command to install the necessary dependencies:

    npm install
  3. Build the library: Run the following command to build the signalstory library:

    ng build signalstory
  4. Serve the sample app: Run the following command to serve the sample app locally:

    ng serve sample --open