@outlit/node 1.5.1 → 1.5.2
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.mts +18 -8
- package/dist/index.d.ts +18 -8
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { CustomerIdentifier, ServerTrackOptions, ServerIdentifyOptions, ServerId
|
|
|
2
2
|
export { ExplicitJourneyStage, IngestResponse, ServerIdentifyOptions, ServerTrackOptions, TrackerConfig } from '@outlit/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Options for
|
|
5
|
+
* Options for user activation events.
|
|
6
6
|
* Server-side stage events require at least one identifier (fingerprint, email, or userId).
|
|
7
7
|
*/
|
|
8
8
|
interface StageOptions extends ServerIdentity {
|
|
@@ -11,6 +11,20 @@ interface StageOptions extends ServerIdentity {
|
|
|
11
11
|
*/
|
|
12
12
|
properties?: Record<string, string | number | boolean | null>;
|
|
13
13
|
}
|
|
14
|
+
interface UserMethods {
|
|
15
|
+
identify: (options: ServerIdentifyOptions) => void;
|
|
16
|
+
activate: (options: StageOptions) => void;
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Outlit derives ENGAGED from tracked activity. Keep tracking product activity
|
|
19
|
+
* with track() and only send activation manually with user.activate().
|
|
20
|
+
*/
|
|
21
|
+
engaged: (options: StageOptions) => void;
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Outlit derives INACTIVE from tracked activity. Keep tracking product activity
|
|
24
|
+
* with track() and only send activation manually with user.activate().
|
|
25
|
+
*/
|
|
26
|
+
inactive: (options: StageOptions) => void;
|
|
27
|
+
}
|
|
14
28
|
/**
|
|
15
29
|
* Options for billing status events.
|
|
16
30
|
* Public billing calls should use customerId.
|
|
@@ -97,6 +111,7 @@ declare class Outlit {
|
|
|
97
111
|
private flushInterval;
|
|
98
112
|
private isShutdown;
|
|
99
113
|
private fatalTransportError;
|
|
114
|
+
private warnedDerivedJourneyStages;
|
|
100
115
|
constructor(options: OutlitOptions);
|
|
101
116
|
/**
|
|
102
117
|
* Track a custom event.
|
|
@@ -138,12 +153,7 @@ declare class Outlit {
|
|
|
138
153
|
/**
|
|
139
154
|
* User namespace methods for contact journey stages.
|
|
140
155
|
*/
|
|
141
|
-
readonly user:
|
|
142
|
-
identify: (options: ServerIdentifyOptions) => void;
|
|
143
|
-
activate: (options: StageOptions) => void;
|
|
144
|
-
engaged: (options: StageOptions) => void;
|
|
145
|
-
inactive: (options: StageOptions) => void;
|
|
146
|
-
};
|
|
156
|
+
readonly user: UserMethods;
|
|
147
157
|
/**
|
|
148
158
|
* Customer namespace methods for billing status.
|
|
149
159
|
*/
|
|
@@ -180,4 +190,4 @@ declare class Outlit {
|
|
|
180
190
|
private handleFatalTransportError;
|
|
181
191
|
}
|
|
182
192
|
|
|
183
|
-
export { type BillingOptions, Outlit, type OutlitOptions, type StageOptions };
|
|
193
|
+
export { type BillingOptions, Outlit, type OutlitOptions, type StageOptions, type UserMethods };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { CustomerIdentifier, ServerTrackOptions, ServerIdentifyOptions, ServerId
|
|
|
2
2
|
export { ExplicitJourneyStage, IngestResponse, ServerIdentifyOptions, ServerTrackOptions, TrackerConfig } from '@outlit/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Options for
|
|
5
|
+
* Options for user activation events.
|
|
6
6
|
* Server-side stage events require at least one identifier (fingerprint, email, or userId).
|
|
7
7
|
*/
|
|
8
8
|
interface StageOptions extends ServerIdentity {
|
|
@@ -11,6 +11,20 @@ interface StageOptions extends ServerIdentity {
|
|
|
11
11
|
*/
|
|
12
12
|
properties?: Record<string, string | number | boolean | null>;
|
|
13
13
|
}
|
|
14
|
+
interface UserMethods {
|
|
15
|
+
identify: (options: ServerIdentifyOptions) => void;
|
|
16
|
+
activate: (options: StageOptions) => void;
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Outlit derives ENGAGED from tracked activity. Keep tracking product activity
|
|
19
|
+
* with track() and only send activation manually with user.activate().
|
|
20
|
+
*/
|
|
21
|
+
engaged: (options: StageOptions) => void;
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Outlit derives INACTIVE from tracked activity. Keep tracking product activity
|
|
24
|
+
* with track() and only send activation manually with user.activate().
|
|
25
|
+
*/
|
|
26
|
+
inactive: (options: StageOptions) => void;
|
|
27
|
+
}
|
|
14
28
|
/**
|
|
15
29
|
* Options for billing status events.
|
|
16
30
|
* Public billing calls should use customerId.
|
|
@@ -97,6 +111,7 @@ declare class Outlit {
|
|
|
97
111
|
private flushInterval;
|
|
98
112
|
private isShutdown;
|
|
99
113
|
private fatalTransportError;
|
|
114
|
+
private warnedDerivedJourneyStages;
|
|
100
115
|
constructor(options: OutlitOptions);
|
|
101
116
|
/**
|
|
102
117
|
* Track a custom event.
|
|
@@ -138,12 +153,7 @@ declare class Outlit {
|
|
|
138
153
|
/**
|
|
139
154
|
* User namespace methods for contact journey stages.
|
|
140
155
|
*/
|
|
141
|
-
readonly user:
|
|
142
|
-
identify: (options: ServerIdentifyOptions) => void;
|
|
143
|
-
activate: (options: StageOptions) => void;
|
|
144
|
-
engaged: (options: StageOptions) => void;
|
|
145
|
-
inactive: (options: StageOptions) => void;
|
|
146
|
-
};
|
|
156
|
+
readonly user: UserMethods;
|
|
147
157
|
/**
|
|
148
158
|
* Customer namespace methods for billing status.
|
|
149
159
|
*/
|
|
@@ -180,4 +190,4 @@ declare class Outlit {
|
|
|
180
190
|
private handleFatalTransportError;
|
|
181
191
|
}
|
|
182
192
|
|
|
183
|
-
export { type BillingOptions, Outlit, type OutlitOptions, type StageOptions };
|
|
193
|
+
export { type BillingOptions, Outlit, type OutlitOptions, type StageOptions, type UserMethods };
|
package/dist/index.js
CHANGED
|
@@ -162,6 +162,14 @@ var HttpTransport = class {
|
|
|
162
162
|
};
|
|
163
163
|
|
|
164
164
|
// src/client.ts
|
|
165
|
+
function warnIfDerivedJourneyStage(warnedStages, stage) {
|
|
166
|
+
if (stage === "activated") return;
|
|
167
|
+
if (warnedStages.has(stage)) return;
|
|
168
|
+
warnedStages.add(stage);
|
|
169
|
+
console.warn(
|
|
170
|
+
`[Outlit] user.${stage}() is deprecated. Outlit now derives ENGAGED and INACTIVE from tracked activity. Keep using user.activate() for product-specific activation milestones.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
165
173
|
var Outlit = class {
|
|
166
174
|
transport;
|
|
167
175
|
queue;
|
|
@@ -169,6 +177,7 @@ var Outlit = class {
|
|
|
169
177
|
flushInterval;
|
|
170
178
|
isShutdown = false;
|
|
171
179
|
fatalTransportError = null;
|
|
180
|
+
warnedDerivedJourneyStages = /* @__PURE__ */ new Set();
|
|
172
181
|
constructor(options) {
|
|
173
182
|
const apiHost = options.apiHost ?? import_core.DEFAULT_API_HOST;
|
|
174
183
|
this.flushInterval = options.flushInterval ?? 1e4;
|
|
@@ -276,6 +285,7 @@ var Outlit = class {
|
|
|
276
285
|
sendStageEvent(stage, options) {
|
|
277
286
|
this.ensureNotShutdown();
|
|
278
287
|
(0, import_core.validateServerIdentity)(options.fingerprint, options.email, options.userId);
|
|
288
|
+
warnIfDerivedJourneyStage(this.warnedDerivedJourneyStages, stage);
|
|
279
289
|
const event = (0, import_core.buildStageEvent)({
|
|
280
290
|
url: `server://${options.email ?? options.userId ?? options.fingerprint}`,
|
|
281
291
|
stage,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/queue.ts","../src/transport.ts"],"sourcesContent":["// Main export\n\n// Re-export useful types from core\nexport type {\n ExplicitJourneyStage,\n IngestResponse,\n ServerIdentifyOptions,\n ServerTrackOptions,\n TrackerConfig,\n} from \"@outlit/core\"\nexport type { BillingOptions, OutlitOptions, StageOptions } from \"./client\"\nexport { Outlit } from \"./client\"\n","import {\n type BillingStatus,\n buildBillingEvent,\n buildCustomEvent,\n buildIdentifyEvent,\n buildStageEvent,\n type CustomerIdentifier,\n DEFAULT_API_HOST,\n type ExplicitJourneyStage,\n type IngestPayload,\n type ServerIdentifyOptions,\n type ServerIdentity,\n type ServerTrackOptions,\n type TrackerEvent,\n validateCustomerIdentity,\n validateServerIdentity,\n} from \"@outlit/core\"\nimport { EventQueue } from \"./queue\"\nimport { HttpTransport, TransportError } from \"./transport\"\n\n// ============================================\n// STAGE OPTIONS\n// ============================================\n\n/**\n * Options for stage transition events (activate, engaged, inactive).\n * Server-side stage events require at least one identifier (fingerprint, email, or userId).\n */\nexport interface StageOptions extends ServerIdentity {\n /**\n * Optional properties for context.\n */\n properties?: Record<string, string | number | boolean | null>\n}\n\n/**\n * Options for billing status events.\n * Public billing calls should use customerId.\n */\nexport interface BillingOptions extends CustomerIdentifier {\n properties?: Record<string, string | number | boolean | null>\n}\n\n// ============================================\n// OUTLIT CLIENT\n// ============================================\n\nexport interface OutlitOptions {\n /**\n * Your Outlit public key.\n */\n publicKey: string\n\n /**\n * API host URL.\n * @default \"https://app.outlit.ai\"\n */\n apiHost?: string\n\n /**\n * How often to flush events (in milliseconds).\n * @default 10000 (10 seconds)\n */\n flushInterval?: number\n\n /**\n * Maximum number of events to batch before flushing.\n * @default 100\n */\n maxBatchSize?: number\n\n /**\n * Request timeout in milliseconds.\n * @default 10000 (10 seconds)\n */\n timeout?: number\n}\n\n/**\n * Outlit server-side tracking client.\n *\n * Supports tracking with fingerprint (device ID), email, userId, or customer attribution.\n * Use fingerprint for anonymous tracking that can be linked to users later.\n *\n * @example\n * ```typescript\n * import { Outlit } from '@outlit/node'\n *\n * const outlit = new Outlit({ publicKey: 'pk_xxx' })\n *\n * // Track with fingerprint only (anonymous, stored for later backfill)\n * outlit.track({\n * fingerprint: deviceId,\n * eventName: 'page_view',\n * properties: { page: '/pricing' }\n * })\n *\n * // Track with customer attribution only\n * outlit.track({\n * customerId: 'cust_123',\n * eventName: 'account_synced'\n * })\n * // `customerId`-only events stay attributable immediately and can later\n * // link to the email-resolved customer when identify() uses the same customerId.\n *\n * // Track with email (resolves immediately)\n * outlit.track({\n * email: 'user@example.com',\n * eventName: 'subscription_created',\n * properties: { plan: 'pro' }\n * })\n *\n * // Identify user and link fingerprint to email\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123',\n * traits: { name: 'John Doe' }\n * })\n *\n * // Flush before shutdown (important for serverless)\n * await outlit.flush()\n * ```\n */\nexport class Outlit {\n private transport: HttpTransport\n private queue: EventQueue\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushInterval: number\n private isShutdown = false\n private fatalTransportError: TransportError | null = null\n\n constructor(options: OutlitOptions) {\n const apiHost = options.apiHost ?? DEFAULT_API_HOST\n this.flushInterval = options.flushInterval ?? 10000\n\n this.transport = new HttpTransport({\n apiHost,\n publicKey: options.publicKey,\n timeout: options.timeout,\n })\n\n this.queue = new EventQueue({\n maxSize: options.maxBatchSize ?? 100,\n onFlush: async (events) => {\n await this.sendEvents(events)\n },\n })\n\n // Start flush timer\n this.startFlushTimer()\n }\n\n /**\n * Track a custom event.\n *\n * Requires at least one of: `fingerprint`, `email`, `userId`, or `customerId`.\n *\n * - Use `fingerprint` for anonymous tracking (events linked later via identify)\n * - Use `email` or `userId` for user-scoped attribution\n * - Use `customerId` for customer/account/workspace-scoped attribution\n * - If you later call `identify({ email, customerId })`, Outlit can link earlier\n * `customerId`-only events to the customer resolved from email\n * - `userId` is your system-owned user/contact ID\n * - `customerId` is your system-owned customer/account/workspace ID\n *\n * @throws Error if no identity is provided\n */\n track(options: ServerTrackOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId, options.customerId)\n\n const event = buildCustomEvent({\n url: `server://${\n options.email ?? options.userId ?? options.customerId ?? options.fingerprint\n }`,\n timestamp: options.timestamp,\n eventName: options.eventName,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n customerId: options.customerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Identify or update a user.\n *\n * Requires `email` or `userId` to establish user-scoped identity.\n * Optionally include `fingerprint` and customer attribution fields to link them.\n * `userId` is your system-owned user/contact ID and `customerId` is your system-owned\n * customer/account/workspace ID. When `email` and `customerId` are sent together,\n * Outlit can link that account/workspace to the customer resolved from email.\n *\n * This is how you link anonymous fingerprint-tracked events to a real user:\n * ```typescript\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123', // Links this app user ID to the user\n * });\n * ```\n *\n * @throws Error if neither email nor userId is provided\n */\n identify(options: ServerIdentifyOptions): void {\n this.ensureNotShutdown()\n\n // Identify requires user-scoped identity.\n if (!options.email && !options.userId) {\n throw new Error(\n \"identify() requires email or userId to establish user-scoped identity. \" +\n \"Use customerId as the optional account/workspace identifier for linking attribution.\",\n )\n }\n\n const event = buildIdentifyEvent({\n url: `server://${options.email ?? options.userId}`,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n traits: options.traits,\n customerId: options.customerId,\n customerTraits: options.customerTraits,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * User namespace methods for contact journey stages.\n */\n readonly user = {\n identify: (options: ServerIdentifyOptions) => this.identify(options),\n activate: (options: StageOptions) => this.sendStageEvent(\"activated\", options),\n engaged: (options: StageOptions) => this.sendStageEvent(\"engaged\", options),\n inactive: (options: StageOptions) => this.sendStageEvent(\"inactive\", options),\n }\n\n /**\n * Customer namespace methods for billing status.\n */\n readonly customer = {\n trialing: (options: BillingOptions) => this.sendBillingEvent(\"trialing\", options),\n paid: (options: BillingOptions) => this.sendBillingEvent(\"paid\", options),\n churned: (options: BillingOptions) => this.sendBillingEvent(\"churned\", options),\n }\n\n /**\n * Internal method to send a stage event.\n */\n private sendStageEvent(stage: ExplicitJourneyStage, options: StageOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId)\n\n const event = buildStageEvent({\n url: `server://${options.email ?? options.userId ?? options.fingerprint}`,\n stage,\n properties: {\n ...options.properties,\n __fingerprint: options.fingerprint ?? null,\n __email: options.email ?? null,\n __userId: options.userId ?? null,\n },\n })\n\n this.queue.enqueue(event)\n }\n\n private sendBillingEvent(status: BillingStatus, options: BillingOptions): void {\n this.ensureNotShutdown()\n validateCustomerIdentity(options.customerId, options.stripeCustomerId)\n\n const event = buildBillingEvent({\n url: `server://${options.customerId ?? options.stripeCustomerId}`,\n status,\n customerId: options.customerId,\n stripeCustomerId: options.stripeCustomerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Flush all pending events immediately.\n *\n * Important: Call this before your serverless function exits!\n */\n async flush(): Promise<void> {\n await this.queue.flush()\n }\n\n /**\n * Shutdown the client gracefully.\n *\n * Flushes remaining events and stops the flush timer.\n */\n async shutdown(): Promise<void> {\n if (this.isShutdown) return\n\n this.isShutdown = true\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n await this.flush()\n }\n\n /**\n * Get the number of events waiting to be sent.\n */\n get queueSize(): number {\n return this.queue.size\n }\n\n // ============================================\n // INTERNAL METHODS\n // ============================================\n\n private startFlushTimer(): void {\n if (this.flushTimer) return\n\n this.flushTimer = setInterval(() => {\n this.flush().catch((error) => {\n if (this.isNonRetryableTransportError(error)) {\n // Already handled and logged in sendEvents().\n return\n }\n console.error(\"[Outlit] Flush error:\", error)\n })\n }, this.flushInterval)\n\n // Don't block process exit\n if (this.flushTimer.unref) {\n this.flushTimer.unref()\n }\n }\n\n private async sendEvents(events: TrackerEvent[]): Promise<void> {\n if (events.length === 0) return\n if (this.fatalTransportError) return\n\n // For server events, we don't use visitorId - the API resolves identity\n // directly from the event data (email/userId)\n const payload: IngestPayload = {\n source: \"server\",\n events,\n // visitorId is intentionally omitted for server events\n }\n\n try {\n await this.transport.send(payload)\n } catch (error) {\n if (this.isNonRetryableTransportError(error)) {\n this.handleFatalTransportError(error)\n }\n throw error\n }\n }\n\n private ensureNotShutdown(): void {\n if (this.isShutdown) {\n throw new Error(\n \"[Outlit] Client has been shutdown. Create a new instance to continue tracking.\",\n )\n }\n }\n\n private isNonRetryableTransportError(error: unknown): error is TransportError {\n return error instanceof TransportError && error.retryable === false\n }\n\n private handleFatalTransportError(error: TransportError): void {\n if (this.fatalTransportError) return\n\n this.fatalTransportError = error\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n console.error(\n \"[Outlit] Non-retryable ingest error. Automatic flush retries disabled until client restart:\",\n error,\n )\n }\n}\n","import type { TrackerEvent } from \"@outlit/core\"\n\n// ============================================\n// EVENT QUEUE\n// ============================================\n\nfunction isNonRetryableError(error: unknown): boolean {\n if (!error || typeof error !== \"object\") return false\n if (!(\"retryable\" in error)) return false\n\n return (error as { retryable?: unknown }).retryable === false\n}\n\nexport interface QueueOptions {\n maxSize?: number\n onFlush: (events: TrackerEvent[]) => Promise<void>\n}\n\nexport class EventQueue {\n private queue: TrackerEvent[] = []\n private maxSize: number\n private onFlush: (events: TrackerEvent[]) => Promise<void>\n private isFlushing = false\n\n constructor(options: QueueOptions) {\n this.maxSize = options.maxSize ?? 100\n this.onFlush = options.onFlush\n }\n\n /**\n * Add an event to the queue.\n * Triggers flush if queue reaches max size.\n */\n async enqueue(event: TrackerEvent): Promise<void> {\n this.queue.push(event)\n\n if (this.queue.length >= this.maxSize) {\n await this.flush()\n }\n }\n\n /**\n * Flush all events in the queue.\n */\n async flush(): Promise<void> {\n if (this.isFlushing || this.queue.length === 0) return\n\n this.isFlushing = true\n const events = [...this.queue]\n this.queue = []\n\n try {\n await this.onFlush(events)\n } catch (error) {\n // Re-add events for retryable failures only.\n // Non-retryable failures (e.g. invalid config 4xx) are dropped to avoid infinite retry loops.\n if (!isNonRetryableError(error)) {\n this.queue = [...events, ...this.queue]\n }\n throw error\n } finally {\n this.isFlushing = false\n }\n }\n\n /**\n * Get the number of events in the queue.\n */\n get size(): number {\n return this.queue.length\n }\n\n /**\n * Check if the queue is currently flushing.\n */\n get flushing(): boolean {\n return this.isFlushing\n }\n}\n","import type { IngestPayload, IngestResponse } from \"@outlit/core\"\n\n// ============================================\n// HTTP TRANSPORT\n// ============================================\n\ninterface TransportErrorOptions {\n status?: number\n retryable?: boolean\n cause?: unknown\n}\n\n/**\n * Transport-level error enriched with retryability metadata.\n */\nexport class TransportError extends Error {\n readonly status?: number\n readonly retryable: boolean\n\n constructor(message: string, options?: TransportErrorOptions) {\n super(message)\n this.name = \"TransportError\"\n this.status = options?.status\n this.retryable = options?.retryable ?? true\n\n if (options?.cause !== undefined) {\n this.cause = options.cause\n }\n }\n}\n\nfunction isRetryableStatus(status: number): boolean {\n // 429 is rate limiting (transient). Most other 4xx are configuration/input errors.\n return status === 429 || status >= 500\n}\n\nexport interface TransportOptions {\n apiHost: string\n publicKey: string\n timeout?: number\n}\n\nexport class HttpTransport {\n private apiHost: string\n private publicKey: string\n private timeout: number\n\n constructor(options: TransportOptions) {\n this.apiHost = options.apiHost\n this.publicKey = options.publicKey\n this.timeout = options.timeout ?? 10000\n }\n\n /**\n * Send events to the ingest API.\n */\n async send(payload: IngestPayload): Promise<IngestResponse> {\n const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"Unknown error\")\n throw new TransportError(`HTTP ${response.status}: ${errorBody}`, {\n status: response.status,\n retryable: isRetryableStatus(response.status),\n })\n }\n\n return (await response.json()) as IngestResponse\n } catch (error) {\n if (error instanceof TransportError) {\n throw error\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new TransportError(`Request timed out after ${this.timeout}ms`, {\n retryable: true,\n cause: error,\n })\n }\n\n if (error instanceof Error) {\n throw new TransportError(error.message, {\n retryable: true,\n cause: error,\n })\n }\n\n throw new TransportError(\"Unknown transport error\", {\n retryable: true,\n cause: error,\n })\n } finally {\n clearTimeout(timeoutId)\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/queue.ts","../src/transport.ts"],"sourcesContent":["// Main export\n\n// Re-export useful types from core\nexport type {\n ExplicitJourneyStage,\n IngestResponse,\n ServerIdentifyOptions,\n ServerTrackOptions,\n TrackerConfig,\n} from \"@outlit/core\"\nexport type { BillingOptions, OutlitOptions, StageOptions, UserMethods } from \"./client\"\nexport { Outlit } from \"./client\"\n","import {\n type BillingStatus,\n buildBillingEvent,\n buildCustomEvent,\n buildIdentifyEvent,\n buildStageEvent,\n type CustomerIdentifier,\n DEFAULT_API_HOST,\n type ExplicitJourneyStage,\n type IngestPayload,\n type ServerIdentifyOptions,\n type ServerIdentity,\n type ServerTrackOptions,\n type TrackerEvent,\n validateCustomerIdentity,\n validateServerIdentity,\n} from \"@outlit/core\"\nimport { EventQueue } from \"./queue\"\nimport { HttpTransport, TransportError } from \"./transport\"\n\nfunction warnIfDerivedJourneyStage(\n warnedStages: Set<ExplicitJourneyStage>,\n stage: ExplicitJourneyStage,\n): void {\n if (stage === \"activated\") return\n if (warnedStages.has(stage)) return\n warnedStages.add(stage)\n\n console.warn(\n `[Outlit] user.${stage}() is deprecated. Outlit now derives ENGAGED and INACTIVE from tracked activity. Keep using user.activate() for product-specific activation milestones.`,\n )\n}\n\n// ============================================\n// STAGE OPTIONS\n// ============================================\n\n/**\n * Options for user activation events.\n * Server-side stage events require at least one identifier (fingerprint, email, or userId).\n */\nexport interface StageOptions extends ServerIdentity {\n /**\n * Optional properties for context.\n */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface UserMethods {\n identify: (options: ServerIdentifyOptions) => void\n activate: (options: StageOptions) => void\n /**\n * @deprecated Outlit derives ENGAGED from tracked activity. Keep tracking product activity\n * with track() and only send activation manually with user.activate().\n */\n engaged: (options: StageOptions) => void\n /**\n * @deprecated Outlit derives INACTIVE from tracked activity. Keep tracking product activity\n * with track() and only send activation manually with user.activate().\n */\n inactive: (options: StageOptions) => void\n}\n\n/**\n * Options for billing status events.\n * Public billing calls should use customerId.\n */\nexport interface BillingOptions extends CustomerIdentifier {\n properties?: Record<string, string | number | boolean | null>\n}\n\n// ============================================\n// OUTLIT CLIENT\n// ============================================\n\nexport interface OutlitOptions {\n /**\n * Your Outlit public key.\n */\n publicKey: string\n\n /**\n * API host URL.\n * @default \"https://app.outlit.ai\"\n */\n apiHost?: string\n\n /**\n * How often to flush events (in milliseconds).\n * @default 10000 (10 seconds)\n */\n flushInterval?: number\n\n /**\n * Maximum number of events to batch before flushing.\n * @default 100\n */\n maxBatchSize?: number\n\n /**\n * Request timeout in milliseconds.\n * @default 10000 (10 seconds)\n */\n timeout?: number\n}\n\n/**\n * Outlit server-side tracking client.\n *\n * Supports tracking with fingerprint (device ID), email, userId, or customer attribution.\n * Use fingerprint for anonymous tracking that can be linked to users later.\n *\n * @example\n * ```typescript\n * import { Outlit } from '@outlit/node'\n *\n * const outlit = new Outlit({ publicKey: 'pk_xxx' })\n *\n * // Track with fingerprint only (anonymous, stored for later backfill)\n * outlit.track({\n * fingerprint: deviceId,\n * eventName: 'page_view',\n * properties: { page: '/pricing' }\n * })\n *\n * // Track with customer attribution only\n * outlit.track({\n * customerId: 'cust_123',\n * eventName: 'account_synced'\n * })\n * // `customerId`-only events stay attributable immediately and can later\n * // link to the email-resolved customer when identify() uses the same customerId.\n *\n * // Track with email (resolves immediately)\n * outlit.track({\n * email: 'user@example.com',\n * eventName: 'subscription_created',\n * properties: { plan: 'pro' }\n * })\n *\n * // Identify user and link fingerprint to email\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123',\n * traits: { name: 'John Doe' }\n * })\n *\n * // Flush before shutdown (important for serverless)\n * await outlit.flush()\n * ```\n */\nexport class Outlit {\n private transport: HttpTransport\n private queue: EventQueue\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushInterval: number\n private isShutdown = false\n private fatalTransportError: TransportError | null = null\n private warnedDerivedJourneyStages = new Set<ExplicitJourneyStage>()\n\n constructor(options: OutlitOptions) {\n const apiHost = options.apiHost ?? DEFAULT_API_HOST\n this.flushInterval = options.flushInterval ?? 10000\n\n this.transport = new HttpTransport({\n apiHost,\n publicKey: options.publicKey,\n timeout: options.timeout,\n })\n\n this.queue = new EventQueue({\n maxSize: options.maxBatchSize ?? 100,\n onFlush: async (events) => {\n await this.sendEvents(events)\n },\n })\n\n // Start flush timer\n this.startFlushTimer()\n }\n\n /**\n * Track a custom event.\n *\n * Requires at least one of: `fingerprint`, `email`, `userId`, or `customerId`.\n *\n * - Use `fingerprint` for anonymous tracking (events linked later via identify)\n * - Use `email` or `userId` for user-scoped attribution\n * - Use `customerId` for customer/account/workspace-scoped attribution\n * - If you later call `identify({ email, customerId })`, Outlit can link earlier\n * `customerId`-only events to the customer resolved from email\n * - `userId` is your system-owned user/contact ID\n * - `customerId` is your system-owned customer/account/workspace ID\n *\n * @throws Error if no identity is provided\n */\n track(options: ServerTrackOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId, options.customerId)\n\n const event = buildCustomEvent({\n url: `server://${\n options.email ?? options.userId ?? options.customerId ?? options.fingerprint\n }`,\n timestamp: options.timestamp,\n eventName: options.eventName,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n customerId: options.customerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Identify or update a user.\n *\n * Requires `email` or `userId` to establish user-scoped identity.\n * Optionally include `fingerprint` and customer attribution fields to link them.\n * `userId` is your system-owned user/contact ID and `customerId` is your system-owned\n * customer/account/workspace ID. When `email` and `customerId` are sent together,\n * Outlit can link that account/workspace to the customer resolved from email.\n *\n * This is how you link anonymous fingerprint-tracked events to a real user:\n * ```typescript\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123', // Links this app user ID to the user\n * });\n * ```\n *\n * @throws Error if neither email nor userId is provided\n */\n identify(options: ServerIdentifyOptions): void {\n this.ensureNotShutdown()\n\n // Identify requires user-scoped identity.\n if (!options.email && !options.userId) {\n throw new Error(\n \"identify() requires email or userId to establish user-scoped identity. \" +\n \"Use customerId as the optional account/workspace identifier for linking attribution.\",\n )\n }\n\n const event = buildIdentifyEvent({\n url: `server://${options.email ?? options.userId}`,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n traits: options.traits,\n customerId: options.customerId,\n customerTraits: options.customerTraits,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * User namespace methods for contact journey stages.\n */\n readonly user: UserMethods = {\n identify: (options: ServerIdentifyOptions) => this.identify(options),\n activate: (options: StageOptions) => this.sendStageEvent(\"activated\", options),\n engaged: (options: StageOptions) => this.sendStageEvent(\"engaged\", options),\n inactive: (options: StageOptions) => this.sendStageEvent(\"inactive\", options),\n }\n\n /**\n * Customer namespace methods for billing status.\n */\n readonly customer = {\n trialing: (options: BillingOptions) => this.sendBillingEvent(\"trialing\", options),\n paid: (options: BillingOptions) => this.sendBillingEvent(\"paid\", options),\n churned: (options: BillingOptions) => this.sendBillingEvent(\"churned\", options),\n }\n\n /**\n * Internal method to send a stage event.\n */\n private sendStageEvent(stage: ExplicitJourneyStage, options: StageOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId)\n warnIfDerivedJourneyStage(this.warnedDerivedJourneyStages, stage)\n\n const event = buildStageEvent({\n url: `server://${options.email ?? options.userId ?? options.fingerprint}`,\n stage,\n properties: {\n ...options.properties,\n __fingerprint: options.fingerprint ?? null,\n __email: options.email ?? null,\n __userId: options.userId ?? null,\n },\n })\n\n this.queue.enqueue(event)\n }\n\n private sendBillingEvent(status: BillingStatus, options: BillingOptions): void {\n this.ensureNotShutdown()\n validateCustomerIdentity(options.customerId, options.stripeCustomerId)\n\n const event = buildBillingEvent({\n url: `server://${options.customerId ?? options.stripeCustomerId}`,\n status,\n customerId: options.customerId,\n stripeCustomerId: options.stripeCustomerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Flush all pending events immediately.\n *\n * Important: Call this before your serverless function exits!\n */\n async flush(): Promise<void> {\n await this.queue.flush()\n }\n\n /**\n * Shutdown the client gracefully.\n *\n * Flushes remaining events and stops the flush timer.\n */\n async shutdown(): Promise<void> {\n if (this.isShutdown) return\n\n this.isShutdown = true\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n await this.flush()\n }\n\n /**\n * Get the number of events waiting to be sent.\n */\n get queueSize(): number {\n return this.queue.size\n }\n\n // ============================================\n // INTERNAL METHODS\n // ============================================\n\n private startFlushTimer(): void {\n if (this.flushTimer) return\n\n this.flushTimer = setInterval(() => {\n this.flush().catch((error) => {\n if (this.isNonRetryableTransportError(error)) {\n // Already handled and logged in sendEvents().\n return\n }\n console.error(\"[Outlit] Flush error:\", error)\n })\n }, this.flushInterval)\n\n // Don't block process exit\n if (this.flushTimer.unref) {\n this.flushTimer.unref()\n }\n }\n\n private async sendEvents(events: TrackerEvent[]): Promise<void> {\n if (events.length === 0) return\n if (this.fatalTransportError) return\n\n // For server events, we don't use visitorId - the API resolves identity\n // directly from the event data (email/userId)\n const payload: IngestPayload = {\n source: \"server\",\n events,\n // visitorId is intentionally omitted for server events\n }\n\n try {\n await this.transport.send(payload)\n } catch (error) {\n if (this.isNonRetryableTransportError(error)) {\n this.handleFatalTransportError(error)\n }\n throw error\n }\n }\n\n private ensureNotShutdown(): void {\n if (this.isShutdown) {\n throw new Error(\n \"[Outlit] Client has been shutdown. Create a new instance to continue tracking.\",\n )\n }\n }\n\n private isNonRetryableTransportError(error: unknown): error is TransportError {\n return error instanceof TransportError && error.retryable === false\n }\n\n private handleFatalTransportError(error: TransportError): void {\n if (this.fatalTransportError) return\n\n this.fatalTransportError = error\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n console.error(\n \"[Outlit] Non-retryable ingest error. Automatic flush retries disabled until client restart:\",\n error,\n )\n }\n}\n","import type { TrackerEvent } from \"@outlit/core\"\n\n// ============================================\n// EVENT QUEUE\n// ============================================\n\nfunction isNonRetryableError(error: unknown): boolean {\n if (!error || typeof error !== \"object\") return false\n if (!(\"retryable\" in error)) return false\n\n return (error as { retryable?: unknown }).retryable === false\n}\n\nexport interface QueueOptions {\n maxSize?: number\n onFlush: (events: TrackerEvent[]) => Promise<void>\n}\n\nexport class EventQueue {\n private queue: TrackerEvent[] = []\n private maxSize: number\n private onFlush: (events: TrackerEvent[]) => Promise<void>\n private isFlushing = false\n\n constructor(options: QueueOptions) {\n this.maxSize = options.maxSize ?? 100\n this.onFlush = options.onFlush\n }\n\n /**\n * Add an event to the queue.\n * Triggers flush if queue reaches max size.\n */\n async enqueue(event: TrackerEvent): Promise<void> {\n this.queue.push(event)\n\n if (this.queue.length >= this.maxSize) {\n await this.flush()\n }\n }\n\n /**\n * Flush all events in the queue.\n */\n async flush(): Promise<void> {\n if (this.isFlushing || this.queue.length === 0) return\n\n this.isFlushing = true\n const events = [...this.queue]\n this.queue = []\n\n try {\n await this.onFlush(events)\n } catch (error) {\n // Re-add events for retryable failures only.\n // Non-retryable failures (e.g. invalid config 4xx) are dropped to avoid infinite retry loops.\n if (!isNonRetryableError(error)) {\n this.queue = [...events, ...this.queue]\n }\n throw error\n } finally {\n this.isFlushing = false\n }\n }\n\n /**\n * Get the number of events in the queue.\n */\n get size(): number {\n return this.queue.length\n }\n\n /**\n * Check if the queue is currently flushing.\n */\n get flushing(): boolean {\n return this.isFlushing\n }\n}\n","import type { IngestPayload, IngestResponse } from \"@outlit/core\"\n\n// ============================================\n// HTTP TRANSPORT\n// ============================================\n\ninterface TransportErrorOptions {\n status?: number\n retryable?: boolean\n cause?: unknown\n}\n\n/**\n * Transport-level error enriched with retryability metadata.\n */\nexport class TransportError extends Error {\n readonly status?: number\n readonly retryable: boolean\n\n constructor(message: string, options?: TransportErrorOptions) {\n super(message)\n this.name = \"TransportError\"\n this.status = options?.status\n this.retryable = options?.retryable ?? true\n\n if (options?.cause !== undefined) {\n this.cause = options.cause\n }\n }\n}\n\nfunction isRetryableStatus(status: number): boolean {\n // 429 is rate limiting (transient). Most other 4xx are configuration/input errors.\n return status === 429 || status >= 500\n}\n\nexport interface TransportOptions {\n apiHost: string\n publicKey: string\n timeout?: number\n}\n\nexport class HttpTransport {\n private apiHost: string\n private publicKey: string\n private timeout: number\n\n constructor(options: TransportOptions) {\n this.apiHost = options.apiHost\n this.publicKey = options.publicKey\n this.timeout = options.timeout ?? 10000\n }\n\n /**\n * Send events to the ingest API.\n */\n async send(payload: IngestPayload): Promise<IngestResponse> {\n const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"Unknown error\")\n throw new TransportError(`HTTP ${response.status}: ${errorBody}`, {\n status: response.status,\n retryable: isRetryableStatus(response.status),\n })\n }\n\n return (await response.json()) as IngestResponse\n } catch (error) {\n if (error instanceof TransportError) {\n throw error\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new TransportError(`Request timed out after ${this.timeout}ms`, {\n retryable: true,\n cause: error,\n })\n }\n\n if (error instanceof Error) {\n throw new TransportError(error.message, {\n retryable: true,\n cause: error,\n })\n }\n\n throw new TransportError(\"Unknown transport error\", {\n retryable: true,\n cause: error,\n })\n } finally {\n clearTimeout(timeoutId)\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -144,6 +144,14 @@ var HttpTransport = class {
|
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
// src/client.ts
|
|
147
|
+
function warnIfDerivedJourneyStage(warnedStages, stage) {
|
|
148
|
+
if (stage === "activated") return;
|
|
149
|
+
if (warnedStages.has(stage)) return;
|
|
150
|
+
warnedStages.add(stage);
|
|
151
|
+
console.warn(
|
|
152
|
+
`[Outlit] user.${stage}() is deprecated. Outlit now derives ENGAGED and INACTIVE from tracked activity. Keep using user.activate() for product-specific activation milestones.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
147
155
|
var Outlit = class {
|
|
148
156
|
transport;
|
|
149
157
|
queue;
|
|
@@ -151,6 +159,7 @@ var Outlit = class {
|
|
|
151
159
|
flushInterval;
|
|
152
160
|
isShutdown = false;
|
|
153
161
|
fatalTransportError = null;
|
|
162
|
+
warnedDerivedJourneyStages = /* @__PURE__ */ new Set();
|
|
154
163
|
constructor(options) {
|
|
155
164
|
const apiHost = options.apiHost ?? DEFAULT_API_HOST;
|
|
156
165
|
this.flushInterval = options.flushInterval ?? 1e4;
|
|
@@ -258,6 +267,7 @@ var Outlit = class {
|
|
|
258
267
|
sendStageEvent(stage, options) {
|
|
259
268
|
this.ensureNotShutdown();
|
|
260
269
|
validateServerIdentity(options.fingerprint, options.email, options.userId);
|
|
270
|
+
warnIfDerivedJourneyStage(this.warnedDerivedJourneyStages, stage);
|
|
261
271
|
const event = buildStageEvent({
|
|
262
272
|
url: `server://${options.email ?? options.userId ?? options.fingerprint}`,
|
|
263
273
|
stage,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/queue.ts","../src/transport.ts"],"sourcesContent":["import {\n type BillingStatus,\n buildBillingEvent,\n buildCustomEvent,\n buildIdentifyEvent,\n buildStageEvent,\n type CustomerIdentifier,\n DEFAULT_API_HOST,\n type ExplicitJourneyStage,\n type IngestPayload,\n type ServerIdentifyOptions,\n type ServerIdentity,\n type ServerTrackOptions,\n type TrackerEvent,\n validateCustomerIdentity,\n validateServerIdentity,\n} from \"@outlit/core\"\nimport { EventQueue } from \"./queue\"\nimport { HttpTransport, TransportError } from \"./transport\"\n\n// ============================================\n// STAGE OPTIONS\n// ============================================\n\n/**\n * Options for stage transition events (activate, engaged, inactive).\n * Server-side stage events require at least one identifier (fingerprint, email, or userId).\n */\nexport interface StageOptions extends ServerIdentity {\n /**\n * Optional properties for context.\n */\n properties?: Record<string, string | number | boolean | null>\n}\n\n/**\n * Options for billing status events.\n * Public billing calls should use customerId.\n */\nexport interface BillingOptions extends CustomerIdentifier {\n properties?: Record<string, string | number | boolean | null>\n}\n\n// ============================================\n// OUTLIT CLIENT\n// ============================================\n\nexport interface OutlitOptions {\n /**\n * Your Outlit public key.\n */\n publicKey: string\n\n /**\n * API host URL.\n * @default \"https://app.outlit.ai\"\n */\n apiHost?: string\n\n /**\n * How often to flush events (in milliseconds).\n * @default 10000 (10 seconds)\n */\n flushInterval?: number\n\n /**\n * Maximum number of events to batch before flushing.\n * @default 100\n */\n maxBatchSize?: number\n\n /**\n * Request timeout in milliseconds.\n * @default 10000 (10 seconds)\n */\n timeout?: number\n}\n\n/**\n * Outlit server-side tracking client.\n *\n * Supports tracking with fingerprint (device ID), email, userId, or customer attribution.\n * Use fingerprint for anonymous tracking that can be linked to users later.\n *\n * @example\n * ```typescript\n * import { Outlit } from '@outlit/node'\n *\n * const outlit = new Outlit({ publicKey: 'pk_xxx' })\n *\n * // Track with fingerprint only (anonymous, stored for later backfill)\n * outlit.track({\n * fingerprint: deviceId,\n * eventName: 'page_view',\n * properties: { page: '/pricing' }\n * })\n *\n * // Track with customer attribution only\n * outlit.track({\n * customerId: 'cust_123',\n * eventName: 'account_synced'\n * })\n * // `customerId`-only events stay attributable immediately and can later\n * // link to the email-resolved customer when identify() uses the same customerId.\n *\n * // Track with email (resolves immediately)\n * outlit.track({\n * email: 'user@example.com',\n * eventName: 'subscription_created',\n * properties: { plan: 'pro' }\n * })\n *\n * // Identify user and link fingerprint to email\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123',\n * traits: { name: 'John Doe' }\n * })\n *\n * // Flush before shutdown (important for serverless)\n * await outlit.flush()\n * ```\n */\nexport class Outlit {\n private transport: HttpTransport\n private queue: EventQueue\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushInterval: number\n private isShutdown = false\n private fatalTransportError: TransportError | null = null\n\n constructor(options: OutlitOptions) {\n const apiHost = options.apiHost ?? DEFAULT_API_HOST\n this.flushInterval = options.flushInterval ?? 10000\n\n this.transport = new HttpTransport({\n apiHost,\n publicKey: options.publicKey,\n timeout: options.timeout,\n })\n\n this.queue = new EventQueue({\n maxSize: options.maxBatchSize ?? 100,\n onFlush: async (events) => {\n await this.sendEvents(events)\n },\n })\n\n // Start flush timer\n this.startFlushTimer()\n }\n\n /**\n * Track a custom event.\n *\n * Requires at least one of: `fingerprint`, `email`, `userId`, or `customerId`.\n *\n * - Use `fingerprint` for anonymous tracking (events linked later via identify)\n * - Use `email` or `userId` for user-scoped attribution\n * - Use `customerId` for customer/account/workspace-scoped attribution\n * - If you later call `identify({ email, customerId })`, Outlit can link earlier\n * `customerId`-only events to the customer resolved from email\n * - `userId` is your system-owned user/contact ID\n * - `customerId` is your system-owned customer/account/workspace ID\n *\n * @throws Error if no identity is provided\n */\n track(options: ServerTrackOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId, options.customerId)\n\n const event = buildCustomEvent({\n url: `server://${\n options.email ?? options.userId ?? options.customerId ?? options.fingerprint\n }`,\n timestamp: options.timestamp,\n eventName: options.eventName,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n customerId: options.customerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Identify or update a user.\n *\n * Requires `email` or `userId` to establish user-scoped identity.\n * Optionally include `fingerprint` and customer attribution fields to link them.\n * `userId` is your system-owned user/contact ID and `customerId` is your system-owned\n * customer/account/workspace ID. When `email` and `customerId` are sent together,\n * Outlit can link that account/workspace to the customer resolved from email.\n *\n * This is how you link anonymous fingerprint-tracked events to a real user:\n * ```typescript\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123', // Links this app user ID to the user\n * });\n * ```\n *\n * @throws Error if neither email nor userId is provided\n */\n identify(options: ServerIdentifyOptions): void {\n this.ensureNotShutdown()\n\n // Identify requires user-scoped identity.\n if (!options.email && !options.userId) {\n throw new Error(\n \"identify() requires email or userId to establish user-scoped identity. \" +\n \"Use customerId as the optional account/workspace identifier for linking attribution.\",\n )\n }\n\n const event = buildIdentifyEvent({\n url: `server://${options.email ?? options.userId}`,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n traits: options.traits,\n customerId: options.customerId,\n customerTraits: options.customerTraits,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * User namespace methods for contact journey stages.\n */\n readonly user = {\n identify: (options: ServerIdentifyOptions) => this.identify(options),\n activate: (options: StageOptions) => this.sendStageEvent(\"activated\", options),\n engaged: (options: StageOptions) => this.sendStageEvent(\"engaged\", options),\n inactive: (options: StageOptions) => this.sendStageEvent(\"inactive\", options),\n }\n\n /**\n * Customer namespace methods for billing status.\n */\n readonly customer = {\n trialing: (options: BillingOptions) => this.sendBillingEvent(\"trialing\", options),\n paid: (options: BillingOptions) => this.sendBillingEvent(\"paid\", options),\n churned: (options: BillingOptions) => this.sendBillingEvent(\"churned\", options),\n }\n\n /**\n * Internal method to send a stage event.\n */\n private sendStageEvent(stage: ExplicitJourneyStage, options: StageOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId)\n\n const event = buildStageEvent({\n url: `server://${options.email ?? options.userId ?? options.fingerprint}`,\n stage,\n properties: {\n ...options.properties,\n __fingerprint: options.fingerprint ?? null,\n __email: options.email ?? null,\n __userId: options.userId ?? null,\n },\n })\n\n this.queue.enqueue(event)\n }\n\n private sendBillingEvent(status: BillingStatus, options: BillingOptions): void {\n this.ensureNotShutdown()\n validateCustomerIdentity(options.customerId, options.stripeCustomerId)\n\n const event = buildBillingEvent({\n url: `server://${options.customerId ?? options.stripeCustomerId}`,\n status,\n customerId: options.customerId,\n stripeCustomerId: options.stripeCustomerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Flush all pending events immediately.\n *\n * Important: Call this before your serverless function exits!\n */\n async flush(): Promise<void> {\n await this.queue.flush()\n }\n\n /**\n * Shutdown the client gracefully.\n *\n * Flushes remaining events and stops the flush timer.\n */\n async shutdown(): Promise<void> {\n if (this.isShutdown) return\n\n this.isShutdown = true\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n await this.flush()\n }\n\n /**\n * Get the number of events waiting to be sent.\n */\n get queueSize(): number {\n return this.queue.size\n }\n\n // ============================================\n // INTERNAL METHODS\n // ============================================\n\n private startFlushTimer(): void {\n if (this.flushTimer) return\n\n this.flushTimer = setInterval(() => {\n this.flush().catch((error) => {\n if (this.isNonRetryableTransportError(error)) {\n // Already handled and logged in sendEvents().\n return\n }\n console.error(\"[Outlit] Flush error:\", error)\n })\n }, this.flushInterval)\n\n // Don't block process exit\n if (this.flushTimer.unref) {\n this.flushTimer.unref()\n }\n }\n\n private async sendEvents(events: TrackerEvent[]): Promise<void> {\n if (events.length === 0) return\n if (this.fatalTransportError) return\n\n // For server events, we don't use visitorId - the API resolves identity\n // directly from the event data (email/userId)\n const payload: IngestPayload = {\n source: \"server\",\n events,\n // visitorId is intentionally omitted for server events\n }\n\n try {\n await this.transport.send(payload)\n } catch (error) {\n if (this.isNonRetryableTransportError(error)) {\n this.handleFatalTransportError(error)\n }\n throw error\n }\n }\n\n private ensureNotShutdown(): void {\n if (this.isShutdown) {\n throw new Error(\n \"[Outlit] Client has been shutdown. Create a new instance to continue tracking.\",\n )\n }\n }\n\n private isNonRetryableTransportError(error: unknown): error is TransportError {\n return error instanceof TransportError && error.retryable === false\n }\n\n private handleFatalTransportError(error: TransportError): void {\n if (this.fatalTransportError) return\n\n this.fatalTransportError = error\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n console.error(\n \"[Outlit] Non-retryable ingest error. Automatic flush retries disabled until client restart:\",\n error,\n )\n }\n}\n","import type { TrackerEvent } from \"@outlit/core\"\n\n// ============================================\n// EVENT QUEUE\n// ============================================\n\nfunction isNonRetryableError(error: unknown): boolean {\n if (!error || typeof error !== \"object\") return false\n if (!(\"retryable\" in error)) return false\n\n return (error as { retryable?: unknown }).retryable === false\n}\n\nexport interface QueueOptions {\n maxSize?: number\n onFlush: (events: TrackerEvent[]) => Promise<void>\n}\n\nexport class EventQueue {\n private queue: TrackerEvent[] = []\n private maxSize: number\n private onFlush: (events: TrackerEvent[]) => Promise<void>\n private isFlushing = false\n\n constructor(options: QueueOptions) {\n this.maxSize = options.maxSize ?? 100\n this.onFlush = options.onFlush\n }\n\n /**\n * Add an event to the queue.\n * Triggers flush if queue reaches max size.\n */\n async enqueue(event: TrackerEvent): Promise<void> {\n this.queue.push(event)\n\n if (this.queue.length >= this.maxSize) {\n await this.flush()\n }\n }\n\n /**\n * Flush all events in the queue.\n */\n async flush(): Promise<void> {\n if (this.isFlushing || this.queue.length === 0) return\n\n this.isFlushing = true\n const events = [...this.queue]\n this.queue = []\n\n try {\n await this.onFlush(events)\n } catch (error) {\n // Re-add events for retryable failures only.\n // Non-retryable failures (e.g. invalid config 4xx) are dropped to avoid infinite retry loops.\n if (!isNonRetryableError(error)) {\n this.queue = [...events, ...this.queue]\n }\n throw error\n } finally {\n this.isFlushing = false\n }\n }\n\n /**\n * Get the number of events in the queue.\n */\n get size(): number {\n return this.queue.length\n }\n\n /**\n * Check if the queue is currently flushing.\n */\n get flushing(): boolean {\n return this.isFlushing\n }\n}\n","import type { IngestPayload, IngestResponse } from \"@outlit/core\"\n\n// ============================================\n// HTTP TRANSPORT\n// ============================================\n\ninterface TransportErrorOptions {\n status?: number\n retryable?: boolean\n cause?: unknown\n}\n\n/**\n * Transport-level error enriched with retryability metadata.\n */\nexport class TransportError extends Error {\n readonly status?: number\n readonly retryable: boolean\n\n constructor(message: string, options?: TransportErrorOptions) {\n super(message)\n this.name = \"TransportError\"\n this.status = options?.status\n this.retryable = options?.retryable ?? true\n\n if (options?.cause !== undefined) {\n this.cause = options.cause\n }\n }\n}\n\nfunction isRetryableStatus(status: number): boolean {\n // 429 is rate limiting (transient). Most other 4xx are configuration/input errors.\n return status === 429 || status >= 500\n}\n\nexport interface TransportOptions {\n apiHost: string\n publicKey: string\n timeout?: number\n}\n\nexport class HttpTransport {\n private apiHost: string\n private publicKey: string\n private timeout: number\n\n constructor(options: TransportOptions) {\n this.apiHost = options.apiHost\n this.publicKey = options.publicKey\n this.timeout = options.timeout ?? 10000\n }\n\n /**\n * Send events to the ingest API.\n */\n async send(payload: IngestPayload): Promise<IngestResponse> {\n const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"Unknown error\")\n throw new TransportError(`HTTP ${response.status}: ${errorBody}`, {\n status: response.status,\n retryable: isRetryableStatus(response.status),\n })\n }\n\n return (await response.json()) as IngestResponse\n } catch (error) {\n if (error instanceof TransportError) {\n throw error\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new TransportError(`Request timed out after ${this.timeout}ms`, {\n retryable: true,\n cause: error,\n })\n }\n\n if (error instanceof Error) {\n throw new TransportError(error.message, {\n retryable: true,\n cause: error,\n })\n }\n\n throw new TransportError(\"Unknown transport error\", {\n retryable: true,\n cause: error,\n })\n } finally {\n clearTimeout(timeoutId)\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/queue.ts","../src/transport.ts"],"sourcesContent":["import {\n type BillingStatus,\n buildBillingEvent,\n buildCustomEvent,\n buildIdentifyEvent,\n buildStageEvent,\n type CustomerIdentifier,\n DEFAULT_API_HOST,\n type ExplicitJourneyStage,\n type IngestPayload,\n type ServerIdentifyOptions,\n type ServerIdentity,\n type ServerTrackOptions,\n type TrackerEvent,\n validateCustomerIdentity,\n validateServerIdentity,\n} from \"@outlit/core\"\nimport { EventQueue } from \"./queue\"\nimport { HttpTransport, TransportError } from \"./transport\"\n\nfunction warnIfDerivedJourneyStage(\n warnedStages: Set<ExplicitJourneyStage>,\n stage: ExplicitJourneyStage,\n): void {\n if (stage === \"activated\") return\n if (warnedStages.has(stage)) return\n warnedStages.add(stage)\n\n console.warn(\n `[Outlit] user.${stage}() is deprecated. Outlit now derives ENGAGED and INACTIVE from tracked activity. Keep using user.activate() for product-specific activation milestones.`,\n )\n}\n\n// ============================================\n// STAGE OPTIONS\n// ============================================\n\n/**\n * Options for user activation events.\n * Server-side stage events require at least one identifier (fingerprint, email, or userId).\n */\nexport interface StageOptions extends ServerIdentity {\n /**\n * Optional properties for context.\n */\n properties?: Record<string, string | number | boolean | null>\n}\n\nexport interface UserMethods {\n identify: (options: ServerIdentifyOptions) => void\n activate: (options: StageOptions) => void\n /**\n * @deprecated Outlit derives ENGAGED from tracked activity. Keep tracking product activity\n * with track() and only send activation manually with user.activate().\n */\n engaged: (options: StageOptions) => void\n /**\n * @deprecated Outlit derives INACTIVE from tracked activity. Keep tracking product activity\n * with track() and only send activation manually with user.activate().\n */\n inactive: (options: StageOptions) => void\n}\n\n/**\n * Options for billing status events.\n * Public billing calls should use customerId.\n */\nexport interface BillingOptions extends CustomerIdentifier {\n properties?: Record<string, string | number | boolean | null>\n}\n\n// ============================================\n// OUTLIT CLIENT\n// ============================================\n\nexport interface OutlitOptions {\n /**\n * Your Outlit public key.\n */\n publicKey: string\n\n /**\n * API host URL.\n * @default \"https://app.outlit.ai\"\n */\n apiHost?: string\n\n /**\n * How often to flush events (in milliseconds).\n * @default 10000 (10 seconds)\n */\n flushInterval?: number\n\n /**\n * Maximum number of events to batch before flushing.\n * @default 100\n */\n maxBatchSize?: number\n\n /**\n * Request timeout in milliseconds.\n * @default 10000 (10 seconds)\n */\n timeout?: number\n}\n\n/**\n * Outlit server-side tracking client.\n *\n * Supports tracking with fingerprint (device ID), email, userId, or customer attribution.\n * Use fingerprint for anonymous tracking that can be linked to users later.\n *\n * @example\n * ```typescript\n * import { Outlit } from '@outlit/node'\n *\n * const outlit = new Outlit({ publicKey: 'pk_xxx' })\n *\n * // Track with fingerprint only (anonymous, stored for later backfill)\n * outlit.track({\n * fingerprint: deviceId,\n * eventName: 'page_view',\n * properties: { page: '/pricing' }\n * })\n *\n * // Track with customer attribution only\n * outlit.track({\n * customerId: 'cust_123',\n * eventName: 'account_synced'\n * })\n * // `customerId`-only events stay attributable immediately and can later\n * // link to the email-resolved customer when identify() uses the same customerId.\n *\n * // Track with email (resolves immediately)\n * outlit.track({\n * email: 'user@example.com',\n * eventName: 'subscription_created',\n * properties: { plan: 'pro' }\n * })\n *\n * // Identify user and link fingerprint to email\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123',\n * traits: { name: 'John Doe' }\n * })\n *\n * // Flush before shutdown (important for serverless)\n * await outlit.flush()\n * ```\n */\nexport class Outlit {\n private transport: HttpTransport\n private queue: EventQueue\n private flushTimer: ReturnType<typeof setInterval> | null = null\n private flushInterval: number\n private isShutdown = false\n private fatalTransportError: TransportError | null = null\n private warnedDerivedJourneyStages = new Set<ExplicitJourneyStage>()\n\n constructor(options: OutlitOptions) {\n const apiHost = options.apiHost ?? DEFAULT_API_HOST\n this.flushInterval = options.flushInterval ?? 10000\n\n this.transport = new HttpTransport({\n apiHost,\n publicKey: options.publicKey,\n timeout: options.timeout,\n })\n\n this.queue = new EventQueue({\n maxSize: options.maxBatchSize ?? 100,\n onFlush: async (events) => {\n await this.sendEvents(events)\n },\n })\n\n // Start flush timer\n this.startFlushTimer()\n }\n\n /**\n * Track a custom event.\n *\n * Requires at least one of: `fingerprint`, `email`, `userId`, or `customerId`.\n *\n * - Use `fingerprint` for anonymous tracking (events linked later via identify)\n * - Use `email` or `userId` for user-scoped attribution\n * - Use `customerId` for customer/account/workspace-scoped attribution\n * - If you later call `identify({ email, customerId })`, Outlit can link earlier\n * `customerId`-only events to the customer resolved from email\n * - `userId` is your system-owned user/contact ID\n * - `customerId` is your system-owned customer/account/workspace ID\n *\n * @throws Error if no identity is provided\n */\n track(options: ServerTrackOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId, options.customerId)\n\n const event = buildCustomEvent({\n url: `server://${\n options.email ?? options.userId ?? options.customerId ?? options.fingerprint\n }`,\n timestamp: options.timestamp,\n eventName: options.eventName,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n customerId: options.customerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Identify or update a user.\n *\n * Requires `email` or `userId` to establish user-scoped identity.\n * Optionally include `fingerprint` and customer attribution fields to link them.\n * `userId` is your system-owned user/contact ID and `customerId` is your system-owned\n * customer/account/workspace ID. When `email` and `customerId` are sent together,\n * Outlit can link that account/workspace to the customer resolved from email.\n *\n * This is how you link anonymous fingerprint-tracked events to a real user:\n * ```typescript\n * outlit.identify({\n * email: 'user@example.com',\n * fingerprint: deviceId, // Links this device to the user\n * userId: 'usr_123', // Links this app user ID to the user\n * });\n * ```\n *\n * @throws Error if neither email nor userId is provided\n */\n identify(options: ServerIdentifyOptions): void {\n this.ensureNotShutdown()\n\n // Identify requires user-scoped identity.\n if (!options.email && !options.userId) {\n throw new Error(\n \"identify() requires email or userId to establish user-scoped identity. \" +\n \"Use customerId as the optional account/workspace identifier for linking attribution.\",\n )\n }\n\n const event = buildIdentifyEvent({\n url: `server://${options.email ?? options.userId}`,\n email: options.email,\n userId: options.userId,\n fingerprint: options.fingerprint,\n traits: options.traits,\n customerId: options.customerId,\n customerTraits: options.customerTraits,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * User namespace methods for contact journey stages.\n */\n readonly user: UserMethods = {\n identify: (options: ServerIdentifyOptions) => this.identify(options),\n activate: (options: StageOptions) => this.sendStageEvent(\"activated\", options),\n engaged: (options: StageOptions) => this.sendStageEvent(\"engaged\", options),\n inactive: (options: StageOptions) => this.sendStageEvent(\"inactive\", options),\n }\n\n /**\n * Customer namespace methods for billing status.\n */\n readonly customer = {\n trialing: (options: BillingOptions) => this.sendBillingEvent(\"trialing\", options),\n paid: (options: BillingOptions) => this.sendBillingEvent(\"paid\", options),\n churned: (options: BillingOptions) => this.sendBillingEvent(\"churned\", options),\n }\n\n /**\n * Internal method to send a stage event.\n */\n private sendStageEvent(stage: ExplicitJourneyStage, options: StageOptions): void {\n this.ensureNotShutdown()\n validateServerIdentity(options.fingerprint, options.email, options.userId)\n warnIfDerivedJourneyStage(this.warnedDerivedJourneyStages, stage)\n\n const event = buildStageEvent({\n url: `server://${options.email ?? options.userId ?? options.fingerprint}`,\n stage,\n properties: {\n ...options.properties,\n __fingerprint: options.fingerprint ?? null,\n __email: options.email ?? null,\n __userId: options.userId ?? null,\n },\n })\n\n this.queue.enqueue(event)\n }\n\n private sendBillingEvent(status: BillingStatus, options: BillingOptions): void {\n this.ensureNotShutdown()\n validateCustomerIdentity(options.customerId, options.stripeCustomerId)\n\n const event = buildBillingEvent({\n url: `server://${options.customerId ?? options.stripeCustomerId}`,\n status,\n customerId: options.customerId,\n stripeCustomerId: options.stripeCustomerId,\n properties: options.properties,\n })\n\n this.queue.enqueue(event)\n }\n\n /**\n * Flush all pending events immediately.\n *\n * Important: Call this before your serverless function exits!\n */\n async flush(): Promise<void> {\n await this.queue.flush()\n }\n\n /**\n * Shutdown the client gracefully.\n *\n * Flushes remaining events and stops the flush timer.\n */\n async shutdown(): Promise<void> {\n if (this.isShutdown) return\n\n this.isShutdown = true\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n await this.flush()\n }\n\n /**\n * Get the number of events waiting to be sent.\n */\n get queueSize(): number {\n return this.queue.size\n }\n\n // ============================================\n // INTERNAL METHODS\n // ============================================\n\n private startFlushTimer(): void {\n if (this.flushTimer) return\n\n this.flushTimer = setInterval(() => {\n this.flush().catch((error) => {\n if (this.isNonRetryableTransportError(error)) {\n // Already handled and logged in sendEvents().\n return\n }\n console.error(\"[Outlit] Flush error:\", error)\n })\n }, this.flushInterval)\n\n // Don't block process exit\n if (this.flushTimer.unref) {\n this.flushTimer.unref()\n }\n }\n\n private async sendEvents(events: TrackerEvent[]): Promise<void> {\n if (events.length === 0) return\n if (this.fatalTransportError) return\n\n // For server events, we don't use visitorId - the API resolves identity\n // directly from the event data (email/userId)\n const payload: IngestPayload = {\n source: \"server\",\n events,\n // visitorId is intentionally omitted for server events\n }\n\n try {\n await this.transport.send(payload)\n } catch (error) {\n if (this.isNonRetryableTransportError(error)) {\n this.handleFatalTransportError(error)\n }\n throw error\n }\n }\n\n private ensureNotShutdown(): void {\n if (this.isShutdown) {\n throw new Error(\n \"[Outlit] Client has been shutdown. Create a new instance to continue tracking.\",\n )\n }\n }\n\n private isNonRetryableTransportError(error: unknown): error is TransportError {\n return error instanceof TransportError && error.retryable === false\n }\n\n private handleFatalTransportError(error: TransportError): void {\n if (this.fatalTransportError) return\n\n this.fatalTransportError = error\n\n if (this.flushTimer) {\n clearInterval(this.flushTimer)\n this.flushTimer = null\n }\n\n console.error(\n \"[Outlit] Non-retryable ingest error. Automatic flush retries disabled until client restart:\",\n error,\n )\n }\n}\n","import type { TrackerEvent } from \"@outlit/core\"\n\n// ============================================\n// EVENT QUEUE\n// ============================================\n\nfunction isNonRetryableError(error: unknown): boolean {\n if (!error || typeof error !== \"object\") return false\n if (!(\"retryable\" in error)) return false\n\n return (error as { retryable?: unknown }).retryable === false\n}\n\nexport interface QueueOptions {\n maxSize?: number\n onFlush: (events: TrackerEvent[]) => Promise<void>\n}\n\nexport class EventQueue {\n private queue: TrackerEvent[] = []\n private maxSize: number\n private onFlush: (events: TrackerEvent[]) => Promise<void>\n private isFlushing = false\n\n constructor(options: QueueOptions) {\n this.maxSize = options.maxSize ?? 100\n this.onFlush = options.onFlush\n }\n\n /**\n * Add an event to the queue.\n * Triggers flush if queue reaches max size.\n */\n async enqueue(event: TrackerEvent): Promise<void> {\n this.queue.push(event)\n\n if (this.queue.length >= this.maxSize) {\n await this.flush()\n }\n }\n\n /**\n * Flush all events in the queue.\n */\n async flush(): Promise<void> {\n if (this.isFlushing || this.queue.length === 0) return\n\n this.isFlushing = true\n const events = [...this.queue]\n this.queue = []\n\n try {\n await this.onFlush(events)\n } catch (error) {\n // Re-add events for retryable failures only.\n // Non-retryable failures (e.g. invalid config 4xx) are dropped to avoid infinite retry loops.\n if (!isNonRetryableError(error)) {\n this.queue = [...events, ...this.queue]\n }\n throw error\n } finally {\n this.isFlushing = false\n }\n }\n\n /**\n * Get the number of events in the queue.\n */\n get size(): number {\n return this.queue.length\n }\n\n /**\n * Check if the queue is currently flushing.\n */\n get flushing(): boolean {\n return this.isFlushing\n }\n}\n","import type { IngestPayload, IngestResponse } from \"@outlit/core\"\n\n// ============================================\n// HTTP TRANSPORT\n// ============================================\n\ninterface TransportErrorOptions {\n status?: number\n retryable?: boolean\n cause?: unknown\n}\n\n/**\n * Transport-level error enriched with retryability metadata.\n */\nexport class TransportError extends Error {\n readonly status?: number\n readonly retryable: boolean\n\n constructor(message: string, options?: TransportErrorOptions) {\n super(message)\n this.name = \"TransportError\"\n this.status = options?.status\n this.retryable = options?.retryable ?? true\n\n if (options?.cause !== undefined) {\n this.cause = options.cause\n }\n }\n}\n\nfunction isRetryableStatus(status: number): boolean {\n // 429 is rate limiting (transient). Most other 4xx are configuration/input errors.\n return status === 429 || status >= 500\n}\n\nexport interface TransportOptions {\n apiHost: string\n publicKey: string\n timeout?: number\n}\n\nexport class HttpTransport {\n private apiHost: string\n private publicKey: string\n private timeout: number\n\n constructor(options: TransportOptions) {\n this.apiHost = options.apiHost\n this.publicKey = options.publicKey\n this.timeout = options.timeout ?? 10000\n }\n\n /**\n * Send events to the ingest API.\n */\n async send(payload: IngestPayload): Promise<IngestResponse> {\n const url = `${this.apiHost}/api/i/v1/${this.publicKey}/events`\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), this.timeout)\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"Unknown error\")\n throw new TransportError(`HTTP ${response.status}: ${errorBody}`, {\n status: response.status,\n retryable: isRetryableStatus(response.status),\n })\n }\n\n return (await response.json()) as IngestResponse\n } catch (error) {\n if (error instanceof TransportError) {\n throw error\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new TransportError(`Request timed out after ${this.timeout}ms`, {\n retryable: true,\n cause: error,\n })\n }\n\n if (error instanceof Error) {\n throw new TransportError(error.message, {\n retryable: true,\n cause: error,\n })\n }\n\n throw new TransportError(\"Unknown transport error\", {\n retryable: true,\n cause: error,\n })\n } finally {\n clearTimeout(timeoutId)\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outlit/node",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Outlit server-side tracking SDK for Node.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Outlit AI",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"test": "vitest run"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@outlit/core": "^1.5.
|
|
54
|
+
"@outlit/core": "^1.5.2"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@outlit/typescript-config": "workspace:*",
|