churn-warn-node-sdk
v0.1.0
Published
Async fetch client for ChurnWarn Gateway event APIs (Node 18+).
Maintainers
Readme
ChurnWarn Node.js SDK
Zero runtime dependencies. Uses global fetch (Node 18+).
Background capture API: initialize → captureEvent (queued) → periodic / size-based flush to POST /api/events/batch. Call shutdown() to drain and stop the interval.
Install
From the repo (path or file: in your app’s package.json):
"dependencies": {
"churn-warn-node-sdk": "file:../sdks/churn_warn_node_sdk"
}Usage (ESM)
import { initialize, captureEvent, shutdown, Metrics, RawEvents } from 'churn-warn-node-sdk';
initialize({
baseUrl: 'https://your-gateway.example.com',
apiToken: process.env.CHURNWARN_TOKEN, // or apiKey for X-Api-Key
defaultTenantId: null,
defaultSource: 'node_sdk',
batchSize: 50,
flushIntervalMs: 5000,
maxQueueSize: 10000,
onSendError: (e) => console.error('send failed', e),
});
captureEvent('acct-1', Metrics.LOGIN, { payload: { path: '/' } });
captureEvent({
externalAccountId: 'acct-2',
eventType: RawEvents.APP_LOGIN,
payload: { x: 1 },
});
await shutdown();Account facts — upsertAccount(externalId, fields)
Some dashboard-template signals are slow-changing account facts, not events: the fintech
direct_deposit/kyc_completed flags, a marketplace account's role, the mobile
push_opt_in flag, or a headline money figure. Write them with upsertAccount, which
PUTs to /api/accounts/{externalId}. Unlike captureEvent, it awaits the request and
rejects on a non-2xx response. Only fields you pass are written.
import { upsertAccount, AccountAttributes, BusinessTypes } from 'churn-warn-node-sdk';
await upsertAccount('acct-1', {
businessType: BusinessTypes.FINTECH,
monetaryValue: 2450.0,
valueBasis: 'balance',
role: 'buyer', // marketplace side
attributes: { [AccountAttributes.DIRECT_DEPOSIT]: true, kyc_completed: true },
});Known keys (name, email, kind, businessType, monetaryValue, valueBasis,
currency, planKey, lifecycleStage, renewalAt, status, role) map to account
columns; attributes merges into the fact bag (a null value removes a key). Enum-ish fields
are lowercased. Put metrics in events, facts in upsertAccount.
Auth
apiKey→X-Api-Key.apiToken→Authorization: Bearer …(leadingBeareris stripped).
Options
| Option | Default | Description |
|--------|---------|-------------|
| baseUrl | required | Gateway root URL |
| apiKey | — | X-Api-Key header (preferred for servers) |
| apiToken | — | Bearer JWT when apiKey is not set |
| defaultTenantId | — | Applied when events omit tenantId |
| defaultSource | node_sdk | Event source field |
| batchSize | 50 | Max events per flush (≤ 500, MAX_EVENTS_PER_BATCH) |
| flushIntervalMs | 5000 | Max wait before sending a non-empty queue |
| maxQueueSize | 10000 | When full, the event is dropped and onSendError is called |
| requestTimeoutMs | 30000 | Per-request timeout for a batch POST |
| maxRetries | 3 | Retry attempts after the first try for a failed flush (0 disables) |
| retryBaseDelayMs | 500 | Base delay for exponential backoff between retries |
| retryMaxDelayMs | 30000 | Upper bound on any single backoff delay |
| redactPayload | true | Mask common sensitive patterns before enqueue |
| onBeforeEnqueue | — | Optional hook to transform events before enqueue |
| onSendError | — | Called when a background flush fails |
| fetch | global fetch | Optional override (tests or polyfills) |
Retries and delivery
A failed batch flush is retried up to maxRetries times with exponential backoff and equal
jitter (delay = min(retryMaxDelayMs, retryBaseDelayMs × 2ⁿ), half fixed / half random), capped
by retryMaxDelayMs.
Only transient failures are retried:
- network errors and request timeouts (
requestTimeoutMs) - HTTP 429 and 5xx
Everything else (4xx other than 429 — bad auth, validation errors) fails immediately and is
reported through onSendError; retrying would not help. Every event carries an
idempotencyKey, so a retried batch never duplicates events server-side.
When all attempts are exhausted, the batch is dropped and the final error goes to
onSendError. Capture is fire-and-forget: no failure ever surfaces to the captureEvent
caller.
Batch chunking
Each flush sends up to 500 events per HTTP request; larger in-memory slices are split automatically.
Tenant id in batches
If any event includes tenantId, every non-empty tenantId in that flush must be identical. That value is sent as the batch-level tenantId, or combined with the client’s defaultTenantId when all per-event values are omitted.
Errors
Non-success HTTP responses during background send surface through onSendError as ChurnWarnApiError (statusCode, message, body). A direct 202 batch response can still include per-row error strings when using a lower-level API.
Payload and idempotency
- Use
payload(object) orpayloadJson(string). The API receives a JSON string inpayload. - Omit
idempotencyKeyto let the SDK generate one per event.
Privacy and payload redaction
By default (redactPayload: true), the SDK redacts common sensitive patterns in payloads before enqueue:
- Masks emails, phone numbers, credit cards, SSN-like values, JWTs, API keys, and URL-embedded passwords in string values.
- Strips query strings and hashes from
url,referrer, and keys ending inUrl. - Replaces values for keys containing
password,secret,token,api_key,authorization,cookie,ssn, orcredit_cardwith***.
initialize({
baseUrl: 'https://your-gateway.example.com',
apiToken: process.env.CHURNWARN_TOKEN,
redactPayload: true, // default
onBeforeEnqueue: (rec) => rec, // optional custom hook
});payloadJsonis parsed and redacted when possible; if parsing fails, the raw string is masked.- Prefer sending
pathorrouteinstead of full URLs in server-side payloads. - Avoid using emails or usernames as
externalAccountIdwhen a stable non-PII id is available.
Exported helpers: maskSensitiveText, redactPayload, redactPayloadJson, safeUrlString.
Constants
Metrics— canonical metric strings (all business-type template signals).RawEvents— dotted raw vendor names the gateway maps toMetrics.PayloadFields— payload keys read bysum_payload/avg_payload(value,side,quantity).AccountAttributes— account fact keys (direct_deposit,kyc_completed,push_opt_in,installed_at).BusinessTypes— dashboard-template keys (ecommerce,fintech,subscription_box,mobile,marketplace_buyer, …).
All mirror sdks/signals.manifest.json; test/parity.test.js (run npm test) asserts they stay in sync.
