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

@effect-firebase/mock

v1.0.1

Published

An in-memory, reactive `FirestoreService` implementation for testing and developing Effect Firebase applications. No Firebase connection required.

Readme

@effect-firebase/mock

An in-memory, reactive FirestoreService implementation for testing and developing Effect Firebase applications. No Firebase connection required.

Beyond a plain test double, the mock is a small simulated backend built for developer experience:

  • Fixtures — seed hard-coded models through your real schemas, so reads exercise the exact decoding path production data takes.
  • Reactive streams — streamDoc / streamQuery are live: writes and runtime toggles push new emissions through already-subscribed streams, just like onSnapshot.
  • Simulated states — flip any collection between data, empty, loading and error at runtime with the MockController, and watch your UI's spinner, empty and error paths render with no backend involved.
  • Latency simulation — add artificial delay to every operation.
  • Write fidelity — server timestamps materialize on write, delete/arrayUnion/arrayRemove/increment sentinels are honored, and queries (where, orderBy, cursors, limits, collection groups) are evaluated in-process.

Installation

npm install --save-dev @effect-firebase/mock

Usage

Provide layer in place of the real Admin or Client layer:

import { Effect, Option } from 'effect';
import { layer as mockFirestore } from '@effect-firebase/mock';

await Effect.runPromise(
  Effect.gen(function* () {
    const repo = yield* PostRepository;

    const postId = yield* repo.add({
      title: 'Test',
      content: '...',
      status: 'draft',
    });
    const post = yield* repo.getById(postId);
    expect(Option.getOrThrow(post).title).toBe('Test');
  }).pipe(Effect.provide(mockFirestore())),
);

Repositories built with Firestore.makeRepository are effects that only need FirestoreService, so providing the mock layer once covers all of them. Each Effect.provide(layer()) call gets a fresh in-memory store, so tests are isolated by default.

Fixtures

Seed the backend with hard-coded models. Documents are encoded through the model's schema, so getById, query and streams decode them exactly like real data:

import { fixture, layer } from '@effect-firebase/mock';
import { DateTime } from 'effect';

const posts = fixture(PostModel, {
  collectionPath: 'posts',
  idField: 'id',
  docs: [
    new PostModel({
      id: PostId.make('1'),
      title: 'Hello world',
      content: '...',
      createdAt: DateTime.makeUnsafe('2024-01-01'),
      // ...
    }),
  ],
});

const mock = layer({ fixtures: [posts] });

For documents without a model schema, use rawFixture with already-encoded data:

import { rawFixture } from '@effect-firebase/mock';

const settings = rawFixture('settings', {
  general: { theme: 'dark' },
});

To fill a page with volume (long lists, pagination, layout stress), map over an array — fixture takes any ReadonlyArray of models:

const manyPosts = fixture(PostModel, {
  collectionPath: 'posts',
  idField: 'id',
  docs: Array.from({ length: 50 }, (_, i) => makePost(i)),
});

Simulated states

The layer also provides a MockController service for driving the backend at runtime — from tests, a dev panel, or a devtools plugin:

import { layer, MockController, MockState } from '@effect-firebase/mock';

Effect.gen(function* () {
  const controller = yield* MockController;

  // Live streams re-emit immediately:
  yield* controller.setState('posts', 'empty');
  yield* controller.setState('posts', 'loading'); // reads hang, streams go silent
  yield* controller.setState('posts', 'error'); // reads/writes fail: code 'unavailable'
  yield* controller.setState('posts', MockState.error('permission-denied'));
  yield* controller.setState('posts', 'data'); // back to normal

  // Apply to every collection at once:
  yield* controller.setState(MockState.All, 'loading');

  // Other controls:
  yield* controller.clearState('posts'); // drop a per-collection override
  yield* controller.setLatency('300 millis');
  yield* controller.seed(morePosts);
  yield* controller.setDoc('posts/raw', { title: 'Raw' }); // bypasses states and latency
  yield* controller.removeDoc('posts/raw');
  yield* controller.reset;

  // Inspect:
  const states = yield* controller.states;
  const docs = yield* controller.docs; // all docs keyed by full path
  // controller.changes is a Stream<StoreSnapshot> of every store change
});

MockState also exports data, empty and loading constants; MockState.error takes a Firestore error code or a full FirestoreError. Fixtures passed to layer/make and controller.seed must use models whose schemas need no extra encoding services.

