@shiftgraph/node
v0.2.5
Published
Value-free recorder for Node servers: observes the third-party API calls your backend makes and ships structural skeletons to ShiftGraph. Types, keys, and shape only. Values never leave your process.
Maintainers
Readme
@shiftgraph/node
Value-free recorder for Node servers. It observes the third-party API calls
your backend makes (Stripe, OpenAI, Twilio, anything over fetch) and ships
structural skeletons of the responses to ShiftGraph,
which watches them for contract drift: fields changing type, disappearing, or
being restructured while the provider's status page stays green.
Types, keys, and shape only. Values never leave your process.
Install
npm install @shiftgraph/nodeNode 18 or later (uses global fetch). Zero dependencies.
Use
import { ShiftgraphRecorder } from "@shiftgraph/node";
const sg = new ShiftgraphRecorder({
key: process.env.SHIFTGRAPH_KEY!, // sgk_live_… from Sources -> ingest keys
environment: "production",
project: "checkout-api",
});
const uninstall = sg.install(); // wraps global fetch; third-party calls are now recordedThat is the whole integration. Your dependency graph appears in the dashboard as traffic flows, typically within minutes.
What actually gets sent
Before anything leaves your process, every leaf value in a response is replaced with a same-type placeholder:
| Your value | What we send |
| -------------------- | ------------ |
| "ch_live_abc123" | "x" |
| 4999 | 0 |
| 0.93 | 0.5 |
| true | false |
| a secret-looking key | redacted |
Field names, types, and nesting survive. Nothing else does. Run sg.debug()
to inspect what would be sent. The hosted ingest edge independently rejects
any batch that still carries a real value, so the constraint is enforced on
both ends, and the client half is auditable: the reducer is skeleton() in
this package's source, about thirty lines.
Behavior
- Batched: flushes every 5s or 500 events, whichever comes first.
- Backpressure-safe: drops oldest under load. It never blocks your app.
- Never throws into your request path. Everything that goes wrong goes to
onErrorif you provide one, otherwise it is swallowed: network failures, and any response the ingest endpoint rejects. A rejected batch is reported even though nothing threw — a401from a bad key is a completed request that says no, and the version of this SDK before 0.2.3 dropped those in silence, so a mistyped key looked exactly like a working install with no traffic. - Retries
5xxand429by requeueing the batch (bounded bymaxBuffer, drop-oldest). Other4xxresponses reject that payload or that key rather than the attempt, so they are dropped rather than resent forever. Either way you are told which happened. - The flush timer is
unref()ed, so the recorder never keeps your process alive on its own.
Options
new ShiftgraphRecorder({
key: "sgk_live_…", // required
environment: "production", // default "production"
project: "checkout-api", // optional logical grouping
endpoint: "…", // default: the hosted ingest endpoint
flushIntervalMs: 5000,
maxBatch: 500,
maxBuffer: 5000, // offline/backpressure cap
onError: (err) => {}, // transport errors; default: swallow
});Manual recording
Not on fetch? Record calls yourself:
import { skeleton } from "@shiftgraph/node";
sg.record({
method: "POST",
host: "api.openai.com",
path: "/v1/chat/completions",
status: 200,
durationMs: 812,
shape: skeleton(responseBody),
});skeleton() is exported standalone for exactly this.
Shutdown
await sg.close(); // stops the timer, restores fetch, drains the bufferThe drain sends every buffered batch, not just the first one. It stops early if the endpoint is rejecting with a retryable status, because an unbounded retry at process exit is a hang.
Or call the function returned by install() to restore fetch without
stopping the recorder — record() keeps working, so a custom transport can
still feed it.
If another library wrapped fetch after us, neither call removes our wrapper:
taking it out would silently uninstall theirs too. It stops observing instead,
so nothing is recorded or sent through it after you shut the recorder down.
License
MIT. The recorder is open by design: code that touches your traffic should be code you can read.
