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

@gridlock/economy

v0.2.1

Published

Generic token-economy primitives for gridlock games — append-only ledger, spend/credit errors, numeric validation. Product packages compose on top with their own reason enums + catalogs.

Readme

@gridlock/economy

Generic token-economy primitives for gridlock games — the mechanism half of the engine/product split. Products compose on top with their own reason enums, offer catalogs, and prices.

The engine defines what the system can do; the product defines what it means. Reason enums, offer catalogs, prices, caps, and player-visible prose are all product-side. Ledger semantics, gate ordering, refusal codes, and the compensating-transaction shape are engine-side.

Migrated from TIC's @tic/economy across MS-27 follow-up #1497. See docs/architecture/economy.md § Architecture in the TIC repo for the gridlock/TIC split, the composition pattern, and the designer-interface table.

What you get

| Symbol | Purpose | |---|---| | TokenLedger<TReason> | Append-only per-player ledger contract with credit / spend / adjust / getBalance / getHistory / replayBalance. Concurrency contract: two simultaneous spends serialise via SQLite BEGIN IMMEDIATE (in-memory backing inherits JS single-thread atomicity). | | InMemoryTokenLedger<TReason> | Default backing — single-threaded JS atomicity. | | SqliteTokenLedger<TReason> | Production backing — BEGIN IMMEDIATE concurrency, schema migrations baked in, persistence across restart. | | LedgerEntry<TReason> / HistoryQuery<TReason> | Generic value types. | | InsufficientBalanceError, InvalidLedgerInputError | Ledger errors. | | assertPositiveIntegerDelta, assertNonZeroIntegerDelta, assertNonEmptyPlayerId | Generic validation helpers products plug into the ledger's assertCredit / assertSpend hooks. | | TransactionStore (+ InMemoryTransactionStore, SqliteTransactionStore) | Off-ledger purchase metadata: 1:1 with the host's purchase-reason ledger entry. Holds external-provider session id, SKU, currency, status. Supports refund / chargeback transitions. | | HistoryExporter<TReason> | Player-visible ledger + purchase history exporter (JSON + CSV). Constructor takes transactionReason: TReason naming the single ledger reason whose entries pair with TransactionRecord rows. | | OfferEngine<TOfferId, TReason> | Spend-gate pipeline: known offer → enabled in case → stage id (per-stage caps) → frequency cap → balance. Ledger is never written on a refusal. Constructor requires resolveOffer(offerId): OfferDefinition \| undefined — no catalog ships in gridlock. | | CompensatingOfferFlow<TOfferId, TReason> | "Redeem an offer, run a host-supplied restore, refund on failure" pattern. Hosts construct one flow per compensating offer (e.g. TIC's appeal). The frequency-cap slot is not refunded on restore failure — the player still consumed the slot. |

Status

End-state as of 2026-04-28. All five MS-27 #1497 slices have shipped; the package is feature-complete as a mechanism layer and is consumed by @tic/economy through workspace-protocol re-exports.

Installation

npm install @gridlock/economy better-sqlite3

better-sqlite3 is a peer-style runtime dependency required by the SQLite backings; the in-memory backings + the contract types work without it but tree-shaking won't drop the import unless you avoid SqliteTokenLedger / SqliteTransactionStore.

Wiring a product on top

A new gridlock game defines its own TReason and TOfferId unions and supplies a catalog resolver. Reason enums, prices, caps, and ack templates stay in your code — the engine never sees them.

import {
  CompensatingOfferFlow,
  HistoryExporter,
  InMemoryTokenLedger,
  OfferEngine,
  type OfferDefinition,
} from '@gridlock/economy';

// Your product reason union — the ledger and offer engine both bind to it.
type MyReason =
  | 'grant:welcome'
  | 'grant:level-up'
  | 'spend:hint'
  | 'spend:retry'
  | 'purchase'
  | 'refund'
  | 'adjustment:admin';

type MyOfferId = 'hint' | 'retry';

const CATALOG: Readonly<Record<MyOfferId, OfferDefinition<MyOfferId, MyReason>>> = {
  hint: {
    offerId: 'hint',
    costInTokens: 1,
    reason: 'spend:hint',
    cap: { kind: 'per-stage', max: 1 },
    description: 'Reveal one hint for the current puzzle.',
    ackTemplate: 'Hint unlocked — {costInTokens} token spent ({balanceRemaining} left).',
  },
  retry: {
    offerId: 'retry',
    costInTokens: 5,
    reason: 'spend:retry',
    cap: { kind: 'per-case', max: 3 },
    description: 'Replay the level with progress preserved.',
    ackTemplate: 'Retry granted — {costInTokens} tokens spent ({balanceRemaining} left).',
  },
};

const ledger = new InMemoryTokenLedger<MyReason>({
  defaultAdjustmentReason: 'adjustment:admin',
});

const offerEngine = new OfferEngine<MyOfferId, MyReason>({
  ledger,
  resolveOffer: (id) => CATALOG[id],
});

const retryFlow = new CompensatingOfferFlow<MyOfferId, MyReason>({
  ledger,
  offerEngine,
  offerId: 'retry',
  refundReason: 'refund',
});

const historyExporter = new HistoryExporter<MyReason>({
  ledger,
  transactionStore, // your TransactionStore instance
  transactionReason: 'purchase',
});

That's the whole interface. All gating logic, atomicity, error shapes, and CSV/JSON formats are inherited.

What stays in your product package

  • The reason union itself + credit/spend kind sets.
  • The catalog (offer ids, prices, caps, ack templates).
  • Earn schedules tied to your game's progression model.
  • Appeal/retry constants if you want them as importable values.
  • Player-facing alert systems, balance UIs, leaderboards.

Reference

  • TIC's wiring: packages/economy/src/ in the TIC repo — @tic/economy is the canonical example consumer, with all generic shapes pinned to TIC's TokenReason + OfferId unions.
  • Migration history: TIC issue #1497.
  • Architecture rationale: docs/architecture/economy.md § Architecture in the TIC repo.