States can also be set up front:

const mock = layer({
  fixtures: [posts],
  states: { comments: 'loading' },
  latency: '200 millis',
});

A collection group query (queryGroup / streamQueryGroup, or a repository's group view) resolves its state by collection ID, so states: { comments: 'loading' } covers both a top-level comments collection and the comments group across every parent. As in Firestore, a __name__ cursor (Query.orderByDocumentId) in a group query must be a full document path (posts/p1/comments/c1); a bare ID fails with invalid-argument, as it does on the real SDKs. A single-collection query takes a bare ID.

Driving the backend from outside Effect

make() returns a handle instead of just a layer: the same options as layer(), plus direct access to the controller as a plain value. Every controller effect requires no services, so React components, Storybook decorators or test helpers can run them with Effect.runPromise directly. This is what the @effect-firebase/devtools panel builds on:

import { Effect } from 'effect';
import { Atom } from 'effect/reactivity';
import { make } from '@effect-firebase/mock';

const mock = make({ fixtures: [posts] });

// Provide mock.layer to your app runtime (all provides share one store)...
const runtime = Atom.runtime(mock.layer);

// ...and drive the same store from anywhere:
await Effect.runPromise(mock.controller.setState('posts', 'loading'));

Notes on semantics:

  • empty affects reads only; writes still land in the store.
  • loading suspends reads and writes, and live streams stop emitting. A stream subscribed while loading emits nothing until the state flips.
  • error fails effects per call. A live stream fails terminally (matching onSnapshot semantics) — consumers must re-subscribe after the state recovers, e.g. by refreshing the atom/query that owns the stream.

Multiple repositories

Provide one mock layer with fixtures for every collection; each repository reads from the same store:

const program = Effect.gen(function* () {
  const posts = yield* PostRepository;
  const users = yield* UserRepository;
  // ...
}).pipe(Effect.provide(layer({ fixtures: [posts, users] })));

Error handling

A missing document is Option.none(), not a failure. Use the error state to exercise failure paths:

await Effect.runPromise(
  Effect.gen(function* () {
    const controller = yield* MockController;
    yield* controller.setState('posts', 'error');
    const repo = yield* PostRepository;
    return yield* repo.query(Query.limit(10));
  }).pipe(
    Effect.provide(layer()),
    Effect.catchTag('FirestoreError', (e) => Effect.succeed(e.code)), // 'unavailable'
  ),
);

Stubbing individual methods

For a plain test double without the in-memory store, MockFirestoreService(overrides) returns a FirestoreService layer built from the methods you pass; any method you leave out throws "not implemented" when called.

NaN field values

Firestore stores and normalizes NaN, and the mock follows its semantics:

  • Ordering — NaN is placed in the total order below -Infinity, so orderBy lists it first ascending and last descending. Timestamp and GeoPoint sub-fields use the same rule for their numeric components.
  • Equality (==, !=) — NaN is equal only to NaN. This also drives arrayUnion / arrayRemove dedup, so a stored NaN neither absorbs nor is absorbed by a finite value.
  • Range filters (<, <=, >, >=) — a NaN field value is excluded from range scans entirely, even though it sorts below -Infinity for orderBy.
  • in — never matches a NaN field value, even with a NaN candidate (unlike == NaN, which matches).
  • not-in — always includes a NaN field value, regardless of the candidate list.
  • Live streams — streamDoc / streamQuery detect a NaN-to-number transition as a real change (a stored NaN and a finite number are not equal) and re-emit, instead of collapsing it via the old NaN-equals-everything comparison.

Limitations

  • In-memory only — no persistence between process restarts
  • Queries are evaluated in-process — behaviour may differ from real Firestore for edge cases (composite index requirements are not enforced, not-in/!= null semantics are simplified, and a NaN query operand such as where('v', '<', NaN) is not validated — real Firestore rejects it with invalid-argument, the mock returns an empty/ordered result; only NaN field-value semantics are emulated)
  • Simulated states are keyed per collection path (or the '*' wildcard), not per query; collection group queries resolve their state by collection ID
  • No security rules evaluation
  • withTransaction and withBatch run the effect directly — no retries, no rollback, and no staged writes
  • No multi-client synchronization

For tests that need full Firestore semantics, use the Firebase Emulator Suite.

License

MIT