ProvenPin Install

nanostores atoms, maps and computed stores: working versions (tested 2026-10-16)

Model derived state with nanostores using atom and map stores, a computed derived store, plus listen/subscribe and get; assert computed updates and that unsubscribe stops notifications offline with no DOM.

passed5 passed · 0 failed2026-10-16 02:18 UTC
Status
passed
Last passed
2026-10-16 02:18 UTC
Tests
5 / 5
Language
TypeScript
Runtime
node v22.23.3
pinned versions3
nanostores@1.5.5@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38839686104

The code

Exactly the files that passed. Your agent can pull them with get_recipe("nanostores-atom-computed") (install), or see them on GitHub.

package.json
{
  "name": "nanostores-atom-computed",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "nanostores": "1.5.5"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
src/store.ts
import { atom, computed, map } from "nanostores";
import type { MapStore, ReadableAtom, WritableAtom } from "nanostores";

export type LineItem = { sku: string; price: number; qty: number };

export type Cart = {
  taxRate: WritableAtom<number>;
  items: MapStore<Record<string, LineItem>>;
  subtotal: ReadableAtom<number>;
  total: ReadableAtom<number>;
};

export function createCart(initialTaxRate = 0.1): Cart {
  const taxRate = atom(initialTaxRate);
  const items = map<Record<string, LineItem>>({});

  const subtotal = computed(items, (bySku) => {
    let sum = 0;
    for (const item of Object.values(bySku)) {
      sum += item.price * item.qty;
    }
    return Math.round(sum * 100) / 100;
  });

  const total = computed([subtotal, taxRate], (sub, rate) =>
    Math.round(sub * (1 + rate) * 100) / 100,
  );

  return { taxRate, items, subtotal, total };
}
test/store.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { atom, map } from "nanostores";
import { createCart } from "../src/store.ts";

test("atom get and set", () => {
  const count = atom(0);
  assert.equal(count.get(), 0);
  count.set(7);
  assert.equal(count.get(), 7);
});

test("map get and setKey", () => {
  const user = map({ name: "Ada", age: 36 });
  assert.equal(user.get().name, "Ada");
  user.setKey("age", 37);
  assert.equal(user.get().age, 37);
  user.set({ name: "Grace", age: 45 });
  assert.deepEqual(user.get(), { name: "Grace", age: 45 });
});

test("subscribe fires immediately and stops after unsubscribe", () => {
  const count = atom(1);
  const seen: number[] = [];
  const off = count.subscribe((value) => seen.push(value));
  assert.deepEqual(seen, [1]);
  count.set(2);
  assert.deepEqual(seen, [1, 2]);
  off();
  count.set(3);
  assert.deepEqual(seen, [1, 2]);
  assert.equal(count.get(), 3);
});

test("listen skips the initial value and stops after unsubscribe", () => {
  const count = atom(0);
  const seen: number[] = [];
  const off = count.listen((value) => seen.push(value));
  assert.deepEqual(seen, []);
  count.set(1);
  assert.deepEqual(seen, [1]);
  off();
  count.set(2);
  assert.deepEqual(seen, [1]);
});

test("computed derives from atom and map stores and freezes when unsubscribed", () => {
  const cart = createCart(0.1);
  const seen: number[] = [];
  const off = cart.total.listen((value) => seen.push(value));

  cart.items.setKey("a", { sku: "a", price: 10, qty: 2 });
  assert.equal(cart.subtotal.get(), 20);
  assert.equal(cart.total.get(), 22);

  cart.taxRate.set(0.2);
  assert.equal(cart.subtotal.get(), 20);
  assert.equal(cart.total.get(), 24);
  assert.equal(seen[seen.length - 1], 24);

  const notifications = seen.length;
  off();
  cart.items.setKey("b", { sku: "b", price: 5, qty: 1 });
  cart.taxRate.set(0.5);
  // Listeners stop, but the stores themselves keep updating.
  assert.equal(seen.length, notifications);
  assert.equal(cart.subtotal.get(), 25);
  assert.equal(cart.total.get(), 37.5);
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.