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

@matheuspuel/query-state

v0.3.0

Published

Type safe, immutable query state representation with loading, success, and failure states.

Readme

QueryState

Type safe, immutable query state representation with loading, success, and failure states.

Useful for defining UI state related to asynchronous operations.

This allows use cases like:

  • Show stale data (success) while refreshing (loading)
  • Display an error (failure) alongside stale data (success)
  • Track loading progress while retaining previous results

State properties

Each QueryState instance has three properties that can be independently set or cleared:

| Property | Type | Meaning | | --------- | ---------------------------------------------- | ------------------------------------ | | loading | { time?: DateTime.Utc, progress: P } \| null | Current loading state. | | success | { time?: DateTime.Utc, data: A } \| null | Last still valid, successful result. | | failure | { time?: DateTime.Utc, error: E } \| null | Last still applicable failure. |

Combination of state properties

The possible combinations of state properties allow for a comprehensive representation of an operations' state.

| Loading | Success | Failure | Meaning | | ------- | ------- | ------- | ------------------------------------------------------------ | | - | - | - | The operation has not started. | | ✅ | - | - | In progress, no previous data or errors. | | - | ✅ | - | Successfully completed. | | - | - | ✅ | Failed. | | ✅ | ✅ | - | Refreshing with stale data available. | | ✅ | - | ✅ | Retrying after a previous failure. | | - | ✅ | ✅ | Has stale data, but failed while refreshing. | | ✅ | ✅ | ✅ | Has stale data, failed while refreshing and is now retrying. |

Key behaviors

  • start() sets loading and preserves success and failure
  • succeed() sets success and clears loading and failure
  • fail() sets failure, clears loading, but preserves success

Install

pnpm add @matheuspuel/query-state

Usage

import { QueryState } from '@matheuspuel/query-state'

// Create an empty QueryState
let queryState = QueryState.initial<
  // success data type
  { count: number },
  // failure error type
  'NetworkError' | 'ValidationError'
>()
console.log(queryState)
// QueryState { loading: null, success: null, failure: null }

// Start
queryState = queryState.start()
console.log(queryState)
// QueryState { loading: { progress: undefined }, success: null, failure: null }

// Fail
queryState = queryState.fail({ error: 'NetworkError' })
console.log(queryState)
// QueryState { loading: null, success: null, failure: { error: 'NetworkError' } }

// Retry
queryState = queryState.start()
console.log(queryState)
// QueryState { loading: { progress: undefined }, success: null, failure: { error: 'NetworkError' } }

// Succeed
queryState = queryState.succeed({ data: { count: 1 } })
console.log(queryState)
// QueryState { loading: null, success: { data: { count: 1 } }, failure: null }

// Refresh, but fail and retry again
queryState = queryState.start()
console.log(queryState)
// QueryState { loading: { progress: undefined }, success: { data: { count: 1 } }, failure: null }
queryState = queryState.fail({ error: 'ValidationError' })
console.log(queryState)
// QueryState { loading: null, success: { data: { count: 1 } }, failure: { error: 'ValidationError' } }
queryState = queryState.start()
console.log(queryState)
// QueryState { loading: { progress: undefined }, success: { data: { count: 1 } }, failure: { error: 'ValidationError' } }

Tracking progress

You can track the loading progress for showing a progress bar.

import { QueryState } from '@matheuspuel/query-state'

let queryState = QueryState.initial<
  string,
  Error,
  // loading progress type
  number
>()
// QueryState { loading: null, success: null, failure: null }

queryState = queryState.makeProgress(0.1)
console.log(queryState)
// QueryState { loading: { progress: 0.1 }, success: null, failure: null }
queryState = queryState.makeProgress(0.2)
console.log(queryState)
// QueryState { loading: { progress: 0.2 }, success: null, failure: null }

Or you can use it to describe the current step in progress.

let queryState = QueryState.initial<
  string,
  Error,
  'validating' | 'fetching' | 'calculating'
