New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

tanstack-db-atom

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tanstack-db-atom

TanStack DB utilities for Effect Atom - provides reactive atoms that integrate with TanStack DB collections and queries

latest
Source
npmnpm
Version
1.0.0
Version published
Weekly downloads
3
Maintainers
1
Weekly downloads
 
Created
Source

tanstack-db-atom

npm version License: MIT

TanStack DB utilities for Effect Atom - provides reactive atoms that integrate with TanStack DB collections and queries.

Features

  • Type-safe reactive atoms from TanStack DB queries
  • Automatic subscription management with cleanup on unmount
  • Result-based error handling with Result.Result<T>
  • Support for single and array results
  • Conditional queries that can be enabled/disabled
  • Integration with Effect Atom ecosystem

Installation

npm install tanstack-db-atom

Peer Dependencies

This package requires the following peer dependencies:

npm install @effect-atom/atom-react @tanstack/db effect
{
  "dependencies": {
    "tanstack-db-atom": "^1.0.0",
    "@effect-atom/atom-react": "^1.0.0",
    "@tanstack/db": "^0.5.0",
    "effect": "^3.0.0"
  }
}

Quick Start

1. Setup TanStack DB Collection

import { createCollection } from '@tanstack/db'

const todoCollection = createCollection<Todo, string>({
  id: 'todos',
  getKey: (todo) => todo.id,
  sync: {
    sync: async (params) => {
      const todos = await fetchTodosFromAPI()
      params.begin()
      for (const todo of todos) {
        params.write({ type: 'insert', value: todo })
      }
      params.commit()
      params.markReady()
    }
  },
  startSync: true
})

2. Create Query Atom

import { makeQuery } from 'tanstack-db-atom'
import { useAtom } from '@effect-atom/atom-react'
import { Result } from '@effect-atom/atom-react'
import { eq } from '@tanstack/db'

const activeTodosAtom = makeQuery((q) =>
  q.from({ todos: todoCollection })
   .where(({ todos }) => eq(todos.completed, false))
)

3. Use in React Component

function TodoList() {
  const todosResult = useAtom(activeTodosAtom)

  return Result.match(todosResult, {
    onInitial: () => <Loading />,
    onFailure: (error) => <Error message={error.message} />,
    onSuccess: (todos) => (
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    )
  })
}

Usage

Basic Query

Create an atom from a TanStack DB query with filtering and projection:

import { makeQuery } from 'tanstack-db-atom'
import { useAtom } from '@effect-atom/atom-react'
import { Result } from '@effect-atom/atom-react'
import { eq } from '@tanstack/db'

const activeTodosAtom = makeQuery((q) =>
  q.from({ todos: todoCollection })
   .where(({ todos }) => eq(todos.completed, false))
   .select(({ todos }) => ({
     id: todos.id,
     title: todos.title
   }))
)

function TodoList() {
  const todosResult = useAtom(activeTodosAtom)

  return Result.match(todosResult, {
    onInitial: () => <Loading />,
    onFailure: (error) => <Error message={error.message} />,
    onSuccess: (todos) => (
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    )
  })
}

Unsafe Query (Simplified)

For when you don't need error handling, use makeQueryUnsafe:

import { makeQueryUnsafe } from 'tanstack-db-atom'
import { useAtom } from '@effect-atom/atom-react'

const todosAtom = makeQueryUnsafe((q) =>
  q.from({ todos: todoCollection })
)

function TodoList() {
  const todos = useAtom(todosAtom) // Array<Todo> | undefined

  if (!todos) return <Loading />

  return <ul>{todos.map(todo => <TodoItem key={todo.id} {...todo} />)}</ul>
}

Conditional Queries

Queries that can be enabled/disabled based on runtime conditions:

import { makeQueryConditional } from 'tanstack-db-atom'

const userTodosAtom = makeQueryConditional((q) => {
  const userId = getCurrentUserId()
  if (!userId) return null  // Disabled when no user

  return q.from({ todos: todoCollection })
          .where(({ todos }) => eq(todos.userId, userId))
})

