SkillAgentSearch skills...

Tansu

tansu is a lightweight, push-based framework-agnostic state management library. It borrows the ideas and APIs originally designed and implemented by Svelte stores and extends them with computed and batch.

Install / Use

/learn @AmadeusITGroup/Tansu

README

Tansu

npm build codecov

Tansu is a lightweight, push-based framework-agnostic state management library. It borrows the ideas and APIs originally designed and implemented by Svelte stores and extends them with computed and batch.

Main characteristics:

  • small conceptual surface with expressive and very flexible API (functional and class-based);
  • can be used to create "local" (module-level or component-level), collaborating stores;
  • can handle both immutable and mutable data;
  • results in compact code with the absolute minimum of boilerplate.

Implementation wise, it is a tiny (1300 LOC) library without any external dependencies.

Installation

You can add Tansu to your project by installing the @amadeus-it-group/tansu package using your favorite package manager, ex.:

  • yarn add @amadeus-it-group/tansu
  • npm install @amadeus-it-group/tansu

Usage

Check out the Tansu API documentation.

The functional part of the API to manage your reactive state can be categorized into three distinct groups:

  • Base store: writable
  • Computed stores: derived, computed, readable
  • Utilities: batch, asReadable, asWritable

writable

api documentation

Writable: A Fundamental Building Block

A writable serves as the foundational element of a "store" – a container designed to encapsulate a value, enabling observation and modification of its state. You can change the internal value using the set or update methods.

To receive notifications whenever the value undergoes a change, the subscribe() method, paired with a callback function, can be employed.

Basic usage

import {writable} from "@amadeus-it-group/tansu";
const value$ = writable(0);

const unsubscribe = values$.subscribe((value) => {
  console.log(`value = ${value}`);
});

value$.set(1);
value$.update((value) => value + 1);

output:

  value = 0
  value = 1
  value = 2

Setup and teardown

The writable's second parameter allows for receiving notifications when at least one subscriber subscribes or when there are no more subscribers.

import {writable} from "@amadeus-it-group/tansu";

const value$ = writable(0, () => {
  console.log('At least one subscriber');

  return () => {
    console.log('No more subscriber');
  }
});

const unsubscribe = values$.subscribe((value) => {
  console.log(`value = ${value}`);
});

value$.set(1);
unsubscribe();

output:

  At least one subscriber
  value = 0
  value = 1
  No more subscriber

derived

api documentation

A derived store calculates its value based on one or more other stores provided as parameters. Since its value is derived from other stores, it is a read-only store and does not have any set or update methods.

Single store

import {writable, derived} from "@amadeus-it-group/tansu";

const value$ = writable(1);
const double$ = derived(value$, (value) => value * 2);

double$.subscribe((double) => console.log('Double value', double));
value$.set(2);

output:

Double value 2
Double value 4

Multiple stores

import {writable, derived} from "@amadeus-it-group/tansu";

const a$ = writable(1);
const b$ = writable(1);
const sum$ = derived([a$, b$], ([a, b]) => a + b);

sum$.subscribe((sum) => console.log('Sum', sum));
a$.set(2);

output:

Sum 2
Sum 3

Asynchronous set

A derived can directly manipulate its value using the set method instead of relying on the returned value of the provided function. This flexibility allows you to manage asynchronous operations or apply filtering logic before updating the observable's value.

import {writable, derived} from "@amadeus-it-group/tansu";

const a$ = writable(0);
const asynchronousDouble$ = derived(a$, (a, set) => {
  const plannedLater = setTimeout(() => set(a * 2));
  return () => {
    // This clean-up function is called if there is no listener anymore,
    // or if the value of a$ changed
    // In this case, the function passed to setTimeout should not be called
    // (if it was not called already)
    clearTimeout(plannedLater);
  };
}, -1);

const evenOnly$ = derived(a$, (a, set) => {
  if (a % 2 === 0) {
      set(a);
  }
}, <number | undefined>undefined);

asynchronousDouble$.subscribe((double) => console.log('Double (asynchronous)', double));
evenOnly$.subscribe((value) => console.log('Even', value));