>().makeProgress('validating')
console.log(queryState)
// QueryState { loading: { progress: 'validating' }, success: null, failure: null }

Set times

Set the time the operation started and completed to help determining how old the data is or how long the operation is taking to complete.

import { QueryState } from '@matheuspuel/query-state'
import { DateTime } from 'effect'

let queryState: QueryState<number, Error> = QueryState.initial()
console.log(queryState)

// Start and set time
queryState = queryState.start({
  time: DateTime.unsafeMake('2026-01-01T00:00:00Z'),
})
console.log(queryState)
// QueryState {
//   loading: { time: DateTime.Utc(2026-01-01T00:00:00.000Z), progress: undefined },
//   success: null,
//   failure: null
// }

// Succeed and set the result time
queryState = queryState.succeed({
  data: 10,
  time: DateTime.unsafeMake('2026-01-01T00:00:00Z'),
})
console.log(queryState)
// QueryState {
//   loading: null,
//   success: { data: 10, time: DateTime.Utc(2026-01-01T00:00:00.000Z) },
//   failure: null
// }

// Fail and set the failure time
queryState = queryState.fail({
  error: new Error('Something went wrong'),
  time: DateTime.unsafeMake('2026-01-01T00:00:00Z'),
})
console.log(queryState)
// QueryState {
//   loading: null,
//   success: { data: 10, time: DateTime.Utc(2026-01-01T00:00:00.000Z) },
//   failure: {
//     error: Error: Something went wrong
//         at ...
//     time: DateTime.Utc(2026-01-01T00:00:00.000Z)
//   }
// }

Track Effect

Use QueryState.trackEffect to run an Effect while automatically updating the QueryState.

import { QueryState } from '@matheuspuel/query-state'
import { Effect } from 'effect'

const validate = (input: {
  name: string
}): Effect.Effect<{ id: number }, 'ValidationError'> =>
  Effect.succeed({ id: 3 })

const fetchResult = (data: {
  id: number
}): Effect.Effect<{ count: number }, 'NetworkError'> =>
  Math.random() > 0.7
    ? Effect.succeed({ count: 15 })
    : Effect.fail('NetworkError')

let queryState = QueryState.initial<
  { count: number },
  'NetworkError' | 'ValidationError',
  'validating' | 'fetching'
>()

const updateQueryState = (
  f: (state: typeof queryState) => typeof queryState,
) => {
  queryState = f(queryState)
  console.log(queryState)
}

const run = QueryState.trackEffect(
  updateQueryState,
  (input: { name: string }, update) =>
    Effect.gen(function* () {
      const data = yield* validate(input)
      update(_ => _.makeProgress('fetching'))
      const result = yield* fetchResult(data)
      return result
    }),
  { initialProgress: 'validating' },
)

Effect.gen(function* () {
  const result = yield* run({ name: 'Test' }).pipe(Effect.retry({}))
  console.log('Final result:', result)
}).pipe(Effect.runSync)

// QueryState { loading: { time: DateTime.Utc(2026-02-11T20:39:27.392Z), progress: 'validating' }, success: null, failure: null }
// QueryState { loading: { time: DateTime.Utc(2026-02-11T20:39:27.392Z), progress: 'fetching' }, success: null, failure: null }
// QueryState { loading: null, success: null, failure: { time: DateTime.Utc(2026-02-11T20:39:27.392Z), error: 'NetworkError' } }
// QueryState { loading: { time: DateTime.Utc(2026-02-11T20:39:27.403Z), progress: 'validating' }, success: null, failure: { time: DateTime.Utc(2026-02-11T20:39:27.392Z), error: 'NetworkError' } }
// QueryState { loading: { time: DateTime.Utc(2026-02-11T20:39:27.403Z), progress: 'fetching' }, success: null, failure: { time: DateTime.Utc(2026-02-11T20:39:27.392Z), error: 'NetworkError' } }
// QueryState { loading: null, success: { time: DateTime.Utc(2026-02-11T20:39:27.403Z), data: { count: 15 } }, failure: null }
// Final result: { count: 15 }