@traceten/sdk-node 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +312 -0
- package/dist/client.d.ts +107 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +320 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/queue.d.ts +72 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +232 -0
- package/dist/queue.js.map +1 -0
- package/dist/types.d.ts +212 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +99 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +323 -0
- package/dist/validate.js.map +1 -0
- package/dist/webhook.d.ts +203 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +141 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +72 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - Unreleased
|
|
9
|
+
|
|
10
|
+
First stable release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `Client` with `page()`, `track()` and `payment()`, buffered delivery with batching and retry.
|
|
15
|
+
- `flush()` / `close()` for explicit and shutdown-time delivery.
|
|
16
|
+
- Webhook signature-verification helpers.
|
|
17
|
+
- Required `apiKey`, sent as `Authorization: Bearer` on every request. It buys an
|
|
18
|
+
ingestion quota isolated from the shared per-site one.
|
|
19
|
+
- `host` must use `https` off-loopback: the key rides every request, so plaintext
|
|
20
|
+
http would put a live credential on the wire.
|
|
21
|
+
- `payment()` accepts optional `settlementAmount`/`settlementCurrency` — the
|
|
22
|
+
provider's own conversion, used by Traceten only as a fallback when `currency`
|
|
23
|
+
isn't one it can price on its own. Send both or neither.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Traceten
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<img src="https://traceten.com/logos/traceten-wordmark-black.png" alt="Traceten" width="320" />
|
|
2
|
+
|
|
3
|
+
# @traceten/sdk-node
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@traceten/sdk-node)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
|
|
9
|
+
The Traceten server-side SDK for Node.js. Send AI-traffic pageviews and revenue
|
|
10
|
+
events from your backend over authenticated, ad-blocker-resistant HTTP.
|
|
11
|
+
|
|
12
|
+
Use this when the browser snippet cannot run: server-rendered flows, webhooks,
|
|
13
|
+
mobile/API backends, or when you want delivery that ad-blockers and privacy
|
|
14
|
+
browsers cannot strip.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @traceten/sdk-node
|
|
20
|
+
# or: pnpm add @traceten/sdk-node
|
|
21
|
+
# or: yarn add @traceten/sdk-node
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Node 18 or newer. The only runtime dependency is `undici`.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { Client } from "@traceten/sdk-node";
|
|
30
|
+
|
|
31
|
+
const traceten = new Client({
|
|
32
|
+
siteId: "ttid_7Rb4TrC1dTbnD8w3s1TS12", // your site key, from the dashboard's install page
|
|
33
|
+
host: "https://ingest.traceten.com",
|
|
34
|
+
// Required. A secret: load it from your environment, never hardcode it.
|
|
35
|
+
apiKey: process.env.TRACETEN_API_KEY!,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// A pageview. visitorId comes from your own request context (a cookie you set,
|
|
39
|
+
// a user id, etc.) — the server has no Traceten cookie to read.
|
|
40
|
+
traceten.page({
|
|
41
|
+
url: "https://acme.com/pricing",
|
|
42
|
+
referrer: "https://chat.openai.com/",
|
|
43
|
+
visitorId: "123e4567-e89b-42d3-a456-426614174000",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// A conversion. visitorId is required so the revenue can be attributed.
|
|
47
|
+
traceten.track("subscription_started", {
|
|
48
|
+
visitorId: "123e4567-e89b-42d3-a456-426614174000",
|
|
49
|
+
valueCents: 4900,
|
|
50
|
+
currency: "usd",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Flush and stop the background timer on shutdown.
|
|
54
|
+
await traceten.close();
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`page()` and `track()` return immediately. They buffer the event and send it in
|
|
58
|
+
the background. They never throw on network problems, they throw only on
|
|
59
|
+
programmer errors (a bad URL, a missing `visitorId`, a malformed event name).
|
|
60
|
+
|
|
61
|
+
## The API key
|
|
62
|
+
|
|
63
|
+
`apiKey` is required. Create a key in the dashboard under **Settings -> API
|
|
64
|
+
keys**, or use the key shown once when you created the site.
|
|
65
|
+
|
|
66
|
+
Keep it on your server. It is a secret: never put it in client-side code, a
|
|
67
|
+
mobile app, or a public repository. It is not the same value as `siteId`, which
|
|
68
|
+
is public and already embedded in your pages.
|
|
69
|
+
|
|
70
|
+
The key does two things:
|
|
71
|
+
|
|
72
|
+
- **Gets you in.** The SDK posts to the authenticated ingestion endpoints, which
|
|
73
|
+
return `401` without a valid key.
|
|
74
|
+
- **Gets you your own quota.** Authenticated traffic is rate-limited on a bucket
|
|
75
|
+
tied to the key, separate from the shared per-site bucket. `siteId` is public,
|
|
76
|
+
so anyone who can read your page source can send events under it. With a key,
|
|
77
|
+
that traffic cannot exhaust your allowance and 429 your conversion calls.
|
|
78
|
+
|
|
79
|
+
The client validates the key's shape when you construct it and throws if it is
|
|
80
|
+
missing or malformed. An integration that silently drops every event is worse
|
|
81
|
+
than one that fails on its first line.
|
|
82
|
+
|
|
83
|
+
### Permissions
|
|
84
|
+
|
|
85
|
+
A key carries a set of permissions that decide which endpoints it can reach.
|
|
86
|
+
This SDK sends to `/v1/server/*`, which requires **`ingest:write`**. Tick that
|
|
87
|
+
permission when you create the key.
|
|
88
|
+
|
|
89
|
+
The client cannot check this for you. Permissions live on the server and the key
|
|
90
|
+
looks identical either way, so a key without `ingest:write` is rejected with the
|
|
91
|
+
same `401` as an invalid one.
|
|
92
|
+
|
|
93
|
+
Grant only what you need. A key used solely for server-side ingestion does not
|
|
94
|
+
need permission to read your analytics or erase visitor data, and if it leaks it
|
|
95
|
+
cannot do either.
|
|
96
|
+
|
|
97
|
+
To rotate a key: create the new one, deploy it, then revoke the old one.
|
|
98
|
+
Revocation normally takes effect within about a minute. If our database is
|
|
99
|
+
unreachable at that moment, an edge location that was already using the key may
|
|
100
|
+
keep honouring it for up to about fifteen minutes more, so that a database blip
|
|
101
|
+
cannot silently drop your events.
|
|
102
|
+
|
|
103
|
+
## Identifiers
|
|
104
|
+
|
|
105
|
+
Because it runs on your server, the SDK has no cookie and no DOM. You supply
|
|
106
|
+
`visitorId` (and optionally `sessionId`) from your own request context. A
|
|
107
|
+
`visitorId` is either a UUID or an `h:<64-hex>` identify hash. The SDK never
|
|
108
|
+
fabricates one.
|
|
109
|
+
|
|
110
|
+
The robust way to get this value is `window.traceten.getVisitorId()`, called
|
|
111
|
+
client-side and forwarded to your backend (a form field, a fetch body, a
|
|
112
|
+
header) — it always resolves the current cookie, so it keeps working if a
|
|
113
|
+
customer turns cross-subdomain cookies on or off later. If you read the
|
|
114
|
+
cookie by name instead, its name depends on the site's cookie scope:
|
|
115
|
+
cross-subdomain cookies are off by default, giving plain `_traceten_vid`;
|
|
116
|
+
once a customer enables it, the cookie becomes `_traceten_vid_` followed by
|
|
117
|
+
eight characters of the site key. The install page shows the exact current
|
|
118
|
+
name. Read that name exactly, never by prefix: two Traceten sites under one
|
|
119
|
+
registered domain each set their own cookie, and a prefix match picks
|
|
120
|
+
whichever the browser happens to list first, which merges two visitors the
|
|
121
|
+
suffix exists to keep apart. If a request carries no such value, send the
|
|
122
|
+
event without a `visitorId` rather than inventing one.
|
|
123
|
+
|
|
124
|
+
## API
|
|
125
|
+
|
|
126
|
+
### `new Client(options)`
|
|
127
|
+
|
|
128
|
+
| Option | Type | Default | Notes |
|
|
129
|
+
| --------------- | ----------------------- | ---------- | ------------------------------------------------------------ |
|
|
130
|
+
| `siteId` | `string` | (required) | Your site key (`ttid_…`, case-sensitive) from the dashboard's install page. 1-64 chars, `A-Z a-z 0-9 _ -`. |
|
|
131
|
+
| `host` | `string` | (required) | Ingest base URL. Absolute `https` (http on loopback only). |
|
|
132
|
+
| `apiKey` | `string` | (required) | Secret key, sent as `Authorization: Bearer`. See below. |
|
|
133
|
+
| `flushAt` | `number` | `50` | Flush the events queue at this size. Clamped to `[1, 50]`. |
|
|
134
|
+
| `flushInterval` | `number` | `5000` | Background flush cadence in ms. |
|
|
135
|
+
| `maxRetries` | `number` | `3` | Retry attempts on 5xx / 429 / network error. |
|
|
136
|
+
| `timeoutMs` | `number` | `10000` | Per-request timeout in ms. |
|
|
137
|
+
| `onError` | `(err: Error) => void` | - | Called when a batch is permanently dropped. |
|
|
138
|
+
| `flushOnExit` | `boolean` | `true` | Register best-effort flush on `beforeExit`/`SIGTERM`/`SIGINT`.|
|
|
139
|
+
|
|
140
|
+
### `page(props)`
|
|
141
|
+
|
|
142
|
+
| Prop | Type | Default | Notes |
|
|
143
|
+
| ----------- | ---------------- | ------------ | ---------------------------------------- |
|
|
144
|
+
| `url` | `string` | (required) | Valid URL, up to 2048 chars. |
|
|
145
|
+
| `referrer` | `string` | `""` | |
|
|
146
|
+
| `visitorId` | `string` | - | UUID or `h:<hash>`. |
|
|
147
|
+
| `sessionId` | `string` | - | |
|
|
148
|
+
| `userAgent` | `string` | - | The end user's User-Agent, if you know it.|
|
|
149
|
+
| `eventName` | `string` | `"pageview"` | Must match `^[a-z][a-z0-9_-]*$`. |
|
|
150
|
+
| `timestamp` | `Date \| string` | now | |
|
|
151
|
+
|
|
152
|
+
The `userAgent` you pass is what gets recorded as the visitor's user agent.
|
|
153
|
+
Omit it and the event falls back to the `User-Agent` this SDK's HTTP client
|
|
154
|
+
sent, which describes your server, not the visitor. User agent is an input to
|
|
155
|
+
Traceten's traffic classification, so passing the real one materially improves
|
|
156
|
+
your results.
|
|
157
|
+
|
|
158
|
+
### `track(name, opts)`
|
|
159
|
+
|
|
160
|
+
`name` must match `^[a-z][a-z0-9_]*$` (no hyphen).
|
|
161
|
+
|
|
162
|
+
| Option | Type | Default | Notes |
|
|
163
|
+
| ------------ | ---------------- | ---------- | ------------------------------------------------- |
|
|
164
|
+
| `visitorId` | `string` | (required) | UUID or `h:<hash>`. Required to attribute revenue.|
|
|
165
|
+
| `properties` | `object` | `{}` | Arbitrary JSON. |
|
|
166
|
+
| `valueCents` | `number` | - | Non-negative integer, minor units. -> `value_cents`.|
|
|
167
|
+
| `currency` | `string` | - | ISO-4217, lowercased on send. |
|
|
168
|
+
| `sessionId` | `string` | - | |
|
|
169
|
+
| `timestamp` | `Date \| string` | now | |
|
|
170
|
+
|
|
171
|
+
### `goal(name, opts)`
|
|
172
|
+
|
|
173
|
+
Records a goal completion. Same arguments and same endpoint as `track()`, with
|
|
174
|
+
one difference: the reserved names below are rejected.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
traceten.goal("demo_booked", {
|
|
178
|
+
visitorId: "123e4567-e89b-42d3-a456-426614174000",
|
|
179
|
+
properties: { plan: "pro" },
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
A goal is a custom event. `goal("demo_booked", ...)` and
|
|
184
|
+
`track("demo_booked", ...)` send exactly the same payload, and the goal is
|
|
185
|
+
listed by `GET /v1/goals` either way. Use `goal()` when the event is something
|
|
186
|
+
you want to count and put in a funnel, and `track()` when you are recording
|
|
187
|
+
revenue.
|
|
188
|
+
|
|
189
|
+
**Reserved names.** These belong to the Stripe and Shopify integrations, which
|
|
190
|
+
emit them for real subscription and payment events. `goal()` throws on them, so
|
|
191
|
+
a name collision cannot quietly corrupt your revenue funnel:
|
|
192
|
+
|
|
193
|
+
`payment`, `free_trial`, `trial_started`, `trial_converted`,
|
|
194
|
+
`subscription_started`, `subscription_upgraded`, `subscription_downgraded`,
|
|
195
|
+
`subscription_renewed`, `subscription_cancel_scheduled`,
|
|
196
|
+
`subscription_reactivated`, `subscription_ended`.
|
|
197
|
+
|
|
198
|
+
`track()` still accepts them, because that is how those events are legitimately
|
|
199
|
+
sent.
|
|
200
|
+
|
|
201
|
+
**Whitespace is not trimmed.** `goal(" signup ")` throws. The browser snippet
|
|
202
|
+
trims a name read from an HTML attribute, because attribute values pick up
|
|
203
|
+
whitespace from how the page is formatted; a name written in server code does
|
|
204
|
+
not, so a stray space is a bug worth surfacing rather than quietly fixing.
|
|
205
|
+
|
|
206
|
+
**Property keys and values are both stored, and both are readable back.**
|
|
207
|
+
`GET /v1/goals/{name}/properties` returns every property key sent with a goal
|
|
208
|
+
and that key's most common values. Do not put an email address, a person's name,
|
|
209
|
+
or a postal address in either half of a property.
|
|
210
|
+
|
|
211
|
+
Ingestion drops a property whose key is exactly `email`, `phone`, `name`,
|
|
212
|
+
`password`, `token`, `ssn`, `credit_card` or `card_number`, and redacts email,
|
|
213
|
+
phone, card and national-ID patterns inside string values. It has no pattern
|
|
214
|
+
for a personal name or a street address, and it does not scan keys at all, so
|
|
215
|
+
`{"full_name": "Alice Chen"}` is stored and returned exactly as sent.
|
|
216
|
+
|
|
217
|
+
### `payment(props): Promise<PaymentResult>`
|
|
218
|
+
|
|
219
|
+
Records a payment from ANY payment processor (`POST /v1/server/payments`). The
|
|
220
|
+
only method here that sends immediately and reports a delivery failure to the
|
|
221
|
+
caller: a dropped pageview is a dropped pageview, a dropped payment is missing
|
|
222
|
+
revenue.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
const res = await traceten.payment({
|
|
226
|
+
transactionId: "pay_9fK2mQ", // required — the processor's id. Idempotency key.
|
|
227
|
+
amount: 49.99, // required — MAJOR unit, not cents
|
|
228
|
+
currency: "USD", // required — ISO-4217, sent uppercase
|
|
229
|
+
provider: "dodo", // optional — your label. Defaults to "api".
|
|
230
|
+
email: "ada@example.com", // optional — hashed server-side, never stored
|
|
231
|
+
visitorId, // optional — a stronger match than the email
|
|
232
|
+
customerId: "cus_123", // optional
|
|
233
|
+
renewal: false, // optional
|
|
234
|
+
refunded: false, // optional — never send a negative amount
|
|
235
|
+
isFreeTrial: false, // optional — implied by amount: 0
|
|
236
|
+
settlementAmount: 45.50, // optional — your processor's own converted figure
|
|
237
|
+
settlementCurrency: "USD", // optional — must be set together with settlementAmount
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
res.status; // "recorded" | "trial" | "refunded" | "duplicate"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`amount` is the MAJOR unit, the opposite of `track()`'s `valueCents`: `49.99`
|
|
244
|
+
for $49.99, `5000` for ¥5000.
|
|
245
|
+
|
|
246
|
+
`settlementAmount`/`settlementCurrency` are a fallback for when `currency`
|
|
247
|
+
isn't one Traceten can price natively (the ~30 ECB-published codes): your
|
|
248
|
+
processor's own conversion of the payment into a currency it can price, so
|
|
249
|
+
the payment isn't dropped. Set both or neither — sending one without the
|
|
250
|
+
other causes the SDK to omit both.
|
|
251
|
+
|
|
252
|
+
Re-posting the same `transactionId` returns `"duplicate"` and creates nothing,
|
|
253
|
+
which is why a 5xx is retried here. It rejects on an invalid field and after
|
|
254
|
+
every retry is exhausted.
|
|
255
|
+
|
|
256
|
+
⚠️ Do NOT send payments here for a processor you have also connected natively.
|
|
257
|
+
Traceten would record the payment twice and overstate your revenue.
|
|
258
|
+
|
|
259
|
+
### `flush(): Promise<void>`
|
|
260
|
+
|
|
261
|
+
Sends everything queued now. Resolves when every in-flight request settles.
|
|
262
|
+
|
|
263
|
+
### `close(): Promise<void>` / `shutdown(): Promise<void>`
|
|
264
|
+
|
|
265
|
+
Flushes, stops the background timer, and removes the exit hooks. Idempotent.
|
|
266
|
+
After `close()`, `page()`, `track()` and `goal()` throw.
|
|
267
|
+
|
|
268
|
+
## Delivery and retries
|
|
269
|
+
|
|
270
|
+
- Pageviews go to `POST {host}/v1/server/events` in batch envelopes of up to 50.
|
|
271
|
+
- Conversions go to `POST {host}/v1/server/conversions`, one body per event.
|
|
272
|
+
- Every request carries `Authorization: Bearer <apiKey>`. Without a valid key
|
|
273
|
+
these endpoints return `401`.
|
|
274
|
+
- On 5xx, 429, or a network/timeout error, the batch is retried with
|
|
275
|
+
exponential backoff and full jitter, up to `maxRetries`.
|
|
276
|
+
- On any other 4xx the payload is malformed, so it is dropped and `onError` is
|
|
277
|
+
called. It is not retried.
|
|
278
|
+
|
|
279
|
+
## Full docs
|
|
280
|
+
|
|
281
|
+
See [API.md](./API.md) for the complete guide, or [docs.traceten.com/sdks/node](https://docs.traceten.com/sdks/node) for the hosted version.
|
|
282
|
+
|
|
283
|
+
## Versioning
|
|
284
|
+
|
|
285
|
+
This package follows [Semantic Versioning](https://semver.org/). Before `1.0.0`,
|
|
286
|
+
minor versions may include breaking changes — pin an exact version in production
|
|
287
|
+
until then. See [CHANGELOG.md](./CHANGELOG.md) for release history.
|
|
288
|
+
|
|
289
|
+
## Contributing
|
|
290
|
+
|
|
291
|
+
Issues and pull requests are welcome. For anything beyond a small fix, please
|
|
292
|
+
open an issue first to discuss the change. Run the checks below before
|
|
293
|
+
submitting a PR — CI enforces the same steps on every pull request:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm install
|
|
297
|
+
npm run typecheck
|
|
298
|
+
npm run build
|
|
299
|
+
npm test
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
MIT © Traceten — see [LICENSE](./LICENSE).
|
|
305
|
+
|
|
306
|
+
## Links
|
|
307
|
+
|
|
308
|
+
- [Documentation](https://docs.traceten.com/sdks/node)
|
|
309
|
+
- [Traceten](https://traceten.com) — AI traffic attribution for the AI search era
|
|
310
|
+
- [Issues](https://github.com/traceten/sdk-node/issues)
|
|
311
|
+
- [Changelog](./CHANGELOG.md)
|
|
312
|
+
- [Python SDK](https://github.com/traceten/sdk-python) · [Go SDK](https://github.com/traceten/traceten-go) · [AI crawler tracking](https://github.com/traceten/ai-crawl)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The buffered Traceten client.
|
|
3
|
+
*
|
|
4
|
+
* `page()` and `track()` split at the API surface (two endpoints, two payload
|
|
5
|
+
* shapes) but unify in one buffered client with a single flush that drains both
|
|
6
|
+
* internal queues. See DESIGN.md §3–§4.
|
|
7
|
+
*/
|
|
8
|
+
import type { ClientOptions, PageProps, PaymentProps, PaymentResult, TrackOptions } from "./types.js";
|
|
9
|
+
export declare class Client {
|
|
10
|
+
private readonly siteId;
|
|
11
|
+
private readonly host;
|
|
12
|
+
private readonly flushAt;
|
|
13
|
+
private readonly transport;
|
|
14
|
+
private readonly onError;
|
|
15
|
+
private events;
|
|
16
|
+
private conversions;
|
|
17
|
+
private timer;
|
|
18
|
+
private readonly inflight;
|
|
19
|
+
private exitHooks;
|
|
20
|
+
private closed;
|
|
21
|
+
constructor(options: ClientOptions);
|
|
22
|
+
/**
|
|
23
|
+
* Enqueue a pageview (`/v1/server/events`). Returns immediately; never throws on
|
|
24
|
+
* network problems. Throws synchronously on programmer errors (bad url,
|
|
25
|
+
* bad eventName, bad visitorId).
|
|
26
|
+
*/
|
|
27
|
+
page(props: PageProps): void;
|
|
28
|
+
/**
|
|
29
|
+
* Enqueue a conversion (`/v1/server/conversions`). `opts.visitorId` is REQUIRED —
|
|
30
|
+
* omitting it is a programmer error and throws synchronously (a conversion
|
|
31
|
+
* cannot be attributed without it). Returns immediately otherwise.
|
|
32
|
+
*/
|
|
33
|
+
track(name: string, opts: TrackOptions): void;
|
|
34
|
+
/**
|
|
35
|
+
* Enqueue a goal completion. Identical to {@link Client.track} in every
|
|
36
|
+
* respect except the name check: the reserved Stripe/Shopify names are
|
|
37
|
+
* refused, because a goal that collides with one silently corrupts the site's
|
|
38
|
+
* revenue funnel.
|
|
39
|
+
*
|
|
40
|
+
* A goal IS a custom event — this is the same `/v1/server/conversions` call
|
|
41
|
+
* under a name that matches what the dashboard calls it.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* traceten.goal("demo_booked", { visitorId, properties: { plan: "pro" } });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
goal(name: string, opts: TrackOptions): void;
|
|
49
|
+
/**
|
|
50
|
+
* Record a payment from ANY payment processor (`POST /v1/server/payments`).
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ The only method here that is AWAITED and that can reject. `page()` and
|
|
53
|
+
* `track()` are buffered and fire-and-forget because a lost pageview is a lost
|
|
54
|
+
* pageview; a lost payment is missing revenue, and the endpoint's whole point
|
|
55
|
+
* is the idempotency answer it returns — which a queue could not give back.
|
|
56
|
+
*
|
|
57
|
+
* Safe to call again with the same `transactionId`: the server deduplicates
|
|
58
|
+
* durably on `(site, provider, transactionId)` and answers `"duplicate"`
|
|
59
|
+
* rather than recording a second payment.
|
|
60
|
+
*
|
|
61
|
+
* `settlementAmount`/`settlementCurrency` are an optional fallback: your
|
|
62
|
+
* processor's own conversion of the payment into a currency Traceten can
|
|
63
|
+
* price, used only when `currency` itself isn't one Traceten prices
|
|
64
|
+
* natively. Set both or neither — one without the other is dropped.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const res = await traceten.payment({
|
|
69
|
+
* transactionId: charge.id,
|
|
70
|
+
* amount: 49.99,
|
|
71
|
+
* currency: "USD",
|
|
72
|
+
* provider: "dodo",
|
|
73
|
+
* email: customer.email,
|
|
74
|
+
* });
|
|
75
|
+
* // res.status === "recorded"
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
payment(props: PaymentProps): Promise<PaymentResult>;
|
|
79
|
+
/**
|
|
80
|
+
* Drain both queues now. Resolves once every in-flight request settles.
|
|
81
|
+
* Never rejects — delivery failures surface via `onError`.
|
|
82
|
+
*/
|
|
83
|
+
flush(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Flush, stop the background timer, and remove exit hooks. Idempotent — safe
|
|
86
|
+
* to call more than once. After close, `page()`/`track()` throw.
|
|
87
|
+
*/
|
|
88
|
+
close(): Promise<void>;
|
|
89
|
+
/** Alias for {@link Client.close} — matches the cross-language `shutdown()`. */
|
|
90
|
+
shutdown(): Promise<void>;
|
|
91
|
+
private assertOpen;
|
|
92
|
+
/** Trigger a flush when either queue has reached the flush threshold. */
|
|
93
|
+
private maybeFlush;
|
|
94
|
+
/** Track an in-flight send so `close()` can await it, then self-remove. */
|
|
95
|
+
private launch;
|
|
96
|
+
/**
|
|
97
|
+
* Register best-effort flush-on-exit hooks.
|
|
98
|
+
*
|
|
99
|
+
* `beforeExit` fires when the loop would otherwise empty — we flush and let
|
|
100
|
+
* the pending I/O keep the process alive until it settles. For `SIGTERM` /
|
|
101
|
+
* `SIGINT` we flush, then re-exit ONLY if we were the sole handler (otherwise
|
|
102
|
+
* the host app owns the shutdown decision and forcing `process.exit` would
|
|
103
|
+
* stomp its own handlers / exit code).
|
|
104
|
+
*/
|
|
105
|
+
private registerExitHooks;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAwBH,OAAO,KAAK,EACV,aAAa,EAIb,SAAS,EACT,YAAY,EACZ,aAAa,EAEb,YAAY,EACb,MAAM,YAAY,CAAC;AA8BpB,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAElC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAwB;IAE3C,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,aAAa;IAqDlC;;;;OAIG;IACH,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IA0B5B;;;;OAIG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI;IAoC7C;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI;IAI5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IA8B1D;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB5B,gFAAgF;IAChF,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAMzB,OAAO,CAAC,UAAU;IAMlB,yEAAyE;IACzE,OAAO,CAAC,UAAU;IAMlB,2EAA2E;IAC3E,OAAO,CAAC,MAAM;IAQd;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;CAoB1B"}
|