@cross-deck/node 0.1.0 → 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/index.d.ts CHANGED
@@ -1,187 +1,348 @@
1
- type Environment = "production" | "sandbox";
2
- type AuditRail = "apple" | "stripe" | "google" | "manual";
3
- interface PublicEntitlement {
4
- object: "entitlement";
5
- key: string;
6
- isActive: boolean;
7
- validUntil?: number | null;
8
- source: {
9
- rail: AuditRail;
10
- productId: string;
11
- subscriptionId: string;
12
- };
13
- updatedAt: number;
14
- }
15
- interface EntitlementsListResponse {
16
- object: "list";
17
- data: PublicEntitlement[];
18
- crossdeckCustomerId: string;
19
- env: Environment;
20
- }
21
- interface AliasResult {
22
- object: "alias_result";
23
- crossdeckCustomerId: string;
24
- linked: Array<{
25
- type: "developer";
26
- id: string;
27
- } | {
28
- type: "anonymous";
29
- id: string;
30
- }>;
31
- mergePending: boolean;
32
- env: Environment;
33
- }
34
- interface IngestResponse {
35
- object: "list";
36
- received: number;
37
- env: Environment;
38
- throttled?: {
39
- dropped: number;
40
- sampleRate: number;
41
- retryAfterMs: number;
42
- };
43
- }
44
- interface PurchaseResult {
45
- object: "purchase_result";
46
- crossdeckCustomerId: string;
47
- env: Environment;
48
- entitlements: PublicEntitlement[];
49
- }
50
- interface ForgetResult {
51
- object: "forgot";
52
- crossdeckCustomerId: string | null;
53
- queuedAt: number;
54
- env: Environment;
1
+ export { A as AliasIdentityInput, a as AliasResult, b as AuditDecision, c as AuditEntry, B as Breadcrumb, d as BreadcrumbCategory, e as BreadcrumbLevel, C as CROSSDECK_API_VERSION, f as CapturedError, g as CrossdeckAuthenticationError, h as CrossdeckConfigurationError, i as CrossdeckError, j as CrossdeckErrorPayload, k as CrossdeckErrorType, l as CrossdeckInternalError, m as CrossdeckNetworkError, n as CrossdeckPermissionError, o as CrossdeckRateLimitError, p as CrossdeckServer, q as CrossdeckServerOptions, r as CrossdeckValidationError, D as DEFAULT_BASE_URL, s as DEFAULT_TIMEOUT_MS, t as Diagnostics, E as EntitlementCacheOptions, u as EntitlementMutationResult, v as EntitlementsListResponse, w as EntitlementsListener, x as Environment, y as ErrorCaptureConfig, z as ErrorLevel, F as EventProperties, G as ForgetResult, H as GrantDuration, I as GrantEntitlementInput, J as GroupMembership, K as HeartbeatResponse, L as HttpRequestInfo, M as HttpResponseInfo, N as HttpRetriesConfig, O as IdentifyOptions, P as IdentityHints, Q as IngestOptions, R as IngestResponse, S as PublicEntitlement, T as PurchaseResult, U as RequestOptions, V as RevokeEntitlementInput, W as RuntimeHost, X as RuntimeInfo, Y as SDK_NAME, Z as SDK_VERSION, _ as ServerEvent, $ as StackFrame, a0 as SyncPurchaseInput, a1 as makeCrossdeckError } from './crossdeck-server-LvQwPKu5.js';
2
+ import 'node:events';
3
+
4
+ /**
5
+ * Machine-readable index of every error code `@cross-deck/node` can
6
+ * throw, with a short description and a hint on what action to take.
7
+ * Mirrors `@cross-deck/web/src/error-codes.ts`.
8
+ *
9
+ * Stripe publishes the same surface at stripe.com/docs/error-codes;
10
+ * developers love it because every code has a canonical "what does
11
+ * this mean / what should I do" answer.
12
+ *
13
+ * Differences from web:
14
+ * - Drops browser-only config codes (`invalid_public_key`,
15
+ * `missing_app_id`, `invalid_environment`, `environment_mismatch`,
16
+ * `not_initialized`).
17
+ * - Adds `invalid_secret_key` (Node SDK takes a secret key, not a
18
+ * publishable key + env declaration).
19
+ * - Adds Node-lifecycle codes (`flush_on_exit_failed`,
20
+ * `webhook_invalid_signature`, `webhook_replay_window_exceeded`,
21
+ * `webhook_missing_secret`).
22
+ *
23
+ * Adding a new error code:
24
+ * 1. Throw it as `CrossdeckError.code` in the call site.
25
+ * 2. Add an entry here so the dashboard + AI assistants can render
26
+ * the canonical fix.
27
+ *
28
+ * Keep entries terse — consumers surface this in tooltips and
29
+ * automated tickets, not in long-form docs.
30
+ */
31
+ interface ErrorCodeEntry {
32
+ /** The string thrown as `CrossdeckError.code`. */
33
+ code: string;
34
+ /** Broad category — `CrossdeckError.type`. */
35
+ type: "authentication_error" | "permission_error" | "invalid_request_error" | "rate_limit_error" | "internal_error" | "network_error" | "configuration_error";
36
+ /** One-sentence description. Surfaced verbatim in dashboards. */
37
+ description: string;
38
+ /** What the developer should do. Imperative phrasing. */
39
+ resolution: string;
40
+ /** True for codes the SDK can auto-recover from (no developer action). */
41
+ retryable: boolean;
55
42
  }
56
- interface CrossdeckServerOptions {
57
- secretKey: string;
58
- baseUrl?: string;
59
- timeoutMs?: number;
60
- sdkVersion?: string;
43
+ /**
44
+ * Internal source-of-truth with literal types preserved via `as const`.
45
+ * The exported `CROSSDECK_ERROR_CODES` widens to `readonly
46
+ * ErrorCodeEntry[]` for the public surface (callers iterating it
47
+ * shouldn't depend on literal positions), while `CrossdeckErrorCode`
48
+ * below derives the literal union from this constant for type-safe
49
+ * code narrowing.
50
+ */
51
+ declare const _CROSSDECK_ERROR_CODES: readonly [{
52
+ readonly code: "invalid_secret_key";
53
+ readonly type: "configuration_error";
54
+ readonly description: "The secret key passed to new CrossdeckServer({ secretKey }) doesn't start with cd_sk_.";
55
+ readonly resolution: "Copy the key from your Crossdeck dashboard → API keys page. Server SDKs use cd_sk_test_… (sandbox) or cd_sk_live_… (production). Never ship this key to a browser.";
56
+ readonly retryable: false;
57
+ }, {
58
+ readonly code: "missing_user_id";
59
+ readonly type: "invalid_request_error";
60
+ readonly description: "identify() / aliasIdentity() called with an empty userId.";
61
+ readonly resolution: "Pass a stable, non-empty user identifier from your auth layer — never a hardcoded placeholder.";
62
+ readonly retryable: false;
63
+ }, {
64
+ readonly code: "missing_anonymous_id";
65
+ readonly type: "invalid_request_error";
66
+ readonly description: "aliasIdentity() called with an empty anonymousId.";
67
+ readonly resolution: "Pass the anonymousId originally minted by the web SDK on this user's device.";
68
+ readonly retryable: false;
69
+ }, {
70
+ readonly code: "missing_customer_id";
71
+ readonly type: "invalid_request_error";
72
+ readonly description: "An operation that requires a Crossdeck customer ID was called with an empty value.";
73
+ readonly resolution: "Pass the customerId returned from a prior identify() / getEntitlements() call.";
74
+ readonly retryable: false;
75
+ }, {
76
+ readonly code: "missing_identity";
77
+ readonly type: "invalid_request_error";
78
+ readonly description: "An ingest / forget / entitlements call received no identity hints.";
79
+ readonly resolution: "Pass at least one of customerId, userId, or anonymousId on the call (or per-event for ingest).";
80
+ readonly retryable: false;
81
+ }, {
82
+ readonly code: "missing_event_name";
83
+ readonly type: "invalid_request_error";
84
+ readonly description: "track() / ingest() received an event without a name.";
85
+ readonly resolution: "Pass a non-empty string as the event name. The wire shape is { name, properties? }.";
86
+ readonly retryable: false;
87
+ }, {
88
+ readonly code: "missing_events";
89
+ readonly type: "invalid_request_error";
90
+ readonly description: "ingest() received an empty array.";
91
+ readonly resolution: "Pass at least one event. Use server.track(event) to send a single event.";
92
+ readonly retryable: false;
93
+ }, {
94
+ readonly code: "missing_event_id";
95
+ readonly type: "invalid_request_error";
96
+ readonly description: "getAuditEntry() called with an empty eventId.";
97
+ readonly resolution: "Pass the eventId from the audit row you want to inspect.";
98
+ readonly retryable: false;
99
+ }, {
100
+ readonly code: "missing_signed_transaction_info";
101
+ readonly type: "invalid_request_error";
102
+ readonly description: "syncPurchases() called without StoreKit 2 signed transaction info.";
103
+ readonly resolution: "Pass the JWS string from Transaction.currentEntitlements / Transaction.updates.";
104
+ readonly retryable: false;
105
+ }, {
106
+ readonly code: "missing_group_type";
107
+ readonly type: "invalid_request_error";
108
+ readonly description: "group(type, id) called with an empty type.";
109
+ readonly resolution: "Pass a non-empty group type (e.g. \"org\", \"team\", \"plan\") as the first argument.";
110
+ readonly retryable: false;
111
+ }, {
112
+ readonly code: "serialization_failed";
113
+ readonly type: "invalid_request_error";
114
+ readonly description: "An event payload or trait bag could not be JSON-serialised even after sanitisation.";
115
+ readonly resolution: "Inspect the payload for non-JSON-friendly values (functions, symbols, deeply circular refs). The SDK's validator drops these by default, so this usually means a bug — file an issue with the payload shape.";
116
+ readonly retryable: false;
117
+ }, {
118
+ readonly code: "fetch_failed";
119
+ readonly type: "network_error";
120
+ readonly description: "The underlying fetch() call failed (typically a network outage, DNS, or refused connection).";
121
+ readonly resolution: "Check the host's outbound network. The SDK retries automatically with exponential backoff + jitter for queued events.";
122
+ readonly retryable: true;
123
+ }, {
124
+ readonly code: "request_timeout";
125
+ readonly type: "network_error";
126
+ readonly description: "A request was aborted after the configured timeoutMs (default 15s).";
127
+ readonly resolution: "Check the host's network. Increase timeoutMs in CrossdeckServer options if you're on a known-slow link.";
128
+ readonly retryable: true;
129
+ }, {
130
+ readonly code: "invalid_json_response";
131
+ readonly type: "internal_error";
132
+ readonly description: "The server returned a 2xx with an unparseable body.";
133
+ readonly resolution: "Likely a transient backend bug. Retry; if it persists, contact support with the requestId.";
134
+ readonly retryable: true;
135
+ }, {
136
+ readonly code: "flush_on_exit_failed";
137
+ readonly type: "internal_error";
138
+ readonly description: "The on-exit drain (beforeExit / SIGTERM / SIGINT) did not complete before flushOnExitTimeoutMs.";
139
+ readonly resolution: "Increase flushOnExitTimeoutMs in CrossdeckServer options. Default is 2000ms; serverless runtimes typically allow 5-10s before SIGKILL. If events are dropping silently in production, raise this.";
140
+ readonly retryable: false;
141
+ }, {
142
+ readonly code: "webhook_invalid_signature";
143
+ readonly type: "authentication_error";
144
+ readonly description: "The webhook signature header did not verify against the supplied secret.";
145
+ readonly resolution: "Confirm the secret matches the one in your Crossdeck dashboard → Webhooks page. If the request is genuinely from Crossdeck, the secret is wrong, stale, or recently rotated.";
146
+ readonly retryable: false;
147
+ }, {
148
+ readonly code: "webhook_replay_window_exceeded";
149
+ readonly type: "authentication_error";
150
+ readonly description: "The webhook timestamp is older than the replay-tolerance window (default 5 minutes).";
151
+ readonly resolution: "The webhook is either replayed or your receiving clock is wildly skewed. Verify NTP on the receiving host. Increase replayToleranceMs only if you accept the replay-attack risk.";
152
+ readonly retryable: false;
153
+ }, {
154
+ readonly code: "webhook_missing_secret";
155
+ readonly type: "configuration_error";
156
+ readonly description: "verifyWebhookSignature() was called without a signing secret.";
157
+ readonly resolution: "Pass the secret from your Crossdeck dashboard → Webhooks page. Never hardcode in source — read from an env var.";
158
+ readonly retryable: false;
159
+ }];
160
+ /**
161
+ * Literal union of every code documented in `CROSSDECK_ERROR_CODES`.
162
+ * Exported so callers can do type-safe code comparisons:
163
+ *
164
+ * if (err.code === "webhook_invalid_signature") { ... } // typed
165
+ * if (err.code === "webook_invalid_signature") { ... } // TS error
166
+ *
167
+ * Mis-spelling a code at compile time fails to compile — the gap that
168
+ * silently broke v0.1.0 callers checking for non-existent codes.
169
+ */
170
+ type CrossdeckErrorCode = (typeof _CROSSDECK_ERROR_CODES)[number]["code"];
171
+ /** Type guard: narrows a string to the documented literal union. */
172
+ declare function isCrossdeckErrorCode(code: string): code is CrossdeckErrorCode;
173
+ /**
174
+ * Public catalogue, widened to `readonly ErrorCodeEntry[]` for iteration
175
+ * stability. Callers needing literal types should reach for the
176
+ * `CrossdeckErrorCode` union above instead of indexing into this array.
177
+ */
178
+ declare const CROSSDECK_ERROR_CODES: readonly ErrorCodeEntry[];
179
+ /** Lookup helper — returns the entry matching a CrossdeckError.code, or undefined. */
180
+ declare function getErrorCode(code: string): ErrorCodeEntry | undefined;
181
+
182
+ /**
183
+ * Webhook signature verification — Stripe pattern.
184
+ *
185
+ * Lets customers verify the events Crossdeck sends to THEM. Table-stakes
186
+ * for any backend SDK (Stripe ships `Stripe.webhooks.constructEvent()`
187
+ * from day one, Svix ships `Webhook.verify()` from day one).
188
+ *
189
+ * Wire format:
190
+ * Header `Crossdeck-Signature: t=<unix-seconds>,v1=<hex>`
191
+ * Where `v1` is HMAC-SHA256(secret, `${t}.${payload}`) — Stripe-compatible.
192
+ *
193
+ * Customers receive a signing secret from the Crossdeck dashboard
194
+ * (one-time reveal at mint time; rotated as needed). Each webhook
195
+ * carries the signature header above. The customer's handler:
196
+ *
197
+ * import { verifyWebhookSignature } from "@cross-deck/node";
198
+ *
199
+ * app.post("/crossdeck-webhook", express.raw({ type: "application/json" }), (req, res) => {
200
+ * try {
201
+ * const event = verifyWebhookSignature(
202
+ * req.body.toString("utf8"),
203
+ * req.headers["crossdeck-signature"],
204
+ * process.env.CROSSDECK_WEBHOOK_SECRET,
205
+ * );
206
+ * // event is the parsed JSON payload
207
+ * handleCrossdeckEvent(event);
208
+ * res.sendStatus(200);
209
+ * } catch (err) {
210
+ * res.sendStatus(401);
211
+ * }
212
+ * });
213
+ *
214
+ * The signing scheme is constant-time via `crypto.timingSafeEqual` so
215
+ * a malicious caller can't extract the signature by measuring response
216
+ * timing. Replay defence: timestamps older than `replayToleranceMs`
217
+ * (default 5 min) are rejected — required because HMAC-SHA256 is
218
+ * stateless and would otherwise allow an attacker to replay an old
219
+ * webhook indefinitely.
220
+ *
221
+ * Supports multiple secrets for rotation: pass an array; the helper
222
+ * tries each, accepts on the first match. Lets customers rotate the
223
+ * dashboard secret without dropping in-flight webhooks.
224
+ */
225
+ interface VerifyWebhookOptions {
61
226
  /**
62
- * Optional informational appId stamped onto event batches. The server
63
- * ultimately trusts the API key's resolved app routing, so this is
64
- * best-effort metadata only.
227
+ * Maximum age of the webhook timestamp in milliseconds. Default
228
+ * 5 minutes (5 * 60 * 1000). Anything older than this is rejected
229
+ * as a replay. Pass 0 to disable the replay window (NOT recommended
230
+ * — accept the trade-off only if you have a separate replay defence).
65
231
  */
66
- appId?: string;
67
- }
68
- interface IdentityHints {
69
- customerId?: string;
70
- userId?: string;
71
- anonymousId?: string;
72
- }
73
- interface IdentifyOptions {
74
- email?: string;
75
- traits?: Record<string, unknown>;
76
- }
77
- interface AliasIdentityInput extends IdentifyOptions {
78
- userId: string;
79
- anonymousId: string;
80
- }
81
- type ErrorLevel = "error" | "warning" | "info";
82
- interface ServerEvent {
83
- eventId?: string;
84
- name: string;
85
- timestamp?: number;
86
- properties?: Record<string, unknown>;
87
- developerUserId?: string;
88
- anonymousId?: string;
89
- crossdeckCustomerId?: string;
90
- level?: ErrorLevel;
91
- tags?: Record<string, string>;
92
- categoryTags?: string[];
93
- }
94
- interface IngestOptions {
95
- idempotencyKey?: string;
96
- }
97
- interface SyncPurchaseInput {
98
- rail?: "apple";
99
- signedTransactionInfo: string;
100
- signedRenewalInfo?: string;
101
- appAccountToken?: string;
102
- }
103
- type GrantDuration = "P30D" | "P90D" | "P1Y" | "lifetime";
104
- interface GrantEntitlementInput {
105
- customerId: string;
106
- entitlementKey: string;
107
- duration: GrantDuration;
108
- reason: string;
109
- }
110
- interface RevokeEntitlementInput {
111
- customerId: string;
112
- entitlementKey: string;
113
- reason: string;
114
- }
115
- interface EntitlementMutationResult {
116
- object: "entitlement_mutation";
117
- action: "grant" | "revoke";
118
- crossdeckCustomerId: string;
119
- entitlement: PublicEntitlement;
120
- env: Environment;
121
- }
122
- type AuditDecision = "applied" | "no_op" | "rejected";
123
- interface AuditEntry {
124
- eventId: string;
125
- rail: AuditRail;
126
- env: Environment;
127
- eventType: string;
128
- projectId: string;
129
- subscriptionId?: string;
130
- customerId?: string;
131
- fromState?: string;
132
- toState?: string;
133
- decision: AuditDecision;
134
- reason?: string;
135
- derivedSignal?: string;
136
- signatureVerified: boolean;
137
- reconciledWithProvider: boolean;
138
- rawEventReceivedAt: number;
139
- processedAt: number;
232
+ replayToleranceMs?: number;
233
+ /**
234
+ * Override the current time. Tests use this to verify timestamp
235
+ * handling deterministically. Defaults to `Date.now()`.
236
+ */
237
+ now?: () => number;
140
238
  }
239
+ /**
240
+ * Verify a Crossdeck-signed webhook. Returns the parsed JSON payload
241
+ * on success. Throws `CrossdeckError` on:
242
+ * - missing / malformed signature header (`webhook_invalid_signature`)
243
+ * - missing secret (`webhook_missing_secret`)
244
+ * - timestamp outside replay window (`webhook_replay_window_exceeded`)
245
+ * - HMAC mismatch (`webhook_invalid_signature`)
246
+ * - non-JSON payload (`webhook_invalid_signature` — same code because
247
+ * a tampered payload that breaks JSON parses as invalid)
248
+ *
249
+ * `secret` accepts a single string or an array of strings (for
250
+ * rotation). Any one match is sufficient.
251
+ */
252
+ declare function verifyWebhookSignature(payload: string, signatureHeader: string | string[] | undefined, secret: string | string[] | undefined, options?: VerifyWebhookOptions): unknown;
253
+ /**
254
+ * Pure-function signing — mirror of what the Crossdeck backend does
255
+ * when sending a webhook. Exported so customers building their own
256
+ * test fixtures (a service that sends Crossdeck-signed webhooks to
257
+ * their own test harness) can re-use the canonical signing scheme
258
+ * instead of re-implementing it.
259
+ *
260
+ * const ts = Math.floor(Date.now() / 1000);
261
+ * const sig = signWebhookPayload(payload, secret, ts);
262
+ * const header = `t=${ts},v1=${sig}`;
263
+ *
264
+ * NOT marked as a security primitive for general HMAC — use
265
+ * `node:crypto` directly for that. This is only the
266
+ * Crossdeck-signature shape.
267
+ */
268
+ declare function signWebhookPayload(payload: string, secret: string, timestampSec: number): string;
141
269
 
142
- declare class CrossdeckServer {
143
- private readonly http;
144
- private readonly sdkVersion;
145
- private readonly appId?;
146
- constructor(options: CrossdeckServerOptions);
147
- identify(userId: string, anonymousId: string, options?: IdentifyOptions): Promise<AliasResult>;
148
- aliasIdentity(input: AliasIdentityInput): Promise<AliasResult>;
149
- forget(hints: IdentityHints): Promise<ForgetResult>;
150
- getEntitlements(hints: IdentityHints): Promise<EntitlementsListResponse>;
151
- getCustomerEntitlements(customerId: string): Promise<EntitlementsListResponse>;
152
- track(event: ServerEvent, options?: IngestOptions): Promise<IngestResponse>;
153
- ingest(events: ServerEvent[], options?: IngestOptions): Promise<IngestResponse>;
154
- syncPurchases(input: SyncPurchaseInput): Promise<PurchaseResult>;
155
- grantEntitlement(input: GrantEntitlementInput): Promise<EntitlementMutationResult>;
156
- revokeEntitlement(input: RevokeEntitlementInput): Promise<EntitlementMutationResult>;
157
- getAuditEntry(eventId: string): Promise<AuditEntry>;
158
- private identityPayload;
159
- private normalizeEvent;
160
- private mintEventId;
161
- private mintBatchId;
162
- }
270
+ /**
271
+ * PII scrub utilities — Node port of `@cross-deck/web/src/consent.ts`'s
272
+ * regex-based defence layer.
273
+ *
274
+ * **What's NOT here (intentionally)**: the `ConsentManager` class. That's
275
+ * a browser/end-user UX surface (DNT detection, per-dimension consent
276
+ * gating). On the server side, the customer's user already accepted
277
+ * or declined consent in their client; the API caller decides what
278
+ * to forward. Shipping `ConsentManager` here would imply server-side
279
+ * gating that doesn't match the trust model.
280
+ *
281
+ * **What IS here**: opt-in utilities customers can use to scrub
282
+ * email-shaped and card-number-shaped substrings out of event
283
+ * properties before forwarding to Crossdeck. Stripe-grade defence in
284
+ * depth, applied at the caller's discretion:
285
+ *
286
+ * import { scrubPiiFromProperties } from "@cross-deck/node";
287
+ *
288
+ * server.track({
289
+ * name: "checkout.started",
290
+ * developerUserId: userId,
291
+ * properties: scrubPiiFromProperties({
292
+ * url: req.url, // might contain "/users/wes@…/" — gets [email]
293
+ * lastError: e.message, // might contain card numbers
294
+ * }),
295
+ * });
296
+ */
297
+ /**
298
+ * Scrub a single string value: replace email-shaped substrings with
299
+ * `[email]` and card-number-shaped substrings with `[card]`. Returns
300
+ * the original string (===) when nothing matched, so callers can do
301
+ * an identity-check to skip allocating a new event copy.
302
+ */
303
+ declare function scrubPii(value: string): string;
304
+ /**
305
+ * Walk a property bag and replace PII-shaped strings in place. Returns
306
+ * a new object with strings scrubbed; non-string values pass through
307
+ * unchanged.
308
+ *
309
+ * Defensive copy — the input is never altered. Caller can pass the
310
+ * result straight to `server.track()`.
311
+ *
312
+ * Recursive: nested objects + arrays are walked. Functions, symbols,
313
+ * Dates, etc. pass through untouched (those are the
314
+ * `validateEventProperties` sanitiser's job — this is just the
315
+ * PII regex pass).
316
+ */
317
+ declare function scrubPiiFromProperties(properties: Record<string, unknown>): Record<string, unknown>;
163
318
 
164
- type CrossdeckErrorType = "authentication_error" | "permission_error" | "invalid_request_error" | "rate_limit_error" | "internal_error" | "network_error" | "configuration_error";
165
- interface CrossdeckErrorPayload {
166
- type: CrossdeckErrorType;
167
- code: string;
168
- message: string;
169
- requestId?: string;
170
- status?: number;
171
- retryAfterMs?: number;
319
+ /**
320
+ * Debug signal vocabulary — NorthStar §16, Node port of
321
+ * `@cross-deck/web/src/debug.ts`.
322
+ *
323
+ * The SDK speaks a small fixed vocabulary of signals so the dashboard's
324
+ * onboarding checklist and the developer's console output both speak
325
+ * the same words. When `debug: true` is set in `CrossdeckServerOptions`,
326
+ * the signals are also logged to `console.info` so a developer doing
327
+ * copy-paste integration sees actionable feedback live.
328
+ *
329
+ * Signal names are STABLE — adding new ones is fine, renaming is a
330
+ * breaking change because the dashboard onboarding step keys off them.
331
+ *
332
+ * Node-specific additions beyond web's vocabulary:
333
+ * - `sdk.flush_on_exit_started` / `sdk.flush_on_exit_completed`
334
+ * - `sdk.webhook_verified`
335
+ * - `sdk.runtime_detected`
336
+ * - `sdk.entitlement_cache_warm`
337
+ * - `sdk.super_property_registered`
338
+ */
339
+ type DebugSignal = "sdk.configured" | "sdk.first_event_sent" | "sdk.invalid_key" | "sdk.no_identity" | "sdk.entitlement_cache_used" | "sdk.entitlement_cache_warm" | "sdk.purchase_evidence_sent" | "sdk.environment_mismatch" | "sdk.sensitive_property_warning" | "sdk.property_coerced" | "sdk.flush_retry_scheduled" | "sdk.flush_on_exit_started" | "sdk.flush_on_exit_completed" | "sdk.webhook_verified" | "sdk.runtime_detected" | "sdk.super_property_registered";
340
+ interface DebugContext {
341
+ [key: string]: unknown;
172
342
  }
173
- declare class CrossdeckError extends Error {
174
- readonly type: CrossdeckErrorType;
175
- readonly code: string;
176
- readonly requestId?: string;
177
- readonly status?: number;
178
- readonly retryAfterMs?: number;
179
- constructor(payload: CrossdeckErrorPayload);
343
+ interface DebugLogger {
344
+ enabled: boolean;
345
+ emit(signal: DebugSignal, message: string, context?: DebugContext): void;
180
346
  }
181
347
 
182
- declare const SDK_NAME = "@cross-deck/node";
183
- declare const SDK_VERSION = "0.1.0";
184
- declare const DEFAULT_BASE_URL = "https://api.cross-deck.com/v1";
185
- declare const DEFAULT_TIMEOUT_MS = 15000;
186
-
187
- export { type AliasIdentityInput, type AliasResult, type AuditDecision, type AuditEntry, CrossdeckError, type CrossdeckErrorPayload, type CrossdeckErrorType, CrossdeckServer, type CrossdeckServerOptions, DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, type EntitlementMutationResult, type EntitlementsListResponse, type Environment, type ErrorLevel, type ForgetResult, type GrantDuration, type GrantEntitlementInput, type IdentifyOptions, type IdentityHints, type IngestOptions, type IngestResponse, type PublicEntitlement, type RevokeEntitlementInput, SDK_NAME, SDK_VERSION, type ServerEvent, type SyncPurchaseInput };
348
+ export { CROSSDECK_ERROR_CODES, type CrossdeckErrorCode, type DebugContext, type DebugLogger, type DebugSignal, type ErrorCodeEntry, type VerifyWebhookOptions, getErrorCode, isCrossdeckErrorCode, scrubPii, scrubPiiFromProperties, signWebhookPayload, verifyWebhookSignature };