@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/dist/queue.js ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Transport layer for the Traceten Node SDK.
3
+ *
4
+ * Owns the "one HTTP request, with retry" primitive that both queues share.
5
+ * Batching/chunking lives in the {@link Client}; this module only knows how to
6
+ * deliver a single already-shaped body to a single endpoint, retrying on
7
+ * transient failures per DESIGN.md §4.
8
+ *
9
+ * Retry policy:
10
+ * - 2xx → success.
11
+ * - 5xx or 429 or network/timeout → retryable; exponential backoff with full
12
+ * jitter (`base 200ms * 2^attempt`), up to
13
+ * `maxRetries` retries.
14
+ * - other 4xx → the payload is malformed; DROP it and call
15
+ * `onError`. Retrying a 400 forever is a
16
+ * self-DoS.
17
+ */
18
+ import { request } from "undici";
19
+ /** Base backoff in ms — `delay ∈ [0, BASE * 2^attempt)` (full jitter). */
20
+ const BASE_BACKOFF_MS = 200;
21
+ /** Split `items` into contiguous chunks of at most `size`. */
22
+ export function chunk(items, size) {
23
+ const out = [];
24
+ for (let i = 0; i < items.length; i += size) {
25
+ out.push(items.slice(i, i + size));
26
+ }
27
+ return out;
28
+ }
29
+ /** Resolve after `ms` milliseconds. */
30
+ function sleep(ms) {
31
+ return new Promise((resolve) => {
32
+ const t = setTimeout(resolve, ms);
33
+ // Never let a pending backoff keep the process alive on its own.
34
+ if (typeof t === "object" && typeof t.unref === "function") {
35
+ t.unref();
36
+ }
37
+ });
38
+ }
39
+ /** Full-jitter exponential backoff for retry attempt `n` (0-indexed). */
40
+ function backoffDelay(attempt) {
41
+ // Cap the exponent so a caller-supplied large maxRetries can't overflow into
42
+ // an absurd multi-hour sleep (mirrors the Go SDK's guard).
43
+ const exp = Math.min(attempt, 20);
44
+ return Math.random() * (BASE_BACKOFF_MS * 2 ** exp);
45
+ }
46
+ export class Transport {
47
+ eventsUrl;
48
+ conversionsUrl;
49
+ paymentsUrl;
50
+ headers;
51
+ maxRetries;
52
+ timeoutMs;
53
+ onError;
54
+ constructor(opts) {
55
+ // The AUTHENTICATED ingestion endpoints. `/v1/events` and
56
+ // `/v1/conversions` are still open — the browser snippet shares them and
57
+ // structurally cannot hold a secret — but a server SDK can, so it uses the
58
+ // door where the key is mandatory and gets its own rate-limit quota,
59
+ // isolated from the public per-site one.
60
+ this.eventsUrl = `${opts.host}/v1/server/events`;
61
+ this.conversionsUrl = `${opts.host}/v1/server/conversions`;
62
+ this.paymentsUrl = `${opts.host}/v1/server/payments`;
63
+ this.maxRetries = opts.maxRetries;
64
+ this.timeoutMs = opts.timeoutMs;
65
+ this.onError = opts.onError;
66
+ // Always sent. The Client validates the key's shape in its constructor, so
67
+ // an empty value can never reach here.
68
+ this.headers = {
69
+ "content-type": "application/json",
70
+ authorization: `Bearer ${opts.apiKey}`,
71
+ };
72
+ }
73
+ /** Send one `/v1/server/events` batch envelope. Never throws. */
74
+ async sendEvents(events) {
75
+ if (events.length === 0)
76
+ return;
77
+ await this.deliver(this.eventsUrl, JSON.stringify({ events }));
78
+ }
79
+ /** Send one `/v1/server/conversions` body (one event per request). Never throws. */
80
+ async sendConversion(conversion) {
81
+ await this.deliver(this.conversionsUrl, JSON.stringify(conversion));
82
+ }
83
+ /**
84
+ * Send one `/v1/server/payments` body and RETURN the outcome (#794).
85
+ *
86
+ * Unlike the two above, this one is awaited and its result surfaced: the
87
+ * endpoint's whole contract is the idempotency answer, so swallowing it would
88
+ * discard the one thing the caller asked for.
89
+ *
90
+ * Retrying is safe for the same reason — the server deduplicates on
91
+ * `(site, provider, transaction_id)` durably, so a retried 5xx cannot become a
92
+ * second payment.
93
+ *
94
+ * @throws when the payment was NOT accepted after every retry. `payment()` is
95
+ * the only SDK call that rejects, because a silently dropped payment is
96
+ * missing revenue rather than a missing pageview.
97
+ */
98
+ async sendPayment(payment) {
99
+ const body = JSON.stringify(payment);
100
+ const maxAttempts = this.maxRetries + 1;
101
+ let lastError = new Error("Traceten: payment was not sent");
102
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
103
+ const outcome = await this.attemptJson(this.paymentsUrl, body);
104
+ if (outcome.kind === "ok") {
105
+ return {
106
+ status: outcome.status,
107
+ transactionId: payment.transaction_id,
108
+ };
109
+ }
110
+ lastError = outcome.error;
111
+ if (outcome.kind === "drop")
112
+ break;
113
+ if (attempt < maxAttempts - 1)
114
+ await sleep(backoffDelay(attempt));
115
+ }
116
+ this.reportError(lastError);
117
+ throw lastError;
118
+ }
119
+ /** One attempt that also reads the JSON body, for the payments endpoint. */
120
+ async attemptJson(url, body) {
121
+ try {
122
+ const res = await request(url, {
123
+ method: "POST",
124
+ headers: this.headers,
125
+ body,
126
+ headersTimeout: this.timeoutMs,
127
+ bodyTimeout: this.timeoutMs,
128
+ });
129
+ const status = res.statusCode;
130
+ if (status >= 200 && status < 300) {
131
+ const parsed = (await res.body.json());
132
+ const known = ["recorded", "trial", "refunded", "duplicate"];
133
+ // An unrecognised status means the server is ahead of this SDK. Reported
134
+ // as `recorded` rather than thrown: the payment WAS accepted, and
135
+ // failing here would make the caller retry something already stored.
136
+ return {
137
+ kind: "ok",
138
+ status: (known.includes(parsed.status ?? "")
139
+ ? parsed.status
140
+ : "recorded"),
141
+ };
142
+ }
143
+ await res.body.dump();
144
+ if (status === 429 || status >= 500) {
145
+ return { kind: "retry", error: new Error(`Traceten payments responded ${status}`) };
146
+ }
147
+ return {
148
+ kind: "drop",
149
+ error: new Error(status === 401
150
+ ? "Traceten payments rejected the request: the apiKey was not accepted " +
151
+ "(missing, revoked, expired, or scoped to a different site)."
152
+ : `Traceten payments rejected the payload with status ${status}`),
153
+ };
154
+ }
155
+ catch (err) {
156
+ return { kind: "retry", error: err instanceof Error ? err : new Error(String(err)) };
157
+ }
158
+ }
159
+ /**
160
+ * POST `body` to `url`, retrying transient failures. Resolves once the
161
+ * request either succeeds or is permanently given up on (after calling
162
+ * `onError`). Deliberately never rejects — `page()`/`track()`/`flush()` must
163
+ * not throw on network problems.
164
+ */
165
+ async deliver(url, body) {
166
+ const maxAttempts = this.maxRetries + 1;
167
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
168
+ const outcome = await this.attempt(url, body);
169
+ if (outcome.kind === "ok")
170
+ return;
171
+ if (outcome.kind === "drop") {
172
+ this.reportError(outcome.error);
173
+ return;
174
+ }
175
+ // retryable — back off unless this was the final attempt.
176
+ if (attempt < maxAttempts - 1) {
177
+ await sleep(backoffDelay(attempt));
178
+ continue;
179
+ }
180
+ this.reportError(outcome.error);
181
+ return;
182
+ }
183
+ }
184
+ /** A single HTTP attempt, classified into an {@link Outcome}. */
185
+ async attempt(url, body) {
186
+ try {
187
+ const res = await request(url, {
188
+ method: "POST",
189
+ headers: this.headers,
190
+ body,
191
+ headersTimeout: this.timeoutMs,
192
+ bodyTimeout: this.timeoutMs,
193
+ });
194
+ const status = res.statusCode;
195
+ // Always drain the body so the socket can be released back to the pool.
196
+ await res.body.dump();
197
+ if (status >= 200 && status < 300) {
198
+ return { kind: "ok" };
199
+ }
200
+ if (status === 429 || status >= 500) {
201
+ return { kind: "retry", error: new Error(`Traceten ingest responded ${status}`) };
202
+ }
203
+ // 401 gets its own message. It is now by far the most likely 4xx — the
204
+ // key is mandatory — and "rejected the payload" points the reader at
205
+ // their event shape, which is exactly the wrong place to look when the
206
+ // real cause is a missing, revoked, expired, or wrong-site key.
207
+ return {
208
+ kind: "drop",
209
+ error: new Error(status === 401
210
+ ? "Traceten ingest rejected the request: the apiKey was not accepted " +
211
+ "(missing, revoked, expired, or scoped to a different site). " +
212
+ "Check Settings -> API keys in the dashboard."
213
+ : `Traceten ingest rejected the payload with status ${status}`),
214
+ };
215
+ }
216
+ catch (err) {
217
+ // Network error / DNS / timeout — all retryable.
218
+ const error = err instanceof Error ? err : new Error(String(err));
219
+ return { kind: "retry", error };
220
+ }
221
+ }
222
+ /** Invoke the user `onError` hook, swallowing any error it throws. */
223
+ reportError(error) {
224
+ try {
225
+ this.onError(error);
226
+ }
227
+ catch {
228
+ // An observability hook must never take down delivery.
229
+ }
230
+ }
231
+ }
232
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Public and wire-format types for the Traceten Node SDK.
3
+ *
4
+ * The two wire types (`EventWire`, `ConversionWire`) mirror the exact shapes
5
+ * validated by the ingestion Worker (`apps/workers/src/index.ts` —
6
+ * `EventPayloadSchema`, `ConversionPayloadSchema`). The events payload is
7
+ * camelCase; the conversions payload is snake_case. Do not "fix" the casing —
8
+ * it is the contract.
9
+ *
10
+ * The SDK posts to `/v1/server/events` and `/v1/server/conversions`: the same
11
+ * schemas and the same Kafka records as the open `/v1/events` and
12
+ * `/v1/conversions` routes the browser snippet uses, behind a mandatory
13
+ * `Authorization: Bearer` and an isolated rate-limit bucket (#725).
14
+ */
15
+ /** Observability hook invoked when a payload is permanently dropped or errors. */
16
+ export type OnError = (err: Error) => void;
17
+ /**
18
+ * Constructor options for {@link Client}.
19
+ *
20
+ * `siteId`, `host` and `apiKey` are required; everything else has a documented
21
+ * default.
22
+ * See `DESIGN.md` §3.1 for the canonical, cross-language contract.
23
+ */
24
+ export interface ClientOptions {
25
+ /**
26
+ * The site's PUBLIC site id (`snippet_key`) — the same value the browser
27
+ * snippet embeds. 1–64 chars, `^[A-Za-z0-9_-]+$`. It is an identifier, not a
28
+ * credential; {@link ClientOptions.apiKey} is the credential.
29
+ */
30
+ siteId: string;
31
+ /**
32
+ * Ingest base URL, e.g. `https://ingest.traceten.com`. Must be an absolute
33
+ * http(s) URL. A trailing slash is stripped. There is no hardcoded default —
34
+ * every deployment has its own Worker URL.
35
+ */
36
+ host: string;
37
+ /**
38
+ * REQUIRED. Sent as `Authorization: Bearer <apiKey>`.
39
+ *
40
+ * A secret — keep it server-side, never in client code or a public repo.
41
+ * Create one in the dashboard under Settings -> API keys, or take the key
42
+ * returned once when the site was created.
43
+ *
44
+ * The SDK posts to the authenticated endpoints (`/v1/server/events`,
45
+ * `/v1/server/conversions`), which return 401 without a valid key. In
46
+ * exchange, the traffic gets its own rate-limit quota, isolated from the
47
+ * shared per-site one that anyone holding the public `siteId` can consume.
48
+ *
49
+ * Validated for shape in the constructor, which throws — a missing key would
50
+ * otherwise produce an integration that buffers, flushes, 401s every batch,
51
+ * and looks healthy.
52
+ */
53
+ apiKey: string;
54
+ /**
55
+ * Flush the events queue once it reaches this many buffered events. Also the
56
+ * hard chunk cap for `/v1/server/events` batches. Clamped to `[1, 50]`. Default 50.
57
+ */
58
+ flushAt?: number;
59
+ /** Background flush cadence in milliseconds. Default 5000. */
60
+ flushInterval?: number;
61
+ /** Per-batch retry attempts on 5xx / 429 / network error. Default 3. */
62
+ maxRetries?: number;
63
+ /** Per-request timeout in milliseconds. Default 10000. */
64
+ timeoutMs?: number;
65
+ /** Called when a batch is permanently dropped. The SDK never throws here. */
66
+ onError?: OnError;
67
+ /**
68
+ * Register best-effort flush-on-exit hooks (`beforeExit`, `SIGTERM`,
69
+ * `SIGINT`). Default `true`. Set `false` in short-lived scripts or when the
70
+ * host process owns its own signal handling.
71
+ */
72
+ flushOnExit?: boolean;
73
+ }
74
+ /** Arguments to {@link Client.page}. */
75
+ export interface PageProps {
76
+ /** Valid URL of the page/request. Required, ≤2048 chars. */
77
+ url: string;
78
+ /** Referrer URL. Defaults to `""`. */
79
+ referrer?: string;
80
+ /** Long-lived visitor id (UUID or `h:<64-hex>`). Maps to wire `vid`. */
81
+ visitorId?: string;
82
+ /** Opaque session token. */
83
+ sessionId?: string;
84
+ /** End-user User-Agent, if the server knows it. Maps to wire `ua`. */
85
+ userAgent?: string;
86
+ /** Event name. Defaults to `"pageview"`. Must match `^[a-z][a-z0-9_-]*$`. */
87
+ eventName?: string;
88
+ /** Event time. Defaults to now. Accepts a `Date` or ISO-8601 string. */
89
+ timestamp?: Date | string;
90
+ }
91
+ /** Arguments to {@link Client.track}. */
92
+ export interface TrackOptions {
93
+ /** Long-lived visitor id (UUID or `h:<64-hex>`). REQUIRED for conversions. */
94
+ visitorId: string;
95
+ /** Arbitrary JSON properties. Defaults to `{}`. */
96
+ properties?: Record<string, unknown>;
97
+ /**
98
+ * Conversion value in integer minor units (cents). Maps to `value_cents`.
99
+ * Named `*Cents` to prevent the classic dollars-vs-cents mistake.
100
+ */
101
+ valueCents?: number;
102
+ /** ISO-4217 currency code. Lowercased before send. */
103
+ currency?: string;
104
+ /** Opaque session token. */
105
+ sessionId?: string;
106
+ /** Event time. Defaults to now. Accepts a `Date` or ISO-8601 string. */
107
+ timestamp?: Date | string;
108
+ }
109
+ /** Wire payload for `POST /v1/server/events` (camelCase — do not change casing). */
110
+ export interface EventWire {
111
+ siteId: string;
112
+ ts: string;
113
+ url: string;
114
+ referrer: string;
115
+ eventName: string;
116
+ sessionId?: string;
117
+ vid?: string;
118
+ ua?: string;
119
+ }
120
+ /**
121
+ * Arguments to {@link Client.payment} (#794).
122
+ *
123
+ * `amount` is a DECIMAL in the currency's major unit — 49.99, or 5000 for
124
+ * ¥5000 — NOT minor units. Named `amount` rather than `amountCents` for exactly
125
+ * that reason: it is the number the merchant reads off their processor.
126
+ */
127
+ export interface PaymentProps {
128
+ /** The processor's own id for this payment. The idempotency key. Required. */
129
+ transactionId: string;
130
+ /** Decimal, major unit, non-negative. `0` records a free trial. */
131
+ amount: number;
132
+ /** ISO-4217 code. Uppercased before send. */
133
+ currency: string;
134
+ /**
135
+ * Your label for the processor, e.g. `"dodo"`, `"polar"`, `"kajabi"`.
136
+ * Lowercase, 1-32 chars. Defaults to `"api"`.
137
+ *
138
+ * ⚠️ Do NOT use a label for a processor you have ALSO connected natively —
139
+ * both paths would record the same payment and revenue would double-count.
140
+ */
141
+ provider?: string;
142
+ /** Long-lived visitor id (UUID or `h:<64-hex>`). The strongest match. */
143
+ visitorId?: string;
144
+ /** Customer email. Hashed server-side for lookup; never stored. */
145
+ email?: string;
146
+ /** The processor's opaque customer token. */
147
+ customerId?: string;
148
+ /** A subscription renewal rather than a first payment. */
149
+ renewal?: boolean;
150
+ /** Records the payment as refunded. Never send a negative `amount`. */
151
+ refunded?: boolean;
152
+ /** Records a trial signup with no revenue. Implied by `amount: 0`. */
153
+ isFreeTrial?: boolean;
154
+ /** Payment time. Defaults to now. Accepts a `Date` or ISO-8601 string. */
155
+ timestamp?: Date | string;
156
+ /**
157
+ * The processor's OWN conversion of this payment into a currency Traceten
158
+ * can price (e.g. Dodo's `settlement_amount`, always USD/GBP/EUR) — a
159
+ * fallback used ONLY when {@link PaymentProps.currency} isn't one of the
160
+ * ~30 ECB publishes a reference rate for, so the payment doesn't have to be
161
+ * dropped. Decimal, major unit, like `amount`.
162
+ *
163
+ * Must be paired with {@link PaymentProps.settlementCurrency} — set one
164
+ * without the other and the SDK sends neither.
165
+ */
166
+ settlementAmount?: number;
167
+ /** ISO-4217 code for {@link PaymentProps.settlementAmount}. Uppercased before send. */
168
+ settlementCurrency?: string;
169
+ }
170
+ /** What the Payment API did with a payment. See {@link Client.payment}. */
171
+ export type PaymentStatus = "recorded" | "trial" | "refunded" | "duplicate";
172
+ /** Resolved value of {@link Client.payment}. */
173
+ export interface PaymentResult {
174
+ /**
175
+ * `recorded` — attributed. `trial` — recorded with no revenue.
176
+ * `refunded` — the refund was noted. `duplicate` — already had this one.
177
+ *
178
+ * Only `recorded` and `trial` created anything new; the other two are the
179
+ * idempotent answers, and neither creates a second payment.
180
+ */
181
+ status: PaymentStatus;
182
+ transactionId: string;
183
+ }
184
+ /** Wire payload for `POST /v1/server/payments` (snake_case — do not change casing). */
185
+ export interface PaymentWire {
186
+ site_id: string;
187
+ transaction_id: string;
188
+ amount: number;
189
+ currency: string;
190
+ provider?: string;
191
+ visitor_id?: string;
192
+ email?: string;
193
+ customer_id?: string;
194
+ renewal?: boolean;
195
+ refunded?: boolean;
196
+ is_free_trial?: boolean;
197
+ timestamp?: string;
198
+ settlement_amount?: number;
199
+ settlement_currency?: string;
200
+ }
201
+ /** Wire payload for `POST /v1/server/conversions` (snake_case — do not change casing). */
202
+ export interface ConversionWire {
203
+ event_name: string;
204
+ site_id: string;
205
+ timestamp: string;
206
+ visitor_id: string;
207
+ properties: Record<string, unknown>;
208
+ session_id?: string;
209
+ value_cents?: number;
210
+ currency?: string;
211
+ }
212
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Public and wire-format types for the Traceten Node SDK.
3
+ *
4
+ * The two wire types (`EventWire`, `ConversionWire`) mirror the exact shapes
5
+ * validated by the ingestion Worker (`apps/workers/src/index.ts` —
6
+ * `EventPayloadSchema`, `ConversionPayloadSchema`). The events payload is
7
+ * camelCase; the conversions payload is snake_case. Do not "fix" the casing —
8
+ * it is the contract.
9
+ *
10
+ * The SDK posts to `/v1/server/events` and `/v1/server/conversions`: the same
11
+ * schemas and the same Kafka records as the open `/v1/events` and
12
+ * `/v1/conversions` routes the browser snippet uses, behind a mandatory
13
+ * `Authorization: Bearer` and an isolated rate-limit bucket (#725).
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Client-side validation for the Traceten Node SDK.
3
+ *
4
+ * Philosophy (see DESIGN.md §5): programmer errors surface *loudly and
5
+ * synchronously* at call time — a mis-shaped call is a bug you want to find in
6
+ * development, not a silent server-side 422 buried in logs. Delivery failures,
7
+ * by contrast, never throw (they go through `onError`).
8
+ *
9
+ * Every regex here is copied verbatim from the ingestion Worker
10
+ * (`apps/workers/src/index.ts`) so the client rejects exactly what the server
11
+ * would reject — never more, never less.
12
+ */
13
+ /**
14
+ * Validate the constructor `siteId`. Throws synchronously on a bad value —
15
+ * a wrong site id means every event silently 422s server-side.
16
+ */
17
+ export declare function validateSiteId(siteId: unknown): string;
18
+ /**
19
+ * Validate the constructor `apiKey`.
20
+ *
21
+ * REQUIRED since the SDK moved to `/v1/server/*` — that endpoint rejects an
22
+ * unauthenticated request with 401, so a missing key means every event is
23
+ * silently dropped server-side. Failing loudly in the constructor is the whole
24
+ * point: the alternative is a production integration that looks healthy and
25
+ * records nothing.
26
+ *
27
+ * Only the SHAPE is checked here. Whether the key is live, unrevoked and
28
+ * scoped to `siteId` is a server-side question, answered per request.
29
+ */
30
+ export declare function validateApiKey(apiKey: unknown): string;
31
+ /**
32
+ * Validate + normalize the constructor `host`: absolute http(s) URL, trailing
33
+ * slash stripped. Throws on anything else.
34
+ */
35
+ export declare function normalizeHost(host: unknown): string;
36
+ /** Validate the `page()` url: valid URL, ≤2048 chars. */
37
+ export declare function validateUrl(url: unknown): string;
38
+ /** Validate a `/v1/events` `eventName`. */
39
+ export declare function validateEventName(name: unknown): string;
40
+ /** Validate a `/v1/conversions` `event_name` (stricter — no hyphen). */
41
+ export declare function validateConversionName(name: unknown): string;
42
+ /**
43
+ * Goal names the Stripe/Shopify pipeline emits. `goal()` refuses them because a
44
+ * collision silently corrupts the site's revenue funnel; `track()` still accepts
45
+ * them, since that is how those events are legitimately sent.
46
+ *
47
+ * Mirrors RESERVED_GOAL_NAMES in Traceten's shared contract.
48
+ */
49
+ export declare const RESERVED_GOAL_NAMES: readonly ["payment", "free_trial", "trial_started", "trial_converted", "subscription_started", "subscription_upgraded", "subscription_downgraded", "subscription_renewed", "subscription_cancel_scheduled", "subscription_reactivated", "subscription_ended"];
50
+ /** Validate a `goal()` name: a conversion name that is not reserved. */
51
+ export declare function validateGoalName(name: unknown): string;
52
+ /** Validate a visitor id against the shared UUID | `h:<hash>` format. */
53
+ export declare function validateVisitorId(vid: unknown): string;
54
+ /** Validate an `/v1/events` `sessionId` (charset-restricted, ≤128). */
55
+ export declare function validateEventSessionId(sessionId: unknown): string;
56
+ /** Validate a `/v1/conversions` `session_id` (length only, ≤128). */
57
+ export declare function validateConversionSessionId(sessionId: unknown): string;
58
+ /** Validate a User-Agent string (≤512). */
59
+ export declare function validateUserAgent(ua: unknown): string;
60
+ /** Validate a referrer string (≤2048). */
61
+ export declare function validateReferrer(referrer: unknown): string;
62
+ /** Validate `valueCents`: a non-negative integer (minor units). */
63
+ export declare function validateValueCents(value: unknown): number;
64
+ /**
65
+ * Lowercase + validate an ISO-4217 currency code.
66
+ *
67
+ * The lowercasing is a CLIENT-SIDE normalisation only, and deliberately kept
68
+ * (#793): Traceten uppercases at every server boundary, so the case sent here
69
+ * does not reach storage. Do not read this as the wire format — stored and
70
+ * returned codes are uppercase.
71
+ */
72
+ export declare function normalizeCurrency(currency: unknown): string;
73
+ /** Validate the processor's transaction id — the Payment API idempotency key. */
74
+ export declare function validateTransactionId(value: unknown): string;
75
+ /**
76
+ * Validate a payment amount: a finite, non-negative DECIMAL in the major unit.
77
+ *
78
+ * Not integer cents, unlike `valueCents` — see {@link PaymentProps.amount}. A
79
+ * negative is rejected outright because a refund is the `refunded` flag; the
80
+ * server stores revenue as an unsigned integer and could not hold one anyway.
81
+ */
82
+ export declare function validateAmount(value: unknown): number;
83
+ /** Validate the optional Payment API `provider` label. */
84
+ export declare function validateProvider(value: unknown): string;
85
+ /**
86
+ * Uppercase + validate an ISO-4217 code for the Payment API.
87
+ *
88
+ * Uppercase, unlike {@link normalizeCurrency}: this endpoint is newer than the
89
+ * conversions one and there is no legacy lowercase behaviour to preserve, so it
90
+ * sends the case the server actually stores.
91
+ */
92
+ export declare function normalizePaymentCurrency(currency: unknown): string;
93
+ /**
94
+ * Normalize a caller-supplied timestamp to an ISO-8601 UTC string (the format
95
+ * the ingest Worker's `z.string().datetime()` accepts). An invalid `Date` or
96
+ * unparseable string is a programmer error and throws.
97
+ */
98
+ export declare function toIsoTimestamp(value?: Date | string): string;
99
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA+BH;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAQtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAWtD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAgCnD;AAeD,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAmBhD;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAQvD;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAQ5D;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,+PAYtB,CAAC;AAEX,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAStD;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAKtD;AAED,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAQjE;AAED,qEAAqE;AACrE,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAKtE;AAED,2CAA2C;AAC3C,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAKrD;AAED,0CAA0C;AAC1C,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAK1D;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKzD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAS3D;AAKD,iFAAiF;AACjF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAK5D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKrD;AAED,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKvD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CASlE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAkB5D"}