function UserTodos() {
  const todosResult = useAtom(userTodosAtom)

  if (!todosResult) return <div>Please log in</div>

  return Result.match(todosResult, {
    onInitial: () => <Loading />,
    onFailure: (error) => <Error message={error.message} />,
    onSuccess: (todos) => <TodoList todos={todos} />
  })
}

Single Result Queries

For queries that return a single item:

const currentUserAtom = makeQuery((q) =>
  q.from({ users: userCollection })
   .where(({ users }) => eq(users.id, currentUserId))
   .limit(1)
)

function UserProfile() {
  const userResult = useAtom(currentUserAtom)

  return Result.match(userResult, {
    onInitial: () => <Loading />,
    onFailure: () => <div>User not found</div>,
    onSuccess: (users) => {
      const user = users[0]
      return user ? <UserProfileCard user={user} /> : null
    }
  })
}

Working with Existing Collections

Create atoms from pre-existing TanStack DB collections:

import { makeCollectionAtom, makeSingleCollectionAtom } from 'tanstack-db-atom'

// For collections that return arrays
const todosAtom = makeCollectionAtom(todoCollection)

// For collections with singleResult: true
const currentUserAtom = makeSingleCollectionAtom(currentUserCollection)

Query Options

Configure query behavior with options:

const todosAtom = makeQuery(
  (q) => q.from({ todos: todoCollection }),
  {
    gcTime: 5000,              // Keep collection alive for 5s after unmount
    startSync: true,           // Start sync immediately
    suspendOnWaiting: false    // Don't suspend on waiting state
  }
)

Atom Families

Create parameterized queries with Atom families:

import { Atom } from '@effect-atom/atom-react'

const todosByStatusFamily = Atom.family((completed: boolean) =>
  makeQuery((q) =>
    q.from({ todos: todoCollection })
     .where(({ todos }) => eq(todos.completed, completed))
  )
)

function CompletedTodos() {
  const completedTodos = useAtom(todosByStatusFamily(true))

  return Result.match(completedTodos, {
    onInitial: () => <Loading />,
    onFailure: (error) => <Error message={error.message} />,
    onSuccess: (todos) => <TodoList todos={todos} />
  })
}

Complex Queries with Joins

TanStack DB's query builder supports joins and complex transformations:

const enrichedTodosAtom = makeQuery((q) =>
  q.from({ todos: todoCollection })
   .join(
     { users: userCollection },
     ({ todos, users }) => eq(todos.userId, users.id)
   )
   .where(({ todos }) => eq(todos.completed, false))
   .select(({ todos, users }) => ({
     id: todos.id,
     title: todos.title,
     userName: users.name,
     userAvatar: users.avatarUrl
   }))
)

API Reference

makeQuery

Creates an Atom from a TanStack DB query function.

import type { QueryOptions } from 'tanstack-db-atom'

function makeQuery<TContext extends Context>(
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
  options?: QueryOptions
): Atom<Result<InferResultType<TContext>, Error>>

Parameters:

  • queryFn: Function that builds a TanStack DB query
  • options: Optional query configuration

Options:

  • gcTime?: number - Garbage collection time in milliseconds (default: 0)
  • startSync?: boolean - Whether to start sync immediately (default: true)
  • suspendOnWaiting?: boolean - Suspend on waiting state with Atom.result() (default: false)

Returns: Atom<Result<T, Error>> - An atom that emits Result states

makeQueryUnsafe

Creates an Atom that returns data or undefined (no Result wrapper).

function makeQueryUnsafe<TContext extends Context>(
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>,
  options?: QueryOptions
): Atom<InferResultType<TContext> | undefined>

Returns: Atom<T | undefined> - Data when available, undefined when loading/error

makeQueryConditional

Creates an Atom from a conditional query function.

function makeQueryConditional<TContext extends Context>(
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext> | null | undefined,
  options?: QueryOptions
): Atom<Result<InferResultType<TContext>, Error> | undefined>

Behavior:

  • Returns undefined atom when query function returns null or undefined
  • Returns Result<T> atom when query function returns a QueryBuilder

makeCollectionAtom

Creates an Atom from an existing TanStack DB collection.

function makeCollectionAtom<T extends object, TKey extends string | number>(
  collection: Collection<T, TKey, any> & NonSingleResult
): Atom<Result<Array<T>, Error>>

Best for: Collections that return arrays of items

