natsio extension: distributed counters (ADR-49) over JetStream counter streams
Project description
natsio-counters
Distributed counters over JetStream counter streams
(ADR-49),
mirroring orbit.go/counters.
Distribution natsio-counters, imported as natsio.counters. Zero runtime
dependencies beyond natsio. Pre-1.0, no API-stability promises.
Requires nats-server 2.12+ (the allow_msg_counter stream feature).
What it is
A counter stream is a normal JetStream stream created with
allow_msg_counter. Every subject the stream captures becomes an independent,
arbitrary-precision counter. Incrementing is an ordinary publish carrying a
Nats-Incr header; the server folds the delta into a running total, stores it
as the subject's latest message, and returns the new value in the PubAck — so a
bare add needs no follow-up read.
Python int is itself unbounded, so it is used directly for both deltas and
values — no big-integer wrapper.
Usage
import natsio
from natsio.counters import CounterConfig, create_counter, get_counter
nc = await natsio.connect("nats://localhost")
js = nc.jetstream()
# Create a counter stream (allow_msg_counter + allow_direct are forced on).
counter = await create_counter(js, CounterConfig(name="COUNTS", subjects=["events.>"]))
# ...or bind an existing one: counter = await get_counter(js, "COUNTS")
# Increment / decrement — returns the new running total, straight from the PubAck.
await counter.add("events.orders", 1) # -> 1
await counter.add("events.orders", 10) # -> 11
await counter.add("events.orders", -4) # negative deltas are fine -> 7
await counter.add("events.orders", 0) # zero is a valid no-op increment
# Read a single counter's current value.
value = await counter.load("events.orders") # -> 7
# Full entry: value + most-recent increment + source history (aggregation only).
entry = await counter.get("events.orders")
print(entry.value, entry.incr, entry.sources)
# Enumerate many counters in one batch Direct Get (wildcards allowed).
async for entry in counter.get_multiple(["events.>"]):
print(entry.subject, entry.value)
API
| Symbol | Purpose |
|---|---|
create_counter(js, config) |
Create a counter stream and return its Counter. |
get_counter(js, name) |
Bind an existing counter stream by name. |
counter_from_stream(js, stream) |
Wrap an already-fetched counter Stream (no round-trip). |
Counter.add(subject, delta) -> int |
Increment/decrement; returns the new total. |
Counter.load(subject) -> int |
Current value of one subject's counter. |
Counter.get(subject) -> CounterEntry |
Value + last increment + sources. |
Counter.get_multiple(subjects) -> AsyncIterator[CounterEntry] |
Batch/wildcard enumeration. |
CounterConfig |
Counter-stream configuration. |
CounterEntry |
subject, value, sources, incr. |
Errors (all subclass natsio.jetstream.JetStreamError): CounterNotEnabledError,
DirectAccessRequiredError, CounterNotFoundError,
CounterSubjectNotInitializedError, InvalidCounterValueError.
ADR-49 wire contract
Pinned by the wire-contract tests and exported as constants:
- Increment header
Nats-Incr(COUNTER_INCREMENT_HEADER) — a base-10 integer of any size, optional leading sign. A counter stream rejects any publish without it (message counter increment is missing). - Sources header
Nats-Counter-Sources(COUNTER_SOURCES_HEADER) — server-set JSON{source_stream: {subject: contribution}}; present only on aggregating counters. - Stored payload
{"val": "<int-as-string>"}— the value is a string so it survives languages whose JSON numbers cap at 2^53. - PubAck carries the new total in its
valfield (fast feedback, no Get).
Notes: allow_msg_counter can be set only at stream creation (it is read-only
afterwards) and is incompatible with per-message TTLs and message schedules,
so CounterConfig intentionally does not expose those knobs. Reads use Direct
Get, so allow_direct is required (and forced on by create_counter).
Example
A runnable script is at examples/basic.py — start a server with just server, then:
python extensions/natsio-counters/examples/basic.py
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file natsio_counters-0.1.1.tar.gz.
File metadata
- Download URL: natsio_counters-0.1.1.tar.gz
- Upload date:
- Size: 7.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db09330e574f15f55700a5e78ebbdd0f9fc5f7ab316478d0ee19b4171a298fd1
|
|
| MD5 |
47297307db9840e306c0030fb71c392f
|
|
| BLAKE2b-256 |
31b2829ef59b6c7a226435670eff7f84b8ae9fd1bcad6dbc6c49c52d954b4dd0
|
Provenance
The following attestation bundles were made for natsio_counters-0.1.1.tar.gz:
Publisher:
release-extension.yml on corruptmane/natsio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
natsio_counters-0.1.1.tar.gz -
Subject digest:
db09330e574f15f55700a5e78ebbdd0f9fc5f7ab316478d0ee19b4171a298fd1 - Sigstore transparency entry: 2224581397
- Sigstore integration time:
-
Permalink:
corruptmane/natsio@4b7ab383008db59bb904f0eb6fab3394c6e8d904 -
Branch / Tag:
refs/tags/counters/v0.1.1 - Owner: https://github.com/corruptmane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-extension.yml@4b7ab383008db59bb904f0eb6fab3394c6e8d904 -
Trigger Event:
push
-
Statement type:
File details
Details for the file natsio_counters-0.1.1-py3-none-any.whl.
File metadata
- Download URL: natsio_counters-0.1.1-py3-none-any.whl
- Upload date:
- Size: 10.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2e9b6da644b2464d2b217e6a567f0906c00a8717a2b111b980009c01f058681
|
|
| MD5 |
ccf9a3e05b07a19eb0971040e14500e4
|
|
| BLAKE2b-256 |
2ad0a9de6003a63c28d26ffa3ca43c03f42beaf900747bc85ae9987b3d3459fe
|
Provenance
The following attestation bundles were made for natsio_counters-0.1.1-py3-none-any.whl:
Publisher:
release-extension.yml on corruptmane/natsio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
natsio_counters-0.1.1-py3-none-any.whl -
Subject digest:
c2e9b6da644b2464d2b217e6a567f0906c00a8717a2b111b980009c01f058681 - Sigstore transparency entry: 2224582276
- Sigstore integration time:
-
Permalink:
corruptmane/natsio@4b7ab383008db59bb904f0eb6fab3394c6e8d904 -
Branch / Tag:
refs/tags/counters/v0.1.1 - Owner: https://github.com/corruptmane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-extension.yml@4b7ab383008db59bb904f0eb6fab3394c6e8d904 -
Trigger Event:
push
-
Statement type: