/** * Traffical Node.js SDK Client * * HTTP client with caching, background refresh, and graceful degradation. * Wraps the pure core-ts resolution engine. * * Features: * - ETag-based caching for efficient config fetches * - Background refresh for keeping config up-to-date * - Automatic decision tracking for intent-to-treat analysis * - Batched event transport for efficiency * - Graceful degradation with local config and schema defaults */ import { type Context, type DecisionResult, type ParameterValue, type TrafficalClientOptions as CoreClientOptions, type TrackOptions, type TrackEventOptions, type DecideOptions, type GetParamsOptions, type AssignmentLogger, type TrackableEventLogger, type TrackEventMap, type OnSchemaWarnings, type SdkDiagnostics } from "@traffical/core"; /** * Options for the Node.js Traffical client. * Extends the core options with Node-specific settings. */ export interface TrafficalClientOptions extends CoreClientOptions { /** * Whether to automatically track decision events (default: true). * When enabled, every call to decide() automatically sends a DecisionEvent * to the backend, enabling intent-to-treat analysis. */ trackDecisions?: boolean; /** * Decision deduplication TTL in milliseconds (default: 1 hour). * Same user+assignment combination won't be tracked again within this window. */ decisionDeduplicationTtlMs?: number; /** * Event batch size - number of events before auto-flush (default: 10). * @deprecated Use the canonical `batchSize` instead. `eventBatchSize` still works. */ eventBatchSize?: number; /** * Event flush interval in milliseconds (default: 30000). * @deprecated Use the canonical `flushIntervalMs` instead. `eventFlushIntervalMs` still works. */ eventFlushIntervalMs?: number; /** Events per delivery batch (default: 10). Canonical alias of `eventBatchSize`. */ batchSize?: number; /** Event flush cadence in milliseconds (default: 30000). Canonical alias of `eventFlushIntervalMs`. */ flushIntervalMs?: number; /** * Maximum number of events buffered in memory before the oldest is dropped * (default: 1000). Bounds memory in long-lived server processes. */ eventMaxQueueSize?: number; /** * Timeout in milliseconds for SDK network requests — the config bundle * fetch and event batch POSTs (default: 10000). * * On timeout the request is aborted and treated exactly like a network * failure: config fetches fall back to the cached/local config, and event * batches are re-queued for retry. * * @deprecated Use the per-path options `configTimeoutMs`, `eventsTimeoutMs`, * and `resolveTimeoutMs` instead. `requestTimeoutMs` is still honored as the * legacy fallback for all three when the specific option is not provided. */ requestTimeoutMs?: number; /** * Timeout in milliseconds for the config-bundle fetch (default: 10000). * Falls back to `requestTimeoutMs` when not set. */ configTimeoutMs?: number; /** * Timeout in milliseconds for event-delivery POSTs (default: 10000). * Falls back to `requestTimeoutMs` when not set. */ eventsTimeoutMs?: number; /** * Timeout in milliseconds for server-resolve requests (POST /v1/resolve, * default: 5000). Falls back to `requestTimeoutMs` when not set. */ resolveTimeoutMs?: number; /** * Enable debug logging for events (default: false). */ debugEvents?: boolean; /** * Evaluation mode (default: "bundle"). * - "bundle": SDK fetches config bundle, resolves parameters locally. * - "server": SDK delegates resolution to the edge worker via POST /v1/resolve. */ evaluationMode?: "bundle" | "server"; /** * Optional callback for routing assignment events to a customer-managed * pipeline (e.g., Segment, Rudderstack, direct DB writes). */ assignmentLogger?: AssignmentLogger; /** * When true, the SDK will NOT send events to the Traffical control plane. * Default: false */ disableCloudEvents?: boolean; /** * When true, assignment logger calls are deduplicated via in-memory LRU * (same unit+policy+allocation won't fire again within TTL). Default: true. */ deduplicateAssignmentLogger?: boolean; /** * When true, exposure events are deduplicated per (unit, policy, allocation) * within a session via in-memory LRU, mirroring the browser SDK. Default: true. */ deduplicateExposures?: boolean; /** * Exposure deduplication session TTL in milliseconds (default: 30 minutes). */ exposureSessionTtlMs?: number; /** * Optional callback for routing full events (exposure, track, decision) * to a customer-managed pipeline (e.g. Jitsu, Segment). Fires regardless * of disableCloudEvents, so you can send to your own sink instead of (or * in addition to) the Traffical edge. */ eventLogger?: TrackableEventLogger; /** * Callback for schema validation warnings from the edge. * Only fires when event schemas are defined and enforcement is "warn". * Recommended for development builds to surface schema violations. */ onSchemaWarnings?: OnSchemaWarnings; } /** * TrafficalClient - the main SDK client for Node.js environments. * * Features: * - ETag-based caching for efficient config fetches * - Background refresh for keeping config up-to-date * - Automatic decision tracking for intent-to-treat analysis * - Batched event transport for efficiency * - Graceful degradation with local config and schema defaults * - Rate-limited offline warnings */ export declare class TrafficalClient { private readonly _options; private _state; private readonly _errorPolicy; private readonly _eventBatcher; private readonly _requestTimeoutMs; private readonly _decisionDedup; private readonly _decisionClient; private readonly _assignmentLogger?; private readonly _byoEventLogger?; private readonly _disableCloudEvents; /** In-memory LRU for assignment logger deduplication: key → expiry timestamp */ private readonly _assignmentLoggerDedup; /** In-memory LRU for exposure-event deduplication: key → expiry timestamp */ private readonly _exposureDedup; /** Session TTL for exposure deduplication (ms) */ private readonly _exposureSessionTtlMs; /** Cache of recent decisions for attribution lookup on rewards */ private readonly _decisionCache; /** Serialized context of the last server-mode resolve (per-call throttle). */ private _lastResolveContextKey; /** Resolves once the first config load attempt completes (fail-open). */ private _readyResolve; private readonly _readyPromise; constructor(options: TrafficalClientOptions); /** * Counters for degradation that is otherwise invisible: contained resolution * errors, dropped assignment/event log rows, rejected bundles, and the most * recent contained error. * * Monotonic for the life of the client. Poll it, or pass `onError` for a * push-based bridge into your own telemetry. */ getDiagnostics(): SdkDiagnostics; /** * The result a contained resolution failure returns: the caller's defaults, * with `metadata.reason` saying why. Shaped like a real DecisionResult so * callers never have to null-check, and carrying a real decisionId so an * event built from it is still traceable. */ private _degradedDecision; /** * Initializes the client by fetching the config bundle. * This is called automatically by createTrafficalClient. */ initialize(): Promise; /** * Resolves once the first usable config has loaded (or the SDK has failed * open on an unavailable/malformed bundle). Never rejects. */ waitForReady(): Promise; /** * Single teardown verb (spec 0.7.0 design contract). Stops background * refresh and awaits a final event flush before returning. */ close(): Promise; /** * Stops background refresh and cleans up resources. * * @deprecated Use {@link close} instead — the canonical single teardown verb. */ destroy(): Promise; /** * Synchronous destroy for process exit handlers. * * @deprecated Use {@link close} instead. This best-effort variant does not * await the final flush; prefer `await close()` where you can. */ destroySync(): void; /** * Manually refreshes the config bundle. */ refreshConfig(): Promise; /** * Gets the current config bundle version. */ getConfigVersion(): string | null; /** * Returns the context field the bundle buckets on (the project's unit key), * or null before the bundle has loaded. Adapters (e.g. an OpenFeature * provider) map their targeting key onto this field. */ getUnitKeyField(): string | null; /** * Returns the id of the layer a parameter belongs to, or null if the * parameter is unknown / the bundle is not yet loaded. */ getParameterLayerId(key: string): string | null; /** * Flush pending events immediately. */ flushEvents(): Promise; /** * Resolves parameters with defaults as fallback. * * Resolution priority (highest wins): * 1. Policy overrides (from remote bundle) * 2. Parameter defaults (from remote bundle) * 3. Local config (if remote unavailable) * 4. Caller defaults */ getParams>(context: Context, defaults: T): T; /** @deprecated Pass `(context, defaults)` positionally (spec 0.7.0 contract). */ getParams>(options: GetParamsOptions): T; private _getParamsImpl; /** * Makes a decision with full metadata for tracking. * * When trackDecisions is enabled (default), automatically sends a DecisionEvent * to the backend for intent-to-treat analysis. */ decide>(context: Context, defaults: T): DecisionResult; /** @deprecated Pass `(context, defaults)` positionally (spec 0.7.0 contract). */ decide>(options: DecideOptions): DecisionResult; private _decideImpl; /** * Tracks an exposure event — the "user was actually shown this treatment" * signal (treatment-on-the-treated). * * Only layers the caller was actually exposed to are emitted: layers without * a policy/allocation and `attributionOnly` layers (resolved for attribution * but whose parameters weren't requested) are skipped, and each * (unit, policy, allocation) is deduplicated per session so the same exposure * isn't emitted twice. Mirrors the browser SDK. If the decision includes * filtered context (from policies with contextLogging), it is included in the * exposure event for contextual bandit training. */ trackExposure(decision: DecisionResult): void; private _trackExposureImpl; /** * Tracks a user event. * * @example * // Track a purchase with revenue * client.track('purchase', { value: 99.99, orderId: 'ord_123' }); * * // Track a simple event * client.track('add_to_cart', { itemId: 'sku_456' }); * * // Track with explicit decision attribution * client.track('checkout_complete', { value: 1 }, { decisionId: 'dec_xyz' }); */ track>(event: E, properties?: TEvents[E], options?: TrackEventOptions): void; private _trackImpl; /** * @deprecated Use track() instead. * Tracks a reward event. * If decisionId is provided and the decision is cached, attribution is auto-populated. */ trackReward(options: TrackOptions): void; private _emitAssignmentLogEntries; /** * Gets the effective bundle: remote > local > null */ private _getEffectiveBundle; /** * Fetches the config bundle from the edge worker. * Uses ETag for efficient caching. */ private _fetchConfig; /** * Starts background refresh timer. */ private _startBackgroundRefresh; /** * Logs an offline warning (rate-limited). */ /** * Server mode: threads the per-call context into a background /v1/resolve so * the cached snapshot converges to the contexts actually being evaluated. * Throttled by serialized context so repeated identical contexts (the common * case) don't hammer the edge. Because decide()/getParams() are synchronous * they cannot await this; the current call degrades to the last-good snapshot * and subsequent calls pick up the refreshed resolution. */ private _maybeResolveForContext; private _fetchServerResolve; private _logOfflineWarning; private _logMalformedBundleWarning; /** * Routes a built event to the BYO event logger (if configured) and to the * Traffical edge batcher (unless cloud events are disabled). */ private _dispatchEvent; /** * Tracks a decision event (internal). * Called automatically when trackDecisions is enabled. */ private _trackDecision; /** * Caches a decision for attribution lookup when trackReward is called. * Maintains a bounded cache to prevent memory leaks. */ private _cacheDecision; /** * Gets attribution info from cached decision if available. */ private _getAttributionFromCache; } /** * Creates and initializes a Traffical client. * * @example * ```typescript * const traffical = await createTrafficalClient({ * orgId: "org_123", * projectId: "proj_456", * env: "production", * apiKey: "sk_...", * }); * * const params = traffical.getParams({ * context: { userId: "user_789" }, * defaults: { * "ui.button.color": "#000", * }, * }); * ``` */ export declare function createTrafficalClient(options: TrafficalClientOptions): Promise>; /** * Creates a Traffical client without initializing (synchronous). * Useful when you want to control initialization timing. */ export declare function createTrafficalClientSync(options: TrafficalClientOptions): TrafficalClient; //# sourceMappingURL=client.d.ts.map