a$.set(1);
a$.set(2);

output:

Double (asynchronous) -1
Even 0
Even 2
Double (asynchronous) 4

computed

api documentation

A computed store is another variant of a derived store, with the following characteristics:

  • Implicit Dependencies: Unlike in a derived store, there is no requirement to explicitly declare dependencies.

  • Dynamic Dependency Listening: Dependencies are determined based on their usage. This implies that a dependency not actively used is not automatically "listened" to, optimizing resource utilization.

Switch map

This capability to subscribe/unsubscribe to the dependency allows to create switch maps in a natural way.

import {writable, computed} from "@amadeus-it-group/tansu";

const switchToA$ = writable(true);
const a$ = writable(1);
const b$ = writable(0);

const computedValue$ = computed(() => {
  if (switchToA$()) {
    console.log('Return a$');
    return a$();
  } else {
    console.log('Return b$');
    return b$();
  }
});

computedValue$.subscribe((value) => console.log('Computed value:', value));
a$.set(2);
switchToA$.set(false);
a$.set(3);
a$.set(4);
switchToA$.set(true);

output:

Return a$
Computed value: 1
Return a$
Computed value: 2
Return b$
Computed value: 0
Return a$
Computed value: 4

When switchToA$.set(false) is called, the subscription to a$ is canceled, which means that subsequent changes to a$ will no longer trigger the calculation., which is only performed again when switchToA$ is set back to true.

readable

api documentation

Similar to Svelte stores, this function generates a store where the value cannot be externally modified.

import {readable} from '@amadeus-it-group/tansu';

const time = readable(new Date(), (set) => {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return () => clearInterval(interval);
});

derived vs computed

While derived and computed may appear similar, they exhibit distinct characteristics that can significantly impact effectiveness based on use-cases:

  • Declaration of Dependencies:

    • computed: No explicit declaration of dependencies is required, providing more flexibility in code composition.
    • derived: Requires explicit declaration of dependencies.
  • Performance:

    • computed: Better performance by re-running the function only based on changes in the stores involved in the last run.
    • derived: Re-run the function each time a dependent store changes.
  • Asynchronous State:

    • computed: Unable to manage asynchronous state.
    • derived: Can handle asynchronous state with the set method.
  • Skipping Value Emission:

    • computed: Does not provide a mechanism to skip emitting values.
    • derived: Allows skipping the emission of values by choosing not to call the provided set method.
  • Setup and Teardown:

    • computed: Lacks explicit setup and teardown methods.
    • derived: Supports setup and teardown methods, allowing actions such as adding or removing DOM listeners.
    • When the last listener unsubscribes and then subscribes again, the derived function is rerun due to its setup-teardown functionality. In contrast, a computed provides the last value without recomputing if dependencies haven't changed in the meantime.

While computed feels more intuitive in many use-cases, derived excels in scenarios where computed falls short, particularly in managing asynchronous state and providing more granular control over value emissions.

Carefully choosing between them based on specific requirements enhances the effectiveness of state management in your application.

Getting the value

There are three ways for getting the value of a store:

import {writable, get} from "@amadeus-it-group/tansu";

const count$ = writable(1);
const unsubscribe = count$.subscribe((count) => {
  // Will be called with the updated value synchronously first, then each time count$ changes.
  // `unsubscribe` must be called to prevent future calls.
  console.log(count);
});

// A store is also a function that you can call to get the instant value.
console.log(count$());

// Equivalent to
console.log(get(count$));

[!NOTE] Getting the instant value implies the subscription and unsubription on the store:

  • It can be important to know in case of setup/teardown functions.
  • In the same scope, prefer to store the value once in a local variable instead of calling store$() several times.

When called inside a reactive context (i.e. inside a computed), getting the value serves to know and "listen" the dependent stores.

batch

api documentation

Contrary to other libraries like Angular with signals or Svelte with runes, where the callback of a subscription i

View on GitHub
GitHub Stars99
CategoryDesign
Updated7mo ago
Forks10

Languages

TypeScript

Security Score

92/100

Audited on Aug 30, 2025

No findings