makeSingleCollectionAtom

Creates an Atom from a single-result collection.

function makeSingleCollectionAtom<T extends object, TKey extends string | number>(
  collection: Collection<T, TKey, any> & SingleResult
): Atom<Result<T | undefined, Error>>

Best for: Collections with singleResult: true configuration

How It Works

Lifecycle Management

  • Initial Load: Collection sync starts immediately (unless startSync: false)
  • Status Mapping:
    • idle/loadingResult.initial(true) (waiting state)
    • errorResult.fail(error)
    • readyResult.success(data)
    • cleaned-upResult.fail(error)
  • Reactive Updates: Subscribes to collection.subscribeChanges()
  • Cleanup: Unsubscribes automatically via get.addFinalizer()

Incremental View Maintenance

TanStack DB uses D2 (Differential Datalog) for efficient incremental updates:

  • Changes are computed incrementally, not by re-running full queries
  • Only affected rows trigger updates
  • Joins and complex transformations are automatically optimized

Memory Management

  • Collections are cleaned up when atom is unmounted (gcTime: 0 by default)
  • Subscriptions are automatically removed via finalizers
  • No memory leaks - all resources properly cleaned up

Integration with Effect Atom

Works seamlessly with other Effect Atom features:

import { Atom } from '@effect-atom/atom-react'

// Combine with Atom.map
const todoCountAtom = Atom.map(
  makeQueryUnsafe((q) => q.from({ todos: todoCollection })),
  (todos) => todos?.length ?? 0
)

// Use with Atom.flatMap
const selectedTodoAtom = Atom.flatMap(
  selectedIdAtom,
  (id) => makeQuery((q) =>
    q.from({ todos: todoCollection })
     .where(({ todos }) => eq(todos.id, id))
     .limit(1)
   )
)

// Combine multiple queries
const dashboardDataAtom = Atom.all({
  todos: makeQuery((q) => q.from({ todos: todoCollection })),
  users: makeQuery((q) => q.from({ users: userCollection })),
  stats: makeQuery((q) => q.from({ stats: statsCollection }))
})

Result Pattern

This package uses the Result pattern for safe error handling:

import { Result } from '@effect-atom/atom-react'

// Type narrowing
if (Result.isSuccess(result)) {
  // result is Result.Success<T>
  console.log(result.value)
} else if (Result.isInitial(result)) {
  // result is Result.Initial
  console.log('Loading...', result.waiting)
}

// Pattern matching
const display = Result.match(result, {
  onInitial: () => 'Loading...',
  onFailure: (error) => `Error: ${error.message}`,
  onSuccess: (data) => `Data: ${JSON.stringify(data)}`
})

// Get with fallback
const data = Result.getOrElse(result, () => [])

Benefits

  • Seamless Integration: Natural bridge between TanStack DB and Effect Atom
  • Type Safety: Full TypeScript inference throughout
  • Performance: Leverages D2 for efficient incremental updates
  • Familiar API: Similar to useLiveQuery and React Query patterns
  • Composable: Works with Atom.family, Atom.map, Atom.flatMap, etc.
  • Error Handling: Built-in Result types for safe error handling
  • Lifecycle Management: Automatic subscription cleanup via finalizers

Comparison with useLiveQuery

FeatureuseLiveQuery (React)makeQuery (Effect Atom)
FrameworkReactFramework-agnostic
SubscriptionuseSyncExternalStoreAtom finalizers
Error HandlingStatus flagsResult types
ComposabilityLimitedHigh (Atom combinators)
TypeScriptFull inferenceFull inference
PerformanceOptimizedOptimized

TypeScript Support

This package is written in TypeScript and provides full type inference:

// Type is inferred from the query
const todosAtom = makeQuery((q) =>
  q.from({ todos: todoCollection })
   .select(({ todos }) => ({
     id: todos.id,
     title: todos.title
   }))
)

// Type is Atom<Result<Array<{id: string, title: string}>, Error>>
const result = useAtom(todosAtom)

if (Result.isSuccess(result)) {
  // TypeScript knows result.value has {id, title}
  console.log(result.value[0]?.title)
}

License

MIT

Support

Keywords

tanstack

FAQs

Package last updated on 12 Jan 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts