@guidelytech/node 0.15.0 → 0.16.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.cjs CHANGED
@@ -147,7 +147,7 @@ function jitter(delayMs, random) {
147
147
 
148
148
  // src/client.ts
149
149
  var SDK_NAME = "guidely-node";
150
- var SDK_VERSION = "0.15.0";
150
+ var SDK_VERSION = "0.16.0";
151
151
  var DEFAULT_API_URL = "https://guidelytech.com";
152
152
  var BATCH_PATH = "/api/v1/sdk/server/batch";
153
153
  var ENVIRONMENTS = ["development", "staging", "production"];
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/message.ts","../src/retry.ts","../src/client.ts"],"sourcesContent":["export { Guidely, GuidelyError, SDK_NAME, SDK_VERSION, DEFAULT_API_URL } from \"./client\";\nexport type { Environment, FetchLike, GuidelyErrorCode, GuidelyOptions } from \"./client\";\nexport type { GroupMessage, IdentifyMessage, JsonValue, MessageType, Properties, Timestamp, TrackMessage } from \"./message\";\n","import { randomBytes } from \"node:crypto\";\n\n/** Limits shared by every Guidely server SDK (sdks/conformance/track-vectors.json → limits). */\nexport const LIMITS = {\n /** Most messages in one request (the API accepts 1–100). */\n maxBatchMessages: 100,\n /** Most serialized message bytes in one request (the API accepts bodies up to 512 KB). */\n maxBatchBytes: 500_000,\n /** A message larger than this when serialized is dropped and reported. */\n maxMessageBytes: 32_768,\n maxEventLength: 120,\n maxIdLength: 256,\n} as const;\n\nexport type MessageType = \"identify\" | \"track\" | \"group\";\n\n/** JSON-compatible values for traits and properties. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\nexport type Properties = Record<string, unknown>;\n/** A caller-supplied time: a `Date`, epoch milliseconds or an ISO 8601 string with an offset. */\nexport type Timestamp = Date | number | string;\n\nexport interface IdentifyMessage {\n /** Your user id (1–256 characters), the one your frontend passes to `identify()`. */\n userId: string | number;\n /** Merged into the user's traits. */\n traits?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n /** When it happened (default: now). */\n timestamp?: Timestamp;\n}\n\nexport interface TrackMessage {\n /** Your user id; `track` needs `userId` or `anonymousId`. */\n userId?: string | number | null;\n /** The browser SDK's anonymous id (`Guidely.anonymousId`) for visitors who have not signed in. */\n anonymousId?: string | number | null;\n /** The event name (1–120 characters), e.g. `\"Report Exported\"`. */\n event: string;\n properties?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n timestamp?: Timestamp;\n}\n\nexport interface GroupMessage {\n /** The account id (1–256 characters). */\n accountId: string | number;\n /** Merged into the account's traits (`name` also becomes the account name). */\n traits?: Properties | null;\n /** Adds this user to the account. */\n userId?: string | number | null;\n timestamp?: Timestamp;\n}\n\n/** A message as it is sent. */\nexport interface WireMessage {\n type: MessageType;\n messageId: string;\n timestamp: string;\n userId?: string;\n anonymousId?: string;\n event?: string;\n accountId?: string;\n traits?: Record<string, unknown>;\n properties?: Record<string, unknown>;\n}\n\nexport class InvalidMessageError extends Error {}\n\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\n/** 24 random characters from [0-9A-Za-z] (unbiased). */\nexport function newMessageId(): string {\n let id = \"\";\n while (id.length < 24) {\n for (const b of randomBytes(32)) {\n if (b < 248) id += ALPHABET[b % 62]; // 248 = 4 × 62: reject the remainder to stay unbiased\n if (id.length === 24) break;\n }\n }\n return id;\n}\n\n/** ISO 8601 UTC with milliseconds, e.g. `2026-10-26T10:00:00.000Z`. */\nexport function formatTimestamp(value: Timestamp): string {\n let date: Date;\n if (value instanceof Date) date = value;\n else if (typeof value === \"number\") date = new Date(value);\n else if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})$/i.test(value.trim())) date = new Date(value.trim());\n else throw new InvalidMessageError(\"timestamp must be a Date, epoch milliseconds or an ISO 8601 string with an offset\");\n if (Number.isNaN(date.getTime())) throw new InvalidMessageError(\"timestamp is not a valid date\");\n return date.toISOString();\n}\n\n/** UTF-16 code units, like JavaScript's `length` (and the server's validation). */\nconst length = (s: string) => s.length;\n\n/**\n * An id field. `required` ids must be present; optional ones treat `null`, `undefined` and `\"\"` as\n * absent. Integers become strings; anything else that is not a non-blank string ≤ 256 is invalid.\n */\nfunction id(name: string, value: unknown, required: boolean): string | undefined {\n if (value === undefined || value === null || (!required && value === \"\")) {\n if (required) throw new InvalidMessageError(`${name} is required`);\n return undefined;\n }\n let s: string;\n if (typeof value === \"string\") s = value;\n else if (typeof value === \"number\" && Number.isSafeInteger(value)) s = String(value);\n else throw new InvalidMessageError(`${name} must be a string`);\n if (s.trim() === \"\") throw new InvalidMessageError(`${name} must not be empty`);\n if (length(s) > LIMITS.maxIdLength) throw new InvalidMessageError(`${name} is longer than ${LIMITS.maxIdLength} characters`);\n return s;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction object(name: string, value: unknown): Record<string, unknown> | undefined {\n if (value === undefined || value === null) return undefined;\n if (!isPlainObject(value)) throw new InvalidMessageError(`${name} must be an object`);\n return value;\n}\n\n/**\n * Validates a call and returns the message to send (without serializing it). Throws\n * {@link InvalidMessageError} when the call must be dropped.\n */\nexport function buildMessage(type: MessageType, args: unknown, now: () => Date = () => new Date()): WireMessage {\n if (!isPlainObject(args)) throw new InvalidMessageError(`${type}() needs an object argument`);\n const timestamp = args.timestamp === undefined || args.timestamp === null ? now().toISOString() : formatTimestamp(args.timestamp as Timestamp);\n const base = { type, messageId: newMessageId(), timestamp };\n switch (type) {\n case \"identify\": {\n const userId = id(\"userId\", args.userId, true);\n const traits = object(\"traits\", args.traits);\n const accountId = id(\"accountId\", args.accountId, false);\n return { ...base, userId, ...(traits ? { traits } : {}), ...(accountId ? { accountId } : {}) };\n }\n case \"track\": {\n const userId = id(\"userId\", args.userId, false);\n const anonymousId = id(\"anonymousId\", args.anonymousId, false);\n if (!userId && !anonymousId) throw new InvalidMessageError(\"track() needs a userId or an anonymousId\");\n const event = args.event;\n if (typeof event !== \"string\" || event.trim() === \"\") throw new InvalidMessageError(\"track() needs an event name\");\n if (length(event) > LIMITS.maxEventLength) throw new InvalidMessageError(`event is longer than ${LIMITS.maxEventLength} characters`);\n const properties = object(\"properties\", args.properties);\n const accountId = id(\"accountId\", args.accountId, false);\n return {\n ...base,\n ...(userId ? { userId } : {}),\n ...(anonymousId ? { anonymousId } : {}),\n event,\n ...(properties ? { properties } : {}),\n ...(accountId ? { accountId } : {}),\n };\n }\n case \"group\": {\n const accountId = id(\"accountId\", args.accountId, true);\n const traits = object(\"traits\", args.traits);\n const userId = id(\"userId\", args.userId, false);\n return { ...base, accountId, ...(traits ? { traits } : {}), ...(userId ? { userId } : {}) };\n }\n }\n}\n","/** What to do with a batch after an answer (status 0 = no answer: network error or timeout). */\nexport type ResponseAction = \"ok\" | \"retry\" | \"drop\";\n\nexport interface BackoffOptions {\n baseMs: number;\n maxMs: number;\n retryAfterMaxMs: number;\n}\n\nexport const DEFAULT_BACKOFF: BackoffOptions = { baseMs: 1000, maxMs: 30_000, retryAfterMaxMs: 60_000 };\n\n/** 2xx → ok; no answer, 429 and 5xx → retry; anything else (other 4xx, 3xx) → drop. */\nexport function responseAction(status: number): ResponseAction {\n if (status >= 200 && status < 300) return \"ok\";\n if (status === 0 || status === 429 || (status >= 500 && status < 600)) return \"retry\";\n return \"drop\";\n}\n\n/** Retry-After in whole seconds → milliseconds; anything else (HTTP dates, garbage) → null. */\nexport function parseRetryAfter(value: string | null | undefined): number | null {\n if (value == null) return null;\n const v = value.trim();\n return /^\\d+$/.test(v) ? Number(v) * 1000 : null;\n}\n\n/**\n * The wait before retrying after failed attempt `attempt` (1 = the first send), before jitter:\n * `min(baseMs · 2^(attempt−1), maxMs)`, or Retry-After capped at `retryAfterMaxMs`.\n */\nexport function backoffDelay(attempt: number, retryAfter: string | null | undefined, o: BackoffOptions = DEFAULT_BACKOFF): number {\n const ra = parseRetryAfter(retryAfter);\n if (ra !== null) return Math.min(ra, o.retryAfterMaxMs);\n return Math.min(o.baseMs * 2 ** Math.max(0, attempt - 1), o.maxMs);\n}\n\n/** ±20 % jitter on a computed backoff (not on Retry-After). `random` is in [0, 1). */\nexport function jitter(delayMs: number, random: number): number {\n return Math.round(delayMs * (0.8 + 0.4 * random));\n}\n","import { buildMessage, InvalidMessageError, LIMITS, type GroupMessage, type IdentifyMessage, type MessageType, type TrackMessage } from \"./message\";\nimport { backoffDelay, DEFAULT_BACKOFF, jitter, parseRetryAfter, responseAction } from \"./retry\";\n\nexport const SDK_NAME = \"guidely-node\";\nexport const SDK_VERSION = \"0.15.0\";\nexport const DEFAULT_API_URL = \"https://guidelytech.com\";\nexport const BATCH_PATH = \"/api/v1/sdk/server/batch\";\n\nexport type Environment = \"development\" | \"staging\" | \"production\";\nconst ENVIRONMENTS: readonly string[] = [\"development\", \"staging\", \"production\"];\n\n/** The subset of `fetch` the SDK uses, so any implementation (undici, node-fetch, a test fake) fits. */\nexport type FetchLike = (\n url: string,\n init: { method: \"POST\"; headers: Record<string, string>; body: string; signal: AbortSignal },\n) => Promise<{ status: number; headers: { get(name: string): string | null }; text?: () => Promise<string> }>;\n\nexport type GuidelyErrorCode =\n /** A call was invalid (missing userId, empty event…) and dropped. */\n | \"invalid_message\"\n /** A message serialized to more than 32 KB and was dropped. */\n | \"message_too_large\"\n /** The queue was full (`maxQueueSize`); the oldest messages were dropped. */\n | \"queue_full\"\n /** A batch was rejected (4xx), or failed after every retry, or was cut short by a shutdown timeout. */\n | \"delivery_failed\"\n /** A call was made after `shutdown()`. */\n | \"closed\";\n\n/** Reported to `onError`; never thrown by tracking calls. */\nexport class GuidelyError extends Error {\n override readonly name = \"GuidelyError\";\n constructor(\n readonly code: GuidelyErrorCode,\n message: string,\n /** How many messages were dropped. */\n readonly count = 1,\n /** The HTTP status, for `delivery_failed` (0 when no answer was received). */\n readonly status?: number,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\nexport interface GuidelyOptions {\n /** A secret API key (`gly_sk_…`) with the `events:write` scope. Never ship it to a browser. */\n secretKey: string;\n /** The environment events are written to. */\n environment: Environment;\n /** API origin (default `https://guidelytech.com`). */\n apiUrl?: string;\n /** Send a batch when this many messages are queued (default 100; batches hold at most 100). */\n flushAt?: number;\n /** Send what is queued at least this often (default 5000 ms). */\n flushIntervalMs?: number;\n /** Most messages held in memory; beyond it the oldest are dropped (default 10,000). */\n maxQueueSize?: number;\n /** Retries of a batch after network errors, 429 and 5xx (default 5, so up to 6 attempts). */\n maxRetries?: number;\n /** How long `shutdown()` (and the exit hook) waits for queued messages to go out (default 10,000 ms). */\n shutdownTimeoutMs?: number;\n /** Timeout of one HTTP request (default 10,000 ms). */\n timeoutMs?: number;\n /** Receives every problem (default: `console.warn`). Called synchronously; must not throw. */\n onError?: (error: GuidelyError) => void;\n /** `fetch` implementation (default: the global `fetch` of Node 18+). */\n fetch?: FetchLike;\n /** Flush on the process's `beforeExit` event (default true). Signals and `process.exit()` skip it: call `shutdown()`. */\n handleExit?: boolean;\n}\n\ninterface Queued {\n json: string;\n bytes: number;\n}\n\ninterface Sleep {\n timer: ReturnType<typeof setTimeout> | undefined;\n resolve: () => void;\n}\n\nconst int = (value: number | undefined, fallback: number, min: number) =>\n typeof value === \"number\" && Number.isFinite(value) ? Math.max(min, Math.floor(value)) : fallback;\n\n/**\n * Server-side Guidely client: `identify`, `track` and `group` from your backend.\n *\n * ```ts\n * const guidely = new Guidely({ secretKey: process.env.GUIDELY_SECRET_KEY!, environment: \"production\" });\n * guidely.track({ userId: \"u_42\", event: \"Report Exported\", properties: { format: \"csv\" } });\n * await guidely.shutdown(); // before the process exits\n * ```\n *\n * Calls validate, queue and return: they never throw and never wait for the network. Messages go out\n * in batches (every `flushAt` messages, every `flushIntervalMs`, on `flush()`), one request at a time,\n * retried with backoff when Guidely is unreachable.\n */\nexport class Guidely {\n private readonly url: string;\n private readonly headers: Record<string, string>;\n private readonly flushAt: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly maxRetries: number;\n private readonly shutdownTimeoutMs: number;\n private readonly timeoutMs: number;\n private readonly onError: (error: GuidelyError) => void;\n private readonly fetch: FetchLike;\n private readonly sdkJson = JSON.stringify({ name: SDK_NAME, version: SDK_VERSION });\n\n private queue: Queued[] = [];\n private droppedSinceReport = 0;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private pumping: Promise<void> | null = null;\n private drainAll = false;\n /** Bumped to abandon the running pump (shutdown timeout): its request is aborted and its sleeps end. */\n private epoch = 0;\n private inflight: AbortController | null = null;\n private readonly sleeps = new Set<Sleep>();\n private closed = false;\n private shutdownPromise: Promise<void> | null = null;\n private exitDrain: Promise<void> | null = null;\n private readonly exitListener: (() => void) | null = null;\n\n constructor(options: GuidelyOptions) {\n const o = options ?? ({} as GuidelyOptions);\n const key = typeof o.secretKey === \"string\" ? o.secretKey.trim() : \"\";\n if (!key.startsWith(\"gly_sk_\")) {\n throw new TypeError(\"Guidely: secretKey must be a secret API key (gly_sk_…) with the events:write scope\");\n }\n if (!ENVIRONMENTS.includes(o.environment)) {\n throw new TypeError('Guidely: environment must be \"development\", \"staging\" or \"production\"');\n }\n const fetchImpl = o.fetch ?? (typeof globalThis.fetch === \"function\" ? (globalThis.fetch.bind(globalThis) as unknown as FetchLike) : undefined);\n if (!fetchImpl) throw new TypeError(\"Guidely: no global fetch (Node 18+ is required), pass the fetch option\");\n this.fetch = fetchImpl;\n const apiUrl = (o.apiUrl ?? DEFAULT_API_URL).trim().replace(/\\/+$/, \"\");\n this.url = `${apiUrl}${BATCH_PATH}?environment=${encodeURIComponent(o.environment)}`;\n this.headers = {\n Authorization: `Bearer ${key}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"User-Agent\": `${SDK_NAME}/${SDK_VERSION} (Node ${typeof process !== \"undefined\" ? process.version : \"unknown\"})`,\n };\n this.flushAt = int(o.flushAt, 100, 1);\n this.flushIntervalMs = int(o.flushIntervalMs, 5000, 1);\n this.maxQueueSize = int(o.maxQueueSize, 10_000, 1);\n this.maxRetries = int(o.maxRetries, 5, 0);\n this.shutdownTimeoutMs = int(o.shutdownTimeoutMs, 10_000, 0);\n this.timeoutMs = int(o.timeoutMs, 10_000, 1);\n this.onError = o.onError ?? ((e) => console.warn(`[${SDK_NAME}] ${e.message}`));\n if (o.handleExit !== false && typeof process !== \"undefined\" && typeof process.on === \"function\") {\n this.exitListener = () => this.onBeforeExit();\n process.on(\"beforeExit\", this.exitListener);\n }\n }\n\n /** Who a user is: merges `traits` into the user (creating it if needed), optionally linking an account. */\n identify(message: IdentifyMessage): void {\n this.enqueue(\"identify\", message);\n }\n\n /** What a user (or anonymous visitor) did. */\n track(message: TrackMessage): void {\n this.enqueue(\"track\", message);\n }\n\n /** Account traits, and optionally adds `userId` to the account. */\n group(message: GroupMessage): void {\n this.enqueue(\"group\", message);\n }\n\n /** Sends everything queued; resolves once it has been delivered or dropped. Never rejects. */\n flush(): Promise<void> {\n return this.startPump(true);\n }\n\n /**\n * Stops the timer and the exit hook, then flushes, waiting at most `shutdownTimeoutMs`. Later calls\n * are dropped (and reported). Call it before your process exits. Never rejects.\n */\n shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.closed = true;\n this.clearTimer();\n if (this.exitListener) process.removeListener(\"beforeExit\", this.exitListener);\n this.shutdownPromise = this.drain(this.shutdownTimeoutMs);\n return this.shutdownPromise;\n }\n\n // --- queueing ------------------------------------------------------------------------------\n\n private enqueue(type: MessageType, args: unknown): void {\n try {\n if (this.closed) {\n this.report(new GuidelyError(\"closed\", `${type}() was called after shutdown(); the message was dropped`));\n return;\n }\n const message = buildMessage(type, args);\n let json: string;\n try {\n json = JSON.stringify(message);\n } catch (e) {\n throw new InvalidMessageError(`could not be serialized to JSON (${e instanceof Error ? e.message : String(e)})`);\n }\n const bytes = Buffer.byteLength(json, \"utf8\");\n if (bytes > LIMITS.maxMessageBytes) {\n this.report(new GuidelyError(\"message_too_large\", `${type}() message is ${bytes} bytes, more than the ${LIMITS.maxMessageBytes}-byte limit; it was dropped`));\n return;\n }\n if (this.queue.length >= this.maxQueueSize) {\n this.queue.shift();\n this.droppedSinceReport++;\n }\n this.queue.push({ json, bytes });\n if (this.queue.length >= this.flushAt) void this.startPump(false);\n else this.scheduleTimer();\n } catch (e) {\n if (e instanceof InvalidMessageError) this.report(new GuidelyError(\"invalid_message\", `Invalid ${type}() call dropped: ${e.message}`));\n else this.report(new GuidelyError(\"invalid_message\", `${type}() failed: ${e instanceof Error ? e.message : String(e)}`, 1, undefined, { cause: e }));\n }\n }\n\n private scheduleTimer(): void {\n if (this.timer !== undefined || this.closed) return;\n this.timer = setTimeout(() => {\n this.timer = undefined;\n void this.startPump(true);\n }, this.flushIntervalMs);\n (this.timer as { unref?: () => void }).unref?.();\n }\n\n private clearTimer(): void {\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n // --- sending -------------------------------------------------------------------------------\n\n /** Starts (or joins) the single sender. `all`: send everything, not only full batches. */\n private startPump(all: boolean): Promise<void> {\n if (all) this.drainAll = true;\n if (!this.pumping) this.pumping = this.pump();\n return this.pumping;\n }\n\n private async pump(): Promise<void> {\n await Promise.resolve(); // `this.pumping` is assigned before the loop can finish\n const epoch = this.epoch;\n try {\n const cap = Math.min(this.flushAt, LIMITS.maxBatchMessages);\n for (;;) {\n this.reportDrops();\n if (epoch !== this.epoch || this.queue.length === 0 || (!this.drainAll && this.queue.length < cap)) break;\n await this.deliver(this.takeBatch(cap), epoch);\n }\n } catch (e) {\n this.report(new GuidelyError(\"delivery_failed\", `Unexpected error while sending: ${e instanceof Error ? e.message : String(e)}`, 0, undefined, { cause: e }));\n } finally {\n this.pumping = null;\n this.drainAll = false;\n if (this.queue.length > 0) this.scheduleTimer();\n }\n }\n\n /** Up to `cap` messages from the head of the queue, at most `maxBatchBytes` of them (at least one). */\n private takeBatch(cap: number): Queued[] {\n let n = 0;\n let size = 0;\n while (n < this.queue.length && n < cap) {\n const next = this.queue[n]!;\n if (n > 0 && size + next.bytes > LIMITS.maxBatchBytes) break;\n size += next.bytes;\n n++;\n }\n return this.queue.splice(0, n);\n }\n\n private async deliver(batch: Queued[], epoch: number): Promise<void> {\n const messages = batch.map((m) => m.json).join(\",\");\n for (let attempt = 1; ; attempt++) {\n if (epoch !== this.epoch) {\n this.report(new GuidelyError(\"delivery_failed\", `${batch.length} messages were not sent before the shutdown timeout`, batch.length, 0));\n return;\n }\n const body = `{\"batch\":[${messages}],\"sentAt\":${JSON.stringify(new Date().toISOString())},\"sdk\":${this.sdkJson}}`;\n let status = 0;\n let retryAfter: string | null = null;\n let failure: unknown;\n try {\n const res = await this.send(body);\n status = res.status;\n retryAfter = res.headers.get(\"retry-after\");\n } catch (e) {\n failure = e;\n }\n const action = responseAction(status);\n if (action === \"ok\") return;\n if (epoch !== this.epoch) continue; // aborted while sending: reported at the top\n if (action === \"drop\" || attempt > this.maxRetries) {\n this.report(new GuidelyError(\"delivery_failed\", failureMessage(status, failure, batch.length, attempt), batch.length, status, { cause: failure }));\n return;\n }\n const delay = parseRetryAfter(retryAfter) !== null ? backoffDelay(attempt, retryAfter) : jitter(backoffDelay(attempt, null, DEFAULT_BACKOFF), Math.random());\n await this.sleep(delay);\n }\n }\n\n private async send(body: string) {\n const controller = new AbortController();\n this.inflight = controller;\n const timeout = setTimeout(() => controller.abort(new Error(`request timed out after ${this.timeoutMs} ms`)), this.timeoutMs);\n try {\n const res = await this.fetch(this.url, { method: \"POST\", headers: this.headers, body, signal: controller.signal });\n try {\n await res.text?.(); // release the connection\n } catch {\n // the status is what matters\n }\n return res;\n } finally {\n clearTimeout(timeout);\n if (this.inflight === controller) this.inflight = null;\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const s: Sleep = { timer: undefined, resolve };\n s.timer = setTimeout(() => {\n this.sleeps.delete(s);\n resolve();\n }, ms);\n this.sleeps.add(s);\n });\n }\n\n /** Flushes, giving up after `timeoutMs`: the request in flight is aborted and what is left is dropped. */\n private async drain(timeoutMs: number): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = await Promise.race([\n this.startPump(true).then(() => false),\n new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(true), timeoutMs);\n }),\n ]);\n clearTimeout(timer);\n if (!timedOut) return;\n this.epoch++;\n this.inflight?.abort(new Error(\"shutdown timeout\"));\n for (const s of this.sleeps) {\n clearTimeout(s.timer);\n s.resolve();\n }\n this.sleeps.clear();\n const left = this.queue.length;\n this.queue = [];\n if (left > 0) this.report(new GuidelyError(\"delivery_failed\", `${left} queued messages were not sent within ${timeoutMs} ms`, left, 0));\n }\n\n private onBeforeExit(): void {\n if (this.closed || this.exitDrain || (this.queue.length === 0 && !this.pumping)) return;\n this.exitDrain = this.drain(this.shutdownTimeoutMs).finally(() => {\n this.exitDrain = null;\n });\n }\n\n // --- errors --------------------------------------------------------------------------------\n\n private reportDrops(): void {\n if (this.droppedSinceReport === 0) return;\n const n = this.droppedSinceReport;\n this.droppedSinceReport = 0;\n this.report(new GuidelyError(\"queue_full\", `The queue was full (maxQueueSize ${this.maxQueueSize}): dropped the ${n} oldest messages`, n));\n }\n\n private report(error: GuidelyError): void {\n try {\n this.onError(error);\n } catch {\n // an error handler must never break the caller\n }\n }\n}\n\nfunction failureMessage(status: number, failure: unknown, count: number, attempts: number): string {\n const what = `${count} messages were dropped`;\n if (status === 0) return `${what}: Guidely could not be reached in ${attempts} attempt${attempts === 1 ? \"\" : \"s\"} (${failure instanceof Error ? failure.message : String(failure)})`;\n if (status === 401 || status === 403) return `${what}: the key was rejected (HTTP ${status}). Check it is a gly_sk_ key with the events:write scope for this environment`;\n if (attempts > 1) return `${what}: HTTP ${status} after ${attempts} attempts`;\n return `${what}: HTTP ${status}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAA4B;AAGrB,IAAM,SAAS;AAAA;AAAA,EAEpB,kBAAkB;AAAA;AAAA,EAElB,eAAe;AAAA;AAAA,EAEf,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,aAAa;AACf;AAyDO,IAAM,sBAAN,cAAkC,MAAM;AAAC;AAEhD,IAAM,WAAW;AAGV,SAAS,eAAuB;AACrC,MAAIA,MAAK;AACT,SAAOA,IAAG,SAAS,IAAI;AACrB,eAAW,SAAK,gCAAY,EAAE,GAAG;AAC/B,UAAI,IAAI,IAAK,CAAAA,OAAM,SAAS,IAAI,EAAE;AAClC,UAAIA,IAAG,WAAW,GAAI;AAAA,IACxB;AAAA,EACF;AACA,SAAOA;AACT;AAGO,SAAS,gBAAgB,OAA0B;AACxD,MAAI;AACJ,MAAI,iBAAiB,KAAM,QAAO;AAAA,WACzB,OAAO,UAAU,SAAU,QAAO,IAAI,KAAK,KAAK;AAAA,WAChD,OAAO,UAAU,YAAY,wEAAwE,KAAK,MAAM,KAAK,CAAC,EAAG,QAAO,IAAI,KAAK,MAAM,KAAK,CAAC;AAAA,MACzJ,OAAM,IAAI,oBAAoB,mFAAmF;AACtH,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,OAAM,IAAI,oBAAoB,+BAA+B;AAC/F,SAAO,KAAK,YAAY;AAC1B;AAGA,IAAM,SAAS,CAAC,MAAc,EAAE;AAMhC,SAAS,GAAG,MAAc,OAAgB,UAAuC;AAC/E,MAAI,UAAU,UAAa,UAAU,QAAS,CAAC,YAAY,UAAU,IAAK;AACxE,QAAI,SAAU,OAAM,IAAI,oBAAoB,GAAG,IAAI,cAAc;AACjE,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI,OAAO,UAAU,SAAU,KAAI;AAAA,WAC1B,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,EAAG,KAAI,OAAO,KAAK;AAAA,MAC9E,OAAM,IAAI,oBAAoB,GAAG,IAAI,mBAAmB;AAC7D,MAAI,EAAE,KAAK,MAAM,GAAI,OAAM,IAAI,oBAAoB,GAAG,IAAI,oBAAoB;AAC9E,MAAI,OAAO,CAAC,IAAI,OAAO,YAAa,OAAM,IAAI,oBAAoB,GAAG,IAAI,mBAAmB,OAAO,WAAW,aAAa;AAC3H,SAAO;AACT;AAEA,SAAS,cAAc,GAA0C;AAC/D,MAAI,OAAO,MAAM,YAAY,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAG,QAAO;AACpE,QAAM,QAAQ,OAAO,eAAe,CAAC;AACrC,SAAO,UAAU,OAAO,aAAa,UAAU;AACjD;AAEA,SAAS,OAAO,MAAc,OAAqD;AACjF,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,CAAC,cAAc,KAAK,EAAG,OAAM,IAAI,oBAAoB,GAAG,IAAI,oBAAoB;AACpF,SAAO;AACT;AAMO,SAAS,aAAa,MAAmB,MAAe,MAAkB,MAAM,oBAAI,KAAK,GAAgB;AAC9G,MAAI,CAAC,cAAc,IAAI,EAAG,OAAM,IAAI,oBAAoB,GAAG,IAAI,6BAA6B;AAC5F,QAAM,YAAY,KAAK,cAAc,UAAa,KAAK,cAAc,OAAO,IAAI,EAAE,YAAY,IAAI,gBAAgB,KAAK,SAAsB;AAC7I,QAAM,OAAO,EAAE,MAAM,WAAW,aAAa,GAAG,UAAU;AAC1D,UAAQ,MAAM;AAAA,IACZ,KAAK,YAAY;AACf,YAAM,SAAS,GAAG,UAAU,KAAK,QAAQ,IAAI;AAC7C,YAAM,SAAS,OAAO,UAAU,KAAK,MAAM;AAC3C,YAAM,YAAY,GAAG,aAAa,KAAK,WAAW,KAAK;AACvD,aAAO,EAAE,GAAG,MAAM,QAAQ,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,GAAI,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC,EAAG;AAAA,IAC/F;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,SAAS,GAAG,UAAU,KAAK,QAAQ,KAAK;AAC9C,YAAM,cAAc,GAAG,eAAe,KAAK,aAAa,KAAK;AAC7D,UAAI,CAAC,UAAU,CAAC,YAAa,OAAM,IAAI,oBAAoB,0CAA0C;AACrG,YAAM,QAAQ,KAAK;AACnB,UAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,GAAI,OAAM,IAAI,oBAAoB,6BAA6B;AACjH,UAAI,OAAO,KAAK,IAAI,OAAO,eAAgB,OAAM,IAAI,oBAAoB,wBAAwB,OAAO,cAAc,aAAa;AACnI,YAAM,aAAa,OAAO,cAAc,KAAK,UAAU;AACvD,YAAM,YAAY,GAAG,aAAa,KAAK,WAAW,KAAK;AACvD,aAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,QAC3B,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,QACrC;AAAA,QACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,QACnC,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MACnC;AAAA,IACF;AAAA,IACA,KAAK,SAAS;AACZ,YAAM,YAAY,GAAG,aAAa,KAAK,WAAW,IAAI;AACtD,YAAM,SAAS,OAAO,UAAU,KAAK,MAAM;AAC3C,YAAM,SAAS,GAAG,UAAU,KAAK,QAAQ,KAAK;AAC9C,aAAO,EAAE,GAAG,MAAM,WAAW,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,GAAI,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG;AAAA,IAC5F;AAAA,EACF;AACF;;;AChKO,IAAM,kBAAkC,EAAE,QAAQ,KAAM,OAAO,KAAQ,iBAAiB,IAAO;AAG/F,SAAS,eAAe,QAAgC;AAC7D,MAAI,UAAU,OAAO,SAAS,IAAK,QAAO;AAC1C,MAAI,WAAW,KAAK,WAAW,OAAQ,UAAU,OAAO,SAAS,IAAM,QAAO;AAC9E,SAAO;AACT;AAGO,SAAS,gBAAgB,OAAiD;AAC/E,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,IAAI,MAAM,KAAK;AACrB,SAAO,QAAQ,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,MAAO;AAC9C;AAMO,SAAS,aAAa,SAAiB,YAAuC,IAAoB,iBAAyB;AAChI,QAAM,KAAK,gBAAgB,UAAU;AACrC,MAAI,OAAO,KAAM,QAAO,KAAK,IAAI,IAAI,EAAE,eAAe;AACtD,SAAO,KAAK,IAAI,EAAE,SAAS,KAAK,KAAK,IAAI,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK;AACnE;AAGO,SAAS,OAAO,SAAiB,QAAwB;AAC9D,SAAO,KAAK,MAAM,WAAW,MAAM,MAAM,OAAO;AAClD;;;ACnCO,IAAM,WAAW;AACjB,IAAM,cAAc;AACpB,IAAM,kBAAkB;AACxB,IAAM,aAAa;AAG1B,IAAM,eAAkC,CAAC,eAAe,WAAW,YAAY;AAqBxE,IAAM,eAAN,cAA2B,MAAM;AAAA,EAEtC,YACW,MACT,SAES,QAAQ,GAER,QACT,SACA;AACA,UAAM,SAAS,OAAO;AARb;AAGA;AAEA;AAAA,EAIX;AAAA,EATW;AAAA,EAGA;AAAA,EAEA;AAAA,EAPO,OAAO;AAY3B;AAuCA,IAAM,MAAM,CAAC,OAA2B,UAAkB,QACxD,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,CAAC,IAAI;AAepF,IAAM,UAAN,MAAc;AAAA,EACF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU,KAAK,UAAU,EAAE,MAAM,UAAU,SAAS,YAAY,CAAC;AAAA,EAE1E,QAAkB,CAAC;AAAA,EACnB,qBAAqB;AAAA,EACrB;AAAA,EACA,UAAgC;AAAA,EAChC,WAAW;AAAA;AAAA,EAEX,QAAQ;AAAA,EACR,WAAmC;AAAA,EAC1B,SAAS,oBAAI,IAAW;AAAA,EACjC,SAAS;AAAA,EACT,kBAAwC;AAAA,EACxC,YAAkC;AAAA,EACzB,eAAoC;AAAA,EAErD,YAAY,SAAyB;AACnC,UAAM,IAAI,WAAY,CAAC;AACvB,UAAM,MAAM,OAAO,EAAE,cAAc,WAAW,EAAE,UAAU,KAAK,IAAI;AACnE,QAAI,CAAC,IAAI,WAAW,SAAS,GAAG;AAC9B,YAAM,IAAI,UAAU,yFAAoF;AAAA,IAC1G;AACA,QAAI,CAAC,aAAa,SAAS,EAAE,WAAW,GAAG;AACzC,YAAM,IAAI,UAAU,uEAAuE;AAAA,IAC7F;AACA,UAAM,YAAY,EAAE,UAAU,OAAO,WAAW,UAAU,aAAc,WAAW,MAAM,KAAK,UAAU,IAA6B;AACrI,QAAI,CAAC,UAAW,OAAM,IAAI,UAAU,wEAAwE;AAC5G,SAAK,QAAQ;AACb,UAAM,UAAU,EAAE,UAAU,iBAAiB,KAAK,EAAE,QAAQ,QAAQ,EAAE;AACtE,SAAK,MAAM,GAAG,MAAM,GAAG,UAAU,gBAAgB,mBAAmB,EAAE,WAAW,CAAC;AAClF,SAAK,UAAU;AAAA,MACb,eAAe,UAAU,GAAG;AAAA,MAC5B,gBAAgB;AAAA,MAChB,QAAQ;AAAA,MACR,cAAc,GAAG,QAAQ,IAAI,WAAW,UAAU,OAAO,YAAY,cAAc,QAAQ,UAAU,SAAS;AAAA,IAChH;AACA,SAAK,UAAU,IAAI,EAAE,SAAS,KAAK,CAAC;AACpC,SAAK,kBAAkB,IAAI,EAAE,iBAAiB,KAAM,CAAC;AACrD,SAAK,eAAe,IAAI,EAAE,cAAc,KAAQ,CAAC;AACjD,SAAK,aAAa,IAAI,EAAE,YAAY,GAAG,CAAC;AACxC,SAAK,oBAAoB,IAAI,EAAE,mBAAmB,KAAQ,CAAC;AAC3D,SAAK,YAAY,IAAI,EAAE,WAAW,KAAQ,CAAC;AAC3C,SAAK,UAAU,EAAE,YAAY,CAAC,MAAM,QAAQ,KAAK,IAAI,QAAQ,KAAK,EAAE,OAAO,EAAE;AAC7E,QAAI,EAAE,eAAe,SAAS,OAAO,YAAY,eAAe,OAAO,QAAQ,OAAO,YAAY;AAChG,WAAK,eAAe,MAAM,KAAK,aAAa;AAC5C,cAAQ,GAAG,cAAc,KAAK,YAAY;AAAA,IAC5C;AAAA,EACF;AAAA;AAAA,EAGA,SAAS,SAAgC;AACvC,SAAK,QAAQ,YAAY,OAAO;AAAA,EAClC;AAAA;AAAA,EAGA,MAAM,SAA6B;AACjC,SAAK,QAAQ,SAAS,OAAO;AAAA,EAC/B;AAAA;AAAA,EAGA,MAAM,SAA6B;AACjC,SAAK,QAAQ,SAAS,OAAO;AAAA,EAC/B;AAAA;AAAA,EAGA,QAAuB;AACrB,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,WAA0B;AACxB,QAAI,KAAK,gBAAiB,QAAO,KAAK;AACtC,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,QAAI,KAAK,aAAc,SAAQ,eAAe,cAAc,KAAK,YAAY;AAC7E,SAAK,kBAAkB,KAAK,MAAM,KAAK,iBAAiB;AACxD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAIQ,QAAQ,MAAmB,MAAqB;AACtD,QAAI;AACF,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,IAAI,aAAa,UAAU,GAAG,IAAI,yDAAyD,CAAC;AACxG;AAAA,MACF;AACA,YAAM,UAAU,aAAa,MAAM,IAAI;AACvC,UAAI;AACJ,UAAI;AACF,eAAO,KAAK,UAAU,OAAO;AAAA,MAC/B,SAAS,GAAG;AACV,cAAM,IAAI,oBAAoB,oCAAoC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,GAAG;AAAA,MACjH;AACA,YAAM,QAAQ,OAAO,WAAW,MAAM,MAAM;AAC5C,UAAI,QAAQ,OAAO,iBAAiB;AAClC,aAAK,OAAO,IAAI,aAAa,qBAAqB,GAAG,IAAI,iBAAiB,KAAK,yBAAyB,OAAO,eAAe,6BAA6B,CAAC;AAC5J;AAAA,MACF;AACA,UAAI,KAAK,MAAM,UAAU,KAAK,cAAc;AAC1C,aAAK,MAAM,MAAM;AACjB,aAAK;AAAA,MACP;AACA,WAAK,MAAM,KAAK,EAAE,MAAM,MAAM,CAAC;AAC/B,UAAI,KAAK,MAAM,UAAU,KAAK,QAAS,MAAK,KAAK,UAAU,KAAK;AAAA,UAC3D,MAAK,cAAc;AAAA,IAC1B,SAAS,GAAG;AACV,UAAI,aAAa,oBAAqB,MAAK,OAAO,IAAI,aAAa,mBAAmB,WAAW,IAAI,oBAAoB,EAAE,OAAO,EAAE,CAAC;AAAA,UAChI,MAAK,OAAO,IAAI,aAAa,mBAAmB,GAAG,IAAI,cAAc,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,IAAI,GAAG,QAAW,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,IACrJ;AAAA,EACF;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,KAAK,UAAU,UAAa,KAAK,OAAQ;AAC7C,SAAK,QAAQ,WAAW,MAAM;AAC5B,WAAK,QAAQ;AACb,WAAK,KAAK,UAAU,IAAI;AAAA,IAC1B,GAAG,KAAK,eAAe;AACvB,IAAC,KAAK,MAAiC,QAAQ;AAAA,EACjD;AAAA,EAEQ,aAAmB;AACzB,QAAI,KAAK,UAAU,OAAW,cAAa,KAAK,KAAK;AACrD,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA,EAKQ,UAAU,KAA6B;AAC7C,QAAI,IAAK,MAAK,WAAW;AACzB,QAAI,CAAC,KAAK,QAAS,MAAK,UAAU,KAAK,KAAK;AAC5C,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,OAAsB;AAClC,UAAM,QAAQ,QAAQ;AACtB,UAAM,QAAQ,KAAK;AACnB,QAAI;AACF,YAAM,MAAM,KAAK,IAAI,KAAK,SAAS,OAAO,gBAAgB;AAC1D,iBAAS;AACP,aAAK,YAAY;AACjB,YAAI,UAAU,KAAK,SAAS,KAAK,MAAM,WAAW,KAAM,CAAC,KAAK,YAAY,KAAK,MAAM,SAAS,IAAM;AACpG,cAAM,KAAK,QAAQ,KAAK,UAAU,GAAG,GAAG,KAAK;AAAA,MAC/C;AAAA,IACF,SAAS,GAAG;AACV,WAAK,OAAO,IAAI,aAAa,mBAAmB,mCAAmC,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC,IAAI,GAAG,QAAW,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,IAC9J,UAAE;AACA,WAAK,UAAU;AACf,WAAK,WAAW;AAChB,UAAI,KAAK,MAAM,SAAS,EAAG,MAAK,cAAc;AAAA,IAChD;AAAA,EACF;AAAA;AAAA,EAGQ,UAAU,KAAuB;AACvC,QAAI,IAAI;AACR,QAAI,OAAO;AACX,WAAO,IAAI,KAAK,MAAM,UAAU,IAAI,KAAK;AACvC,YAAM,OAAO,KAAK,MAAM,CAAC;AACzB,UAAI,IAAI,KAAK,OAAO,KAAK,QAAQ,OAAO,cAAe;AACvD,cAAQ,KAAK;AACb;AAAA,IACF;AACA,WAAO,KAAK,MAAM,OAAO,GAAG,CAAC;AAAA,EAC/B;AAAA,EAEA,MAAc,QAAQ,OAAiB,OAA8B;AACnE,UAAM,WAAW,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG;AAClD,aAAS,UAAU,KAAK,WAAW;AACjC,UAAI,UAAU,KAAK,OAAO;AACxB,aAAK,OAAO,IAAI,aAAa,mBAAmB,GAAG,MAAM,MAAM,uDAAuD,MAAM,QAAQ,CAAC,CAAC;AACtI;AAAA,MACF;AACA,YAAM,OAAO,aAAa,QAAQ,cAAc,KAAK,WAAU,oBAAI,KAAK,GAAE,YAAY,CAAC,CAAC,UAAU,KAAK,OAAO;AAC9G,UAAI,SAAS;AACb,UAAI,aAA4B;AAChC,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,KAAK,IAAI;AAChC,iBAAS,IAAI;AACb,qBAAa,IAAI,QAAQ,IAAI,aAAa;AAAA,MAC5C,SAAS,GAAG;AACV,kBAAU;AAAA,MACZ;AACA,YAAM,SAAS,eAAe,MAAM;AACpC,UAAI,WAAW,KAAM;AACrB,UAAI,UAAU,KAAK,MAAO;AAC1B,UAAI,WAAW,UAAU,UAAU,KAAK,YAAY;AAClD,aAAK,OAAO,IAAI,aAAa,mBAAmB,eAAe,QAAQ,SAAS,MAAM,QAAQ,OAAO,GAAG,MAAM,QAAQ,QAAQ,EAAE,OAAO,QAAQ,CAAC,CAAC;AACjJ;AAAA,MACF;AACA,YAAM,QAAQ,gBAAgB,UAAU,MAAM,OAAO,aAAa,SAAS,UAAU,IAAI,OAAO,aAAa,SAAS,MAAM,eAAe,GAAG,KAAK,OAAO,CAAC;AAC3J,YAAM,KAAK,MAAM,KAAK;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAc,KAAK,MAAc;AAC/B,UAAM,aAAa,IAAI,gBAAgB;AACvC,SAAK,WAAW;AAChB,UAAM,UAAU,WAAW,MAAM,WAAW,MAAM,IAAI,MAAM,2BAA2B,KAAK,SAAS,KAAK,CAAC,GAAG,KAAK,SAAS;AAC5H,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,MAAM,KAAK,KAAK,EAAE,QAAQ,QAAQ,SAAS,KAAK,SAAS,MAAM,QAAQ,WAAW,OAAO,CAAC;AACjH,UAAI;AACF,cAAM,IAAI,OAAO;AAAA,MACnB,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,IACT,UAAE;AACA,mBAAa,OAAO;AACpB,UAAI,KAAK,aAAa,WAAY,MAAK,WAAW;AAAA,IACpD;AAAA,EACF;AAAA,EAEQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAc,CAAC,YAAY;AACpC,YAAM,IAAW,EAAE,OAAO,QAAW,QAAQ;AAC7C,QAAE,QAAQ,WAAW,MAAM;AACzB,aAAK,OAAO,OAAO,CAAC;AACpB,gBAAQ;AAAA,MACV,GAAG,EAAE;AACL,WAAK,OAAO,IAAI,CAAC;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAc,MAAM,WAAkC;AACpD,QAAI;AACJ,UAAM,WAAW,MAAM,QAAQ,KAAK;AAAA,MAClC,KAAK,UAAU,IAAI,EAAE,KAAK,MAAM,KAAK;AAAA,MACrC,IAAI,QAAiB,CAAC,YAAY;AAChC,gBAAQ,WAAW,MAAM,QAAQ,IAAI,GAAG,SAAS;AAAA,MACnD,CAAC;AAAA,IACH,CAAC;AACD,iBAAa,KAAK;AAClB,QAAI,CAAC,SAAU;AACf,SAAK;AACL,SAAK,UAAU,MAAM,IAAI,MAAM,kBAAkB,CAAC;AAClD,eAAW,KAAK,KAAK,QAAQ;AAC3B,mBAAa,EAAE,KAAK;AACpB,QAAE,QAAQ;AAAA,IACZ;AACA,SAAK,OAAO,MAAM;AAClB,UAAM,OAAO,KAAK,MAAM;AACxB,SAAK,QAAQ,CAAC;AACd,QAAI,OAAO,EAAG,MAAK,OAAO,IAAI,aAAa,mBAAmB,GAAG,IAAI,yCAAyC,SAAS,OAAO,MAAM,CAAC,CAAC;AAAA,EACxI;AAAA,EAEQ,eAAqB;AAC3B,QAAI,KAAK,UAAU,KAAK,aAAc,KAAK,MAAM,WAAW,KAAK,CAAC,KAAK,QAAU;AACjF,SAAK,YAAY,KAAK,MAAM,KAAK,iBAAiB,EAAE,QAAQ,MAAM;AAChE,WAAK,YAAY;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,cAAoB;AAC1B,QAAI,KAAK,uBAAuB,EAAG;AACnC,UAAM,IAAI,KAAK;AACf,SAAK,qBAAqB;AAC1B,SAAK,OAAO,IAAI,aAAa,cAAc,oCAAoC,KAAK,YAAY,kBAAkB,CAAC,oBAAoB,CAAC,CAAC;AAAA,EAC3I;AAAA,EAEQ,OAAO,OAA2B;AACxC,QAAI;AACF,WAAK,QAAQ,KAAK;AAAA,IACpB,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAEA,SAAS,eAAe,QAAgB,SAAkB,OAAe,UAA0B;AACjG,QAAM,OAAO,GAAG,KAAK;AACrB,MAAI,WAAW,EAAG,QAAO,GAAG,IAAI,qCAAqC,QAAQ,WAAW,aAAa,IAAI,KAAK,GAAG,KAAK,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,OAAO,CAAC;AAClL,MAAI,WAAW,OAAO,WAAW,IAAK,QAAO,GAAG,IAAI,gCAAgC,MAAM;AAC1F,MAAI,WAAW,EAAG,QAAO,GAAG,IAAI,UAAU,MAAM,UAAU,QAAQ;AAClE,SAAO,GAAG,IAAI,UAAU,MAAM;AAChC;","names":["id"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/message.ts","../src/retry.ts","../src/client.ts"],"sourcesContent":["export { Guidely, GuidelyError, SDK_NAME, SDK_VERSION, DEFAULT_API_URL } from \"./client\";\nexport type { Environment, FetchLike, GuidelyErrorCode, GuidelyOptions } from \"./client\";\nexport type { GroupMessage, IdentifyMessage, JsonValue, MessageType, Properties, Timestamp, TrackMessage } from \"./message\";\n","import { randomBytes } from \"node:crypto\";\n\n/** Limits shared by every Guidely server SDK (sdks/conformance/track-vectors.json → limits). */\nexport const LIMITS = {\n /** Most messages in one request (the API accepts 1–100). */\n maxBatchMessages: 100,\n /** Most serialized message bytes in one request (the API accepts bodies up to 512 KB). */\n maxBatchBytes: 500_000,\n /** A message larger than this when serialized is dropped and reported. */\n maxMessageBytes: 32_768,\n maxEventLength: 120,\n maxIdLength: 256,\n} as const;\n\nexport type MessageType = \"identify\" | \"track\" | \"group\";\n\n/** JSON-compatible values for traits and properties. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\nexport type Properties = Record<string, unknown>;\n/** A caller-supplied time: a `Date`, epoch milliseconds or an ISO 8601 string with an offset. */\nexport type Timestamp = Date | number | string;\n\nexport interface IdentifyMessage {\n /** Your user id (1–256 characters), the one your frontend passes to `identify()`. */\n userId: string | number;\n /** Merged into the user's traits. */\n traits?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n /** When it happened (default: now). */\n timestamp?: Timestamp;\n}\n\nexport interface TrackMessage {\n /** Your user id; `track` needs `userId` or `anonymousId`. */\n userId?: string | number | null;\n /** The browser SDK's anonymous id (`Guidely.anonymousId`) for visitors who have not signed in. */\n anonymousId?: string | number | null;\n /** The event name (1–120 characters), e.g. `\"Report Exported\"`. */\n event: string;\n properties?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n timestamp?: Timestamp;\n}\n\nexport interface GroupMessage {\n /** The account id (1–256 characters). */\n accountId: string | number;\n /** Merged into the account's traits (`name` also becomes the account name). */\n traits?: Properties | null;\n /** Adds this user to the account. */\n userId?: string | number | null;\n timestamp?: Timestamp;\n}\n\n/** A message as it is sent. */\nexport interface WireMessage {\n type: MessageType;\n messageId: string;\n timestamp: string;\n userId?: string;\n anonymousId?: string;\n event?: string;\n accountId?: string;\n traits?: Record<string, unknown>;\n properties?: Record<string, unknown>;\n}\n\nexport class InvalidMessageError extends Error {}\n\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\n/** 24 random characters from [0-9A-Za-z] (unbiased). */\nexport function newMessageId(): string {\n let id = \"\";\n while (id.length < 24) {\n for (const b of randomBytes(32)) {\n if (b < 248) id += ALPHABET[b % 62]; // 248 = 4 × 62: reject the remainder to stay unbiased\n if (id.length === 24) break;\n }\n }\n return id;\n}\n\n/** ISO 8601 UTC with milliseconds, e.g. `2026-10-26T10:00:00.000Z`. */\nexport function formatTimestamp(value: Timestamp): string {\n let date: Date;\n if (value instanceof Date) date = value;\n else if (typeof value === \"number\") date = new Date(value);\n else if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})$/i.test(value.trim())) date = new Date(value.trim());\n else throw new InvalidMessageError(\"timestamp must be a Date, epoch milliseconds or an ISO 8601 string with an offset\");\n if (Number.isNaN(date.getTime())) throw new InvalidMessageError(\"timestamp is not a valid date\");\n return date.toISOString();\n}\n\n/** UTF-16 code units, like JavaScript's `length` (and the server's validation). */\nconst length = (s: string) => s.length;\n\n/**\n * An id field. `required` ids must be present; optional ones treat `null`, `undefined` and `\"\"` as\n * absent. Integers become strings; anything else that is not a non-blank string ≤ 256 is invalid.\n */\nfunction id(name: string, value: unknown, required: boolean): string | undefined {\n if (value === undefined || value === null || (!required && value === \"\")) {\n if (required) throw new InvalidMessageError(`${name} is required`);\n return undefined;\n }\n let s: string;\n if (typeof value === \"string\") s = value;\n else if (typeof value === \"number\" && Number.isSafeInteger(value)) s = String(value);\n else throw new InvalidMessageError(`${name} must be a string`);\n if (s.trim() === \"\") throw new InvalidMessageError(`${name} must not be empty`);\n if (length(s) > LIMITS.maxIdLength) throw new InvalidMessageError(`${name} is longer than ${LIMITS.maxIdLength} characters`);\n return s;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction object(name: string, value: unknown): Record<string, unknown> | undefined {\n if (value === undefined || value === null) return undefined;\n if (!isPlainObject(value)) throw new InvalidMessageError(`${name} must be an object`);\n return value;\n}\n\n/**\n * Validates a call and returns the message to send (without serializing it). Throws\n * {@link InvalidMessageError} when the call must be dropped.\n */\nexport function buildMessage(type: MessageType, args: unknown, now: () => Date = () => new Date()): WireMessage {\n if (!isPlainObject(args)) throw new InvalidMessageError(`${type}() needs an object argument`);\n const timestamp = args.timestamp === undefined || args.timestamp === null ? now().toISOString() : formatTimestamp(args.timestamp as Timestamp);\n const base = { type, messageId: newMessageId(), timestamp };\n switch (type) {\n case \"identify\": {\n const userId = id(\"userId\", args.userId, true);\n const traits = object(\"traits\", args.traits);\n const accountId = id(\"accountId\", args.accountId, false);\n return { ...base, userId, ...(traits ? { traits } : {}), ...(accountId ? { accountId } : {}) };\n }\n case \"track\": {\n const userId = id(\"userId\", args.userId, false);\n const anonymousId = id(\"anonymousId\", args.anonymousId, false);\n if (!userId && !anonymousId) throw new InvalidMessageError(\"track() needs a userId or an anonymousId\");\n const event = args.event;\n if (typeof event !== \"string\" || event.trim() === \"\") throw new InvalidMessageError(\"track() needs an event name\");\n if (length(event) > LIMITS.maxEventLength) throw new InvalidMessageError(`event is longer than ${LIMITS.maxEventLength} characters`);\n const properties = object(\"properties\", args.properties);\n const accountId = id(\"accountId\", args.accountId, false);\n return {\n ...base,\n ...(userId ? { userId } : {}),\n ...(anonymousId ? { anonymousId } : {}),\n event,\n ...(properties ? { properties } : {}),\n ...(accountId ? { accountId } : {}),\n };\n }\n case \"group\": {\n const accountId = id(\"accountId\", args.accountId, true);\n const traits = object(\"traits\", args.traits);\n const userId = id(\"userId\", args.userId, false);\n return { ...base, accountId, ...(traits ? { traits } : {}), ...(userId ? { userId } : {}) };\n }\n }\n}\n","/** What to do with a batch after an answer (status 0 = no answer: network error or timeout). */\nexport type ResponseAction = \"ok\" | \"retry\" | \"drop\";\n\nexport interface BackoffOptions {\n baseMs: number;\n maxMs: number;\n retryAfterMaxMs: number;\n}\n\nexport const DEFAULT_BACKOFF: BackoffOptions = { baseMs: 1000, maxMs: 30_000, retryAfterMaxMs: 60_000 };\n\n/** 2xx → ok; no answer, 429 and 5xx → retry; anything else (other 4xx, 3xx) → drop. */\nexport function responseAction(status: number): ResponseAction {\n if (status >= 200 && status < 300) return \"ok\";\n if (status === 0 || status === 429 || (status >= 500 && status < 600)) return \"retry\";\n return \"drop\";\n}\n\n/** Retry-After in whole seconds → milliseconds; anything else (HTTP dates, garbage) → null. */\nexport function parseRetryAfter(value: string | null | undefined): number | null {\n if (value == null) return null;\n const v = value.trim();\n return /^\\d+$/.test(v) ? Number(v) * 1000 : null;\n}\n\n/**\n * The wait before retrying after failed attempt `attempt` (1 = the first send), before jitter:\n * `min(baseMs · 2^(attempt−1), maxMs)`, or Retry-After capped at `retryAfterMaxMs`.\n */\nexport function backoffDelay(attempt: number, retryAfter: string | null | undefined, o: BackoffOptions = DEFAULT_BACKOFF): number {\n const ra = parseRetryAfter(retryAfter);\n if (ra !== null) return Math.min(ra, o.retryAfterMaxMs);\n return Math.min(o.baseMs * 2 ** Math.max(0, attempt - 1), o.maxMs);\n}\n\n/** ±20 % jitter on a computed backoff (not on Retry-After). `random` is in [0, 1). */\nexport function jitter(delayMs: number, random: number): number {\n return Math.round(delayMs * (0.8 + 0.4 * random));\n}\n","import { buildMessage, InvalidMessageError, LIMITS, type GroupMessage, type IdentifyMessage, type MessageType, type TrackMessage } from \"./message\";\nimport { backoffDelay, DEFAULT_BACKOFF, jitter, parseRetryAfter, responseAction } from \"./retry\";\n\nexport const SDK_NAME = \"guidely-node\";\nexport const SDK_VERSION = \"0.16.0\";\nexport const DEFAULT_API_URL = \"https://guidelytech.com\";\nexport const BATCH_PATH = \"/api/v1/sdk/server/batch\";\n\nexport type Environment = \"development\" | \"staging\" | \"production\";\nconst ENVIRONMENTS: readonly string[] = [\"development\", \"staging\", \"production\"];\n\n/** The subset of `fetch` the SDK uses, so any implementation (undici, node-fetch, a test fake) fits. */\nexport type FetchLike = (\n url: string,\n init: { method: \"POST\"; headers: Record<string, string>; body: string; signal: AbortSignal },\n) => Promise<{ status: number; headers: { get(name: string): string | null }; text?: () => Promise<string> }>;\n\nexport type GuidelyErrorCode =\n /** A call was invalid (missing userId, empty event…) and dropped. */\n | \"invalid_message\"\n /** A message serialized to more than 32 KB and was dropped. */\n | \"message_too_large\"\n /** The queue was full (`maxQueueSize`); the oldest messages were dropped. */\n | \"queue_full\"\n /** A batch was rejected (4xx), or failed after every retry, or was cut short by a shutdown timeout. */\n | \"delivery_failed\"\n /** A call was made after `shutdown()`. */\n | \"closed\";\n\n/** Reported to `onError`; never thrown by tracking calls. */\nexport class GuidelyError extends Error {\n override readonly name = \"GuidelyError\";\n constructor(\n readonly code: GuidelyErrorCode,\n message: string,\n /** How many messages were dropped. */\n readonly count = 1,\n /** The HTTP status, for `delivery_failed` (0 when no answer was received). */\n readonly status?: number,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\nexport interface GuidelyOptions {\n /** A secret API key (`gly_sk_…`) with the `events:write` scope. Never ship it to a browser. */\n secretKey: string;\n /** The environment events are written to. */\n environment: Environment;\n /** API origin (default `https://guidelytech.com`). */\n apiUrl?: string;\n /** Send a batch when this many messages are queued (default 100; batches hold at most 100). */\n flushAt?: number;\n /** Send what is queued at least this often (default 5000 ms). */\n flushIntervalMs?: number;\n /** Most messages held in memory; beyond it the oldest are dropped (default 10,000). */\n maxQueueSize?: number;\n /** Retries of a batch after network errors, 429 and 5xx (default 5, so up to 6 attempts). */\n maxRetries?: number;\n /** How long `shutdown()` (and the exit hook) waits for queued messages to go out (default 10,000 ms). */\n shutdownTimeoutMs?: number;\n /** Timeout of one HTTP request (default 10,000 ms). */\n timeoutMs?: number;\n /** Receives every problem (default: `console.warn`). Called synchronously; must not throw. */\n onError?: (error: GuidelyError) => void;\n /** `fetch` implementation (default: the global `fetch` of Node 18+). */\n fetch?: FetchLike;\n /** Flush on the process's `beforeExit` event (default true). Signals and `process.exit()` skip it: call `shutdown()`. */\n handleExit?: boolean;\n}\n\ninterface Queued {\n json: string;\n bytes: number;\n}\n\ninterface Sleep {\n timer: ReturnType<typeof setTimeout> | undefined;\n resolve: () => void;\n}\n\nconst int = (value: number | undefined, fallback: number, min: number) =>\n typeof value === \"number\" && Number.isFinite(value) ? Math.max(min, Math.floor(value)) : fallback;\n\n/**\n * Server-side Guidely client: `identify`, `track` and `group` from your backend.\n *\n * ```ts\n * const guidely = new Guidely({ secretKey: process.env.GUIDELY_SECRET_KEY!, environment: \"production\" });\n * guidely.track({ userId: \"u_42\", event: \"Report Exported\", properties: { format: \"csv\" } });\n * await guidely.shutdown(); // before the process exits\n * ```\n *\n * Calls validate, queue and return: they never throw and never wait for the network. Messages go out\n * in batches (every `flushAt` messages, every `flushIntervalMs`, on `flush()`), one request at a time,\n * retried with backoff when Guidely is unreachable.\n */\nexport class Guidely {\n private readonly url: string;\n private readonly headers: Record<string, string>;\n private readonly flushAt: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly maxRetries: number;\n private readonly shutdownTimeoutMs: number;\n private readonly timeoutMs: number;\n private readonly onError: (error: GuidelyError) => void;\n private readonly fetch: FetchLike;\n private readonly sdkJson = JSON.stringify({ name: SDK_NAME, version: SDK_VERSION });\n\n private queue: Queued[] = [];\n private droppedSinceReport = 0;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private pumping: Promise<void> | null = null;\n private drainAll = false;\n /** Bumped to abandon the running pump (shutdown timeout): its request is aborted and its sleeps end. */\n private epoch = 0;\n private inflight: AbortController | null = null;\n private readonly sleeps = new Set<Sleep>();\n private closed = false;\n private shutdownPromise: Promise<void> | null = null;\n private exitDrain: Promise<void> | null = null;\n private readonly exitListener: (() => void) | null = null;\n\n constructor(options: GuidelyOptions) {\n const o = options ?? ({} as GuidelyOptions);\n const key = typeof o.secretKey === \"string\" ? o.secretKey.trim() : \"\";\n if (!key.startsWith(\"gly_sk_\")) {\n throw new TypeError(\"Guidely: secretKey must be a secret API key (gly_sk_…) with the events:write scope\");\n }\n if (!ENVIRONMENTS.includes(o.environment)) {\n throw new TypeError('Guidely: environment must be \"development\", \"staging\" or \"production\"');\n }\n const fetchImpl = o.fetch ?? (typeof globalThis.fetch === \"function\" ? (globalThis.fetch.bind(globalThis) as unknown as FetchLike) : undefined);\n if (!fetchImpl) throw new TypeError(\"Guidely: no global fetch (Node 18+ is required), pass the fetch option\");\n this.fetch = fetchImpl;\n const apiUrl = (o.apiUrl ?? DEFAULT_API_URL).trim().replace(/\\/+$/, \"\");\n this.url = `${apiUrl}${BATCH_PATH}?environment=${encodeURIComponent(o.environment)}`;\n this.headers = {\n Authorization: `Bearer ${key}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"User-Agent\": `${SDK_NAME}/${SDK_VERSION} (Node ${typeof process !== \"undefined\" ? process.version : \"unknown\"})`,\n };\n this.flushAt = int(o.flushAt, 100, 1);\n this.flushIntervalMs = int(o.flushIntervalMs, 5000, 1);\n this.maxQueueSize = int(o.maxQueueSize, 10_000, 1);\n this.maxRetries = int(o.maxRetries, 5, 0);\n this.shutdownTimeoutMs = int(o.shutdownTimeoutMs, 10_000, 0);\n this.timeoutMs = int(o.timeoutMs, 10_000, 1);\n this.onError = o.onError ?? ((e) => console.warn(`[${SDK_NAME}] ${e.message}`));\n if (o.handleExit !== false && typeof process !== \"undefined\" && typeof process.on === \"function\") {\n this.exitListener = () => this.onBeforeExit();\n process.on(\"beforeExit\", this.exitListener);\n }\n }\n\n /** Who a user is: merges `traits` into the user (creating it if needed), optionally linking an account. */\n identify(message: IdentifyMessage): void {\n this.enqueue(\"identify\", message);\n }\n\n /** What a user (or anonymous visitor) did. */\n track(message: TrackMessage): void {\n this.enqueue(\"track\", message);\n }\n\n /** Account traits, and optionally adds `userId` to the account. */\n group(message: GroupMessage): void {\n this.enqueue(\"group\", message);\n }\n\n /** Sends everything queued; resolves once it has been delivered or dropped. Never rejects. */\n flush(): Promise<void> {\n return this.startPump(true);\n }\n\n /**\n * Stops the timer and the exit hook, then flushes, waiting at most `shutdownTimeoutMs`. Later calls\n * are dropped (and reported). Call it before your process exits. Never rejects.\n */\n shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.closed = true;\n this.clearTimer();\n if (this.exitListener) process.removeListener(\"beforeExit\", this.exitListener);\n this.shutdownPromise = this.drain(this.shutdownTimeoutMs);\n return this.shutdownPromise;\n }\n\n // --- queueing ------------------------------------------------------------------------------\n\n private enqueue(type: MessageType, args: unknown): void {\n try {\n if (this.closed) {\n this.report(new GuidelyError(\"closed\", `${type}() was called after shutdown(); the message was dropped`));\n return;\n }\n const message = buildMessage(type, args);\n let json: string;\n try {\n json = JSON.stringify(message);\n } catch (e) {\n throw new InvalidMessageError(`could not be serialized to JSON (${e instanceof Error ? e.message : String(e)})`);\n }\n const bytes = Buffer.byteLength(json, \"utf8\");\n if (bytes > LIMITS.maxMessageBytes) {\n this.report(new GuidelyError(\"message_too_large\", `${type}() message is ${bytes} bytes, more than the ${LIMITS.maxMessageBytes}-byte limit; it was dropped`));\n return;\n }\n if (this.queue.length >= this.maxQueueSize) {\n this.queue.shift();\n this.droppedSinceReport++;\n }\n this.queue.push({ json, bytes });\n if (this.queue.length >= this.flushAt) void this.startPump(false);\n else this.scheduleTimer();\n } catch (e) {\n if (e instanceof InvalidMessageError) this.report(new GuidelyError(\"invalid_message\", `Invalid ${type}() call dropped: ${e.message}`));\n else this.report(new GuidelyError(\"invalid_message\", `${type}() failed: ${e instanceof Error ? e.message : String(e)}`, 1, undefined, { cause: e }));\n }\n }\n\n private scheduleTimer(): void {\n if (this.timer !== undefined || this.closed) return;\n this.timer = setTimeout(() => {\n this.timer = undefined;\n void this.startPump(true);\n }, this.flushIntervalMs);\n (this.timer as { unref?: () => void }).unref?.();\n }\n\n private clearTimer(): void {\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n // --- sending -------------------------------------------------------------------------------\n\n /** Starts (or joins) the single sender. `all`: send everything, not only full batches. */\n private startPump(all: boolean): Promise<void> {\n if (all) this.drainAll = true;\n if (!this.pumping) this.pumping = this.pump();\n return this.pumping;\n }\n\n private async pump(): Promise<void> {\n await Promise.resolve(); // `this.pumping` is assigned before the loop can finish\n const epoch = this.epoch;\n try {\n const cap = Math.min(this.flushAt, LIMITS.maxBatchMessages);\n for (;;) {\n this.reportDrops();\n if (epoch !== this.epoch || this.queue.length === 0 || (!this.drainAll && this.queue.length < cap)) break;\n await this.deliver(this.takeBatch(cap), epoch);\n }\n } catch (e) {\n this.report(new GuidelyError(\"delivery_failed\", `Unexpected error while sending: ${e instanceof Error ? e.message : String(e)}`, 0, undefined, { cause: e }));\n } finally {\n this.pumping = null;\n this.drainAll = false;\n if (this.queue.length > 0) this.scheduleTimer();\n }\n }\n\n /** Up to `cap` messages from the head of the queue, at most `maxBatchBytes` of them (at least one). */\n private takeBatch(cap: number): Queued[] {\n let n = 0;\n let size = 0;\n while (n < this.queue.length && n < cap) {\n const next = this.queue[n]!;\n if (n > 0 && size + next.bytes > LIMITS.maxBatchBytes) break;\n size += next.bytes;\n n++;\n }\n return this.queue.splice(0, n);\n }\n\n private async deliver(batch: Queued[], epoch: number): Promise<void> {\n const messages = batch.map((m) => m.json).join(\",\");\n for (let attempt = 1; ; attempt++) {\n if (epoch !== this.epoch) {\n this.report(new GuidelyError(\"delivery_failed\", `${batch.length} messages were not sent before the shutdown timeout`, batch.length, 0));\n return;\n }\n const body = `{\"batch\":[${messages}],\"sentAt\":${JSON.stringify(new Date().toISOString())},\"sdk\":${this.sdkJson}}`;\n let status = 0;\n let retryAfter: string | null = null;\n let failure: unknown;\n try {\n const res = await this.send(body);\n status = res.status;\n retryAfter = res.headers.get(\"retry-after\");\n } catch (e) {\n failure = e;\n }\n const action = responseAction(status);\n if (action === \"ok\") return;\n if (epoch !== this.epoch) continue; // aborted while sending: reported at the top\n if (action === \"drop\" || attempt > this.maxRetries) {\n this.report(new GuidelyError(\"delivery_failed\", failureMessage(status, failure, batch.length, attempt), batch.length, status, { cause: failure }));\n return;\n }\n const delay = parseRetryAfter(retryAfter) !== null ? backoffDelay(attempt, retryAfter) : jitter(backoffDelay(attempt, null, DEFAULT_BACKOFF), Math.random());\n await this.sleep(delay);\n }\n }\n\n private async send(body: string) {\n const controller = new AbortController();\n this.inflight = controller;\n const timeout = setTimeout(() => controller.abort(new Error(`request timed out after ${this.timeoutMs} ms`)), this.timeoutMs);\n try {\n const res = await this.fetch(this.url, { method: \"POST\", headers: this.headers, body, signal: controller.signal });\n try {\n await res.text?.(); // release the connection\n } catch {\n // the status is what matters\n }\n return res;\n } finally {\n clearTimeout(timeout);\n if (this.inflight === controller) this.inflight = null;\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const s: Sleep = { timer: undefined, resolve };\n s.timer = setTimeout(() => {\n this.sleeps.delete(s);\n resolve();\n }, ms);\n this.sleeps.add(s);\n });\n }\n\n /** Flushes, giving up after `timeoutMs`: the request in flight is aborted and what is left is dropped. */\n private async drain(timeoutMs: number): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = await Promise.race([\n this.startPump(true).then(() => false),\n new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(true), timeoutMs);\n }),\n ]);\n clearTimeout(timer);\n if (!timedOut) return;\n this.epoch++;\n this.inflight?.abort(new Error(\"shutdown timeout\"));\n for (const s of this.sleeps) {\n clearTimeout(s.timer);\n s.resolve();\n }\n this.sleeps.clear();\n const left = this.queue.length;\n this.queue = [];\n if (left > 0) this.report(new GuidelyError(\"delivery_failed\", `${left} queued messages were not sent within ${timeoutMs} ms`, left, 0));\n }\n\n private onBeforeExit(): void {\n if (this.closed || this.exitDrain || (this.queue.length === 0 && !this.pumping)) return;\n this.exitDrain = this.drain(this.shutdownTimeoutMs).finally(() => {\n this.exitDrain = null;\n });\n }\n\n // --- errors --------------------------------------------------------------------------------\n\n private reportDrops(): void {\n if (this.droppedSinceReport === 0) return;\n const n = this.droppedSinceReport;\n this.droppedSinceReport = 0;\n this.report(new GuidelyError(\"queue_full\", `The queue was full (maxQueueSize ${this.maxQueueSize}): dropped the ${n} oldest messages`, n));\n }\n\n private report(error: GuidelyError): void {\n try {\n this.onError(error);\n } catch {\n // an error handler must never break the caller\n }\n }\n}\n\nfunction failureMessage(status: number, failure: unknown, count: number, attempts: number): string {\n const what = `${count} messages were dropped`;\n if (status === 0) return `${what}: Guidely could not be reached in ${attempts} attempt${attempts === 1 ? \"\" : \"s\"} (${failure instanceof Error ? failure.message : String(failure)})`;\n if (status === 401 || status === 403) return `${what}: the key was rejected (HTTP ${status}). Check it is a gly_sk_ key with the events:write scope for this environment`;\n if (attempts > 1) return `${what}: HTTP ${status} after ${attempts} attempts`;\n return `${what}: HTTP ${status}`;\n}\n"],"mappings":"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","names":["id"]}
package/dist/index.d.cts CHANGED
@@ -39,7 +39,7 @@ interface GroupMessage {
39
39
  }
40
40
 
41
41
  declare const SDK_NAME = "guidely-node";
42
- declare const SDK_VERSION = "0.15.0";
42
+ declare const SDK_VERSION = "0.16.0";
43
43
  declare const DEFAULT_API_URL = "https://guidelytech.com";
44
44
  type Environment = "development" | "staging" | "production";
45
45
  /** The subset of `fetch` the SDK uses, so any implementation (undici, node-fetch, a test fake) fits. */
package/dist/index.d.ts CHANGED
@@ -39,7 +39,7 @@ interface GroupMessage {
39
39
  }
40
40
 
41
41
  declare const SDK_NAME = "guidely-node";
42
- declare const SDK_VERSION = "0.15.0";
42
+ declare const SDK_VERSION = "0.16.0";
43
43
  declare const DEFAULT_API_URL = "https://guidelytech.com";
44
44
  type Environment = "development" | "staging" | "production";
45
45
  /** The subset of `fetch` the SDK uses, so any implementation (undici, node-fetch, a test fake) fits. */
package/dist/index.js CHANGED
@@ -117,7 +117,7 @@ function jitter(delayMs, random) {
117
117
 
118
118
  // src/client.ts
119
119
  var SDK_NAME = "guidely-node";
120
- var SDK_VERSION = "0.15.0";
120
+ var SDK_VERSION = "0.16.0";
121
121
  var DEFAULT_API_URL = "https://guidelytech.com";
122
122
  var BATCH_PATH = "/api/v1/sdk/server/batch";
123
123
  var ENVIRONMENTS = ["development", "staging", "production"];
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/message.ts","../src/retry.ts","../src/client.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\n\n/** Limits shared by every Guidely server SDK (sdks/conformance/track-vectors.json → limits). */\nexport const LIMITS = {\n /** Most messages in one request (the API accepts 1–100). */\n maxBatchMessages: 100,\n /** Most serialized message bytes in one request (the API accepts bodies up to 512 KB). */\n maxBatchBytes: 500_000,\n /** A message larger than this when serialized is dropped and reported. */\n maxMessageBytes: 32_768,\n maxEventLength: 120,\n maxIdLength: 256,\n} as const;\n\nexport type MessageType = \"identify\" | \"track\" | \"group\";\n\n/** JSON-compatible values for traits and properties. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\nexport type Properties = Record<string, unknown>;\n/** A caller-supplied time: a `Date`, epoch milliseconds or an ISO 8601 string with an offset. */\nexport type Timestamp = Date | number | string;\n\nexport interface IdentifyMessage {\n /** Your user id (1–256 characters), the one your frontend passes to `identify()`. */\n userId: string | number;\n /** Merged into the user's traits. */\n traits?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n /** When it happened (default: now). */\n timestamp?: Timestamp;\n}\n\nexport interface TrackMessage {\n /** Your user id; `track` needs `userId` or `anonymousId`. */\n userId?: string | number | null;\n /** The browser SDK's anonymous id (`Guidely.anonymousId`) for visitors who have not signed in. */\n anonymousId?: string | number | null;\n /** The event name (1–120 characters), e.g. `\"Report Exported\"`. */\n event: string;\n properties?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n timestamp?: Timestamp;\n}\n\nexport interface GroupMessage {\n /** The account id (1–256 characters). */\n accountId: string | number;\n /** Merged into the account's traits (`name` also becomes the account name). */\n traits?: Properties | null;\n /** Adds this user to the account. */\n userId?: string | number | null;\n timestamp?: Timestamp;\n}\n\n/** A message as it is sent. */\nexport interface WireMessage {\n type: MessageType;\n messageId: string;\n timestamp: string;\n userId?: string;\n anonymousId?: string;\n event?: string;\n accountId?: string;\n traits?: Record<string, unknown>;\n properties?: Record<string, unknown>;\n}\n\nexport class InvalidMessageError extends Error {}\n\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\n/** 24 random characters from [0-9A-Za-z] (unbiased). */\nexport function newMessageId(): string {\n let id = \"\";\n while (id.length < 24) {\n for (const b of randomBytes(32)) {\n if (b < 248) id += ALPHABET[b % 62]; // 248 = 4 × 62: reject the remainder to stay unbiased\n if (id.length === 24) break;\n }\n }\n return id;\n}\n\n/** ISO 8601 UTC with milliseconds, e.g. `2026-10-26T10:00:00.000Z`. */\nexport function formatTimestamp(value: Timestamp): string {\n let date: Date;\n if (value instanceof Date) date = value;\n else if (typeof value === \"number\") date = new Date(value);\n else if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})$/i.test(value.trim())) date = new Date(value.trim());\n else throw new InvalidMessageError(\"timestamp must be a Date, epoch milliseconds or an ISO 8601 string with an offset\");\n if (Number.isNaN(date.getTime())) throw new InvalidMessageError(\"timestamp is not a valid date\");\n return date.toISOString();\n}\n\n/** UTF-16 code units, like JavaScript's `length` (and the server's validation). */\nconst length = (s: string) => s.length;\n\n/**\n * An id field. `required` ids must be present; optional ones treat `null`, `undefined` and `\"\"` as\n * absent. Integers become strings; anything else that is not a non-blank string ≤ 256 is invalid.\n */\nfunction id(name: string, value: unknown, required: boolean): string | undefined {\n if (value === undefined || value === null || (!required && value === \"\")) {\n if (required) throw new InvalidMessageError(`${name} is required`);\n return undefined;\n }\n let s: string;\n if (typeof value === \"string\") s = value;\n else if (typeof value === \"number\" && Number.isSafeInteger(value)) s = String(value);\n else throw new InvalidMessageError(`${name} must be a string`);\n if (s.trim() === \"\") throw new InvalidMessageError(`${name} must not be empty`);\n if (length(s) > LIMITS.maxIdLength) throw new InvalidMessageError(`${name} is longer than ${LIMITS.maxIdLength} characters`);\n return s;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction object(name: string, value: unknown): Record<string, unknown> | undefined {\n if (value === undefined || value === null) return undefined;\n if (!isPlainObject(value)) throw new InvalidMessageError(`${name} must be an object`);\n return value;\n}\n\n/**\n * Validates a call and returns the message to send (without serializing it). Throws\n * {@link InvalidMessageError} when the call must be dropped.\n */\nexport function buildMessage(type: MessageType, args: unknown, now: () => Date = () => new Date()): WireMessage {\n if (!isPlainObject(args)) throw new InvalidMessageError(`${type}() needs an object argument`);\n const timestamp = args.timestamp === undefined || args.timestamp === null ? now().toISOString() : formatTimestamp(args.timestamp as Timestamp);\n const base = { type, messageId: newMessageId(), timestamp };\n switch (type) {\n case \"identify\": {\n const userId = id(\"userId\", args.userId, true);\n const traits = object(\"traits\", args.traits);\n const accountId = id(\"accountId\", args.accountId, false);\n return { ...base, userId, ...(traits ? { traits } : {}), ...(accountId ? { accountId } : {}) };\n }\n case \"track\": {\n const userId = id(\"userId\", args.userId, false);\n const anonymousId = id(\"anonymousId\", args.anonymousId, false);\n if (!userId && !anonymousId) throw new InvalidMessageError(\"track() needs a userId or an anonymousId\");\n const event = args.event;\n if (typeof event !== \"string\" || event.trim() === \"\") throw new InvalidMessageError(\"track() needs an event name\");\n if (length(event) > LIMITS.maxEventLength) throw new InvalidMessageError(`event is longer than ${LIMITS.maxEventLength} characters`);\n const properties = object(\"properties\", args.properties);\n const accountId = id(\"accountId\", args.accountId, false);\n return {\n ...base,\n ...(userId ? { userId } : {}),\n ...(anonymousId ? { anonymousId } : {}),\n event,\n ...(properties ? { properties } : {}),\n ...(accountId ? { accountId } : {}),\n };\n }\n case \"group\": {\n const accountId = id(\"accountId\", args.accountId, true);\n const traits = object(\"traits\", args.traits);\n const userId = id(\"userId\", args.userId, false);\n return { ...base, accountId, ...(traits ? { traits } : {}), ...(userId ? { userId } : {}) };\n }\n }\n}\n","/** What to do with a batch after an answer (status 0 = no answer: network error or timeout). */\nexport type ResponseAction = \"ok\" | \"retry\" | \"drop\";\n\nexport interface BackoffOptions {\n baseMs: number;\n maxMs: number;\n retryAfterMaxMs: number;\n}\n\nexport const DEFAULT_BACKOFF: BackoffOptions = { baseMs: 1000, maxMs: 30_000, retryAfterMaxMs: 60_000 };\n\n/** 2xx → ok; no answer, 429 and 5xx → retry; anything else (other 4xx, 3xx) → drop. */\nexport function responseAction(status: number): ResponseAction {\n if (status >= 200 && status < 300) return \"ok\";\n if (status === 0 || status === 429 || (status >= 500 && status < 600)) return \"retry\";\n return \"drop\";\n}\n\n/** Retry-After in whole seconds → milliseconds; anything else (HTTP dates, garbage) → null. */\nexport function parseRetryAfter(value: string | null | undefined): number | null {\n if (value == null) return null;\n const v = value.trim();\n return /^\\d+$/.test(v) ? Number(v) * 1000 : null;\n}\n\n/**\n * The wait before retrying after failed attempt `attempt` (1 = the first send), before jitter:\n * `min(baseMs · 2^(attempt−1), maxMs)`, or Retry-After capped at `retryAfterMaxMs`.\n */\nexport function backoffDelay(attempt: number, retryAfter: string | null | undefined, o: BackoffOptions = DEFAULT_BACKOFF): number {\n const ra = parseRetryAfter(retryAfter);\n if (ra !== null) return Math.min(ra, o.retryAfterMaxMs);\n return Math.min(o.baseMs * 2 ** Math.max(0, attempt - 1), o.maxMs);\n}\n\n/** ±20 % jitter on a computed backoff (not on Retry-After). `random` is in [0, 1). */\nexport function jitter(delayMs: number, random: number): number {\n return Math.round(delayMs * (0.8 + 0.4 * random));\n}\n","import { buildMessage, InvalidMessageError, LIMITS, type GroupMessage, type IdentifyMessage, type MessageType, type TrackMessage } from \"./message\";\nimport { backoffDelay, DEFAULT_BACKOFF, jitter, parseRetryAfter, responseAction } from \"./retry\";\n\nexport const SDK_NAME = \"guidely-node\";\nexport const SDK_VERSION = \"0.15.0\";\nexport const DEFAULT_API_URL = \"https://guidelytech.com\";\nexport const BATCH_PATH = \"/api/v1/sdk/server/batch\";\n\nexport type Environment = \"development\" | \"staging\" | \"production\";\nconst ENVIRONMENTS: readonly string[] = [\"development\", \"staging\", \"production\"];\n\n/** The subset of `fetch` the SDK uses, so any implementation (undici, node-fetch, a test fake) fits. */\nexport type FetchLike = (\n url: string,\n init: { method: \"POST\"; headers: Record<string, string>; body: string; signal: AbortSignal },\n) => Promise<{ status: number; headers: { get(name: string): string | null }; text?: () => Promise<string> }>;\n\nexport type GuidelyErrorCode =\n /** A call was invalid (missing userId, empty event…) and dropped. */\n | \"invalid_message\"\n /** A message serialized to more than 32 KB and was dropped. */\n | \"message_too_large\"\n /** The queue was full (`maxQueueSize`); the oldest messages were dropped. */\n | \"queue_full\"\n /** A batch was rejected (4xx), or failed after every retry, or was cut short by a shutdown timeout. */\n | \"delivery_failed\"\n /** A call was made after `shutdown()`. */\n | \"closed\";\n\n/** Reported to `onError`; never thrown by tracking calls. */\nexport class GuidelyError extends Error {\n override readonly name = \"GuidelyError\";\n constructor(\n readonly code: GuidelyErrorCode,\n message: string,\n /** How many messages were dropped. */\n readonly count = 1,\n /** The HTTP status, for `delivery_failed` (0 when no answer was received). */\n readonly status?: number,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\nexport interface GuidelyOptions {\n /** A secret API key (`gly_sk_…`) with the `events:write` scope. Never ship it to a browser. */\n secretKey: string;\n /** The environment events are written to. */\n environment: Environment;\n /** API origin (default `https://guidelytech.com`). */\n apiUrl?: string;\n /** Send a batch when this many messages are queued (default 100; batches hold at most 100). */\n flushAt?: number;\n /** Send what is queued at least this often (default 5000 ms). */\n flushIntervalMs?: number;\n /** Most messages held in memory; beyond it the oldest are dropped (default 10,000). */\n maxQueueSize?: number;\n /** Retries of a batch after network errors, 429 and 5xx (default 5, so up to 6 attempts). */\n maxRetries?: number;\n /** How long `shutdown()` (and the exit hook) waits for queued messages to go out (default 10,000 ms). */\n shutdownTimeoutMs?: number;\n /** Timeout of one HTTP request (default 10,000 ms). */\n timeoutMs?: number;\n /** Receives every problem (default: `console.warn`). Called synchronously; must not throw. */\n onError?: (error: GuidelyError) => void;\n /** `fetch` implementation (default: the global `fetch` of Node 18+). */\n fetch?: FetchLike;\n /** Flush on the process's `beforeExit` event (default true). Signals and `process.exit()` skip it: call `shutdown()`. */\n handleExit?: boolean;\n}\n\ninterface Queued {\n json: string;\n bytes: number;\n}\n\ninterface Sleep {\n timer: ReturnType<typeof setTimeout> | undefined;\n resolve: () => void;\n}\n\nconst int = (value: number | undefined, fallback: number, min: number) =>\n typeof value === \"number\" && Number.isFinite(value) ? Math.max(min, Math.floor(value)) : fallback;\n\n/**\n * Server-side Guidely client: `identify`, `track` and `group` from your backend.\n *\n * ```ts\n * const guidely = new Guidely({ secretKey: process.env.GUIDELY_SECRET_KEY!, environment: \"production\" });\n * guidely.track({ userId: \"u_42\", event: \"Report Exported\", properties: { format: \"csv\" } });\n * await guidely.shutdown(); // before the process exits\n * ```\n *\n * Calls validate, queue and return: they never throw and never wait for the network. Messages go out\n * in batches (every `flushAt` messages, every `flushIntervalMs`, on `flush()`), one request at a time,\n * retried with backoff when Guidely is unreachable.\n */\nexport class Guidely {\n private readonly url: string;\n private readonly headers: Record<string, string>;\n private readonly flushAt: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly maxRetries: number;\n private readonly shutdownTimeoutMs: number;\n private readonly timeoutMs: number;\n private readonly onError: (error: GuidelyError) => void;\n private readonly fetch: FetchLike;\n private readonly sdkJson = JSON.stringify({ name: SDK_NAME, version: SDK_VERSION });\n\n private queue: Queued[] = [];\n private droppedSinceReport = 0;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private pumping: Promise<void> | null = null;\n private drainAll = false;\n /** Bumped to abandon the running pump (shutdown timeout): its request is aborted and its sleeps end. */\n private epoch = 0;\n private inflight: AbortController | null = null;\n private readonly sleeps = new Set<Sleep>();\n private closed = false;\n private shutdownPromise: Promise<void> | null = null;\n private exitDrain: Promise<void> | null = null;\n private readonly exitListener: (() => void) | null = null;\n\n constructor(options: GuidelyOptions) {\n const o = options ?? ({} as GuidelyOptions);\n const key = typeof o.secretKey === \"string\" ? o.secretKey.trim() : \"\";\n if (!key.startsWith(\"gly_sk_\")) {\n throw new TypeError(\"Guidely: secretKey must be a secret API key (gly_sk_…) with the events:write scope\");\n }\n if (!ENVIRONMENTS.includes(o.environment)) {\n throw new TypeError('Guidely: environment must be \"development\", \"staging\" or \"production\"');\n }\n const fetchImpl = o.fetch ?? (typeof globalThis.fetch === \"function\" ? (globalThis.fetch.bind(globalThis) as unknown as FetchLike) : undefined);\n if (!fetchImpl) throw new TypeError(\"Guidely: no global fetch (Node 18+ is required), pass the fetch option\");\n this.fetch = fetchImpl;\n const apiUrl = (o.apiUrl ?? DEFAULT_API_URL).trim().replace(/\\/+$/, \"\");\n this.url = `${apiUrl}${BATCH_PATH}?environment=${encodeURIComponent(o.environment)}`;\n this.headers = {\n Authorization: `Bearer ${key}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"User-Agent\": `${SDK_NAME}/${SDK_VERSION} (Node ${typeof process !== \"undefined\" ? process.version : \"unknown\"})`,\n };\n this.flushAt = int(o.flushAt, 100, 1);\n this.flushIntervalMs = int(o.flushIntervalMs, 5000, 1);\n this.maxQueueSize = int(o.maxQueueSize, 10_000, 1);\n this.maxRetries = int(o.maxRetries, 5, 0);\n this.shutdownTimeoutMs = int(o.shutdownTimeoutMs, 10_000, 0);\n this.timeoutMs = int(o.timeoutMs, 10_000, 1);\n this.onError = o.onError ?? ((e) => console.warn(`[${SDK_NAME}] ${e.message}`));\n if (o.handleExit !== false && typeof process !== \"undefined\" && typeof process.on === \"function\") {\n this.exitListener = () => this.onBeforeExit();\n process.on(\"beforeExit\", this.exitListener);\n }\n }\n\n /** Who a user is: merges `traits` into the user (creating it if needed), optionally linking an account. */\n identify(message: IdentifyMessage): void {\n this.enqueue(\"identify\", message);\n }\n\n /** What a user (or anonymous visitor) did. */\n track(message: TrackMessage): void {\n this.enqueue(\"track\", message);\n }\n\n /** Account traits, and optionally adds `userId` to the account. */\n group(message: GroupMessage): void {\n this.enqueue(\"group\", message);\n }\n\n /** Sends everything queued; resolves once it has been delivered or dropped. Never rejects. */\n flush(): Promise<void> {\n return this.startPump(true);\n }\n\n /**\n * Stops the timer and the exit hook, then flushes, waiting at most `shutdownTimeoutMs`. Later calls\n * are dropped (and reported). Call it before your process exits. Never rejects.\n */\n shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.closed = true;\n this.clearTimer();\n if (this.exitListener) process.removeListener(\"beforeExit\", this.exitListener);\n this.shutdownPromise = this.drain(this.shutdownTimeoutMs);\n return this.shutdownPromise;\n }\n\n // --- queueing ------------------------------------------------------------------------------\n\n private enqueue(type: MessageType, args: unknown): void {\n try {\n if (this.closed) {\n this.report(new GuidelyError(\"closed\", `${type}() was called after shutdown(); the message was dropped`));\n return;\n }\n const message = buildMessage(type, args);\n let json: string;\n try {\n json = JSON.stringify(message);\n } catch (e) {\n throw new InvalidMessageError(`could not be serialized to JSON (${e instanceof Error ? e.message : String(e)})`);\n }\n const bytes = Buffer.byteLength(json, \"utf8\");\n if (bytes > LIMITS.maxMessageBytes) {\n this.report(new GuidelyError(\"message_too_large\", `${type}() message is ${bytes} bytes, more than the ${LIMITS.maxMessageBytes}-byte limit; it was dropped`));\n return;\n }\n if (this.queue.length >= this.maxQueueSize) {\n this.queue.shift();\n this.droppedSinceReport++;\n }\n this.queue.push({ json, bytes });\n if (this.queue.length >= this.flushAt) void this.startPump(false);\n else this.scheduleTimer();\n } catch (e) {\n if (e instanceof InvalidMessageError) this.report(new GuidelyError(\"invalid_message\", `Invalid ${type}() call dropped: ${e.message}`));\n else this.report(new GuidelyError(\"invalid_message\", `${type}() failed: ${e instanceof Error ? e.message : String(e)}`, 1, undefined, { cause: e }));\n }\n }\n\n private scheduleTimer(): void {\n if (this.timer !== undefined || this.closed) return;\n this.timer = setTimeout(() => {\n this.timer = undefined;\n void this.startPump(true);\n }, this.flushIntervalMs);\n (this.timer as { unref?: () => void }).unref?.();\n }\n\n private clearTimer(): void {\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n // --- sending -------------------------------------------------------------------------------\n\n /** Starts (or joins) the single sender. `all`: send everything, not only full batches. */\n private startPump(all: boolean): Promise<void> {\n if (all) this.drainAll = true;\n if (!this.pumping) this.pumping = this.pump();\n return this.pumping;\n }\n\n private async pump(): Promise<void> {\n await Promise.resolve(); // `this.pumping` is assigned before the loop can finish\n const epoch = this.epoch;\n try {\n const cap = Math.min(this.flushAt, LIMITS.maxBatchMessages);\n for (;;) {\n this.reportDrops();\n if (epoch !== this.epoch || this.queue.length === 0 || (!this.drainAll && this.queue.length < cap)) break;\n await this.deliver(this.takeBatch(cap), epoch);\n }\n } catch (e) {\n this.report(new GuidelyError(\"delivery_failed\", `Unexpected error while sending: ${e instanceof Error ? e.message : String(e)}`, 0, undefined, { cause: e }));\n } finally {\n this.pumping = null;\n this.drainAll = false;\n if (this.queue.length > 0) this.scheduleTimer();\n }\n }\n\n /** Up to `cap` messages from the head of the queue, at most `maxBatchBytes` of them (at least one). */\n private takeBatch(cap: number): Queued[] {\n let n = 0;\n let size = 0;\n while (n < this.queue.length && n < cap) {\n const next = this.queue[n]!;\n if (n > 0 && size + next.bytes > LIMITS.maxBatchBytes) break;\n size += next.bytes;\n n++;\n }\n return this.queue.splice(0, n);\n }\n\n private async deliver(batch: Queued[], epoch: number): Promise<void> {\n const messages = batch.map((m) => m.json).join(\",\");\n for (let attempt = 1; ; attempt++) {\n if (epoch !== this.epoch) {\n this.report(new GuidelyError(\"delivery_failed\", `${batch.length} messages were not sent before the shutdown timeout`, batch.length, 0));\n return;\n }\n const body = `{\"batch\":[${messages}],\"sentAt\":${JSON.stringify(new Date().toISOString())},\"sdk\":${this.sdkJson}}`;\n let status = 0;\n let retryAfter: string | null = null;\n let failure: unknown;\n try {\n const res = await this.send(body);\n status = res.status;\n retryAfter = res.headers.get(\"retry-after\");\n } catch (e) {\n failure = e;\n }\n const action = responseAction(status);\n if (action === \"ok\") return;\n if (epoch !== this.epoch) continue; // aborted while sending: reported at the top\n if (action === \"drop\" || attempt > this.maxRetries) {\n this.report(new GuidelyError(\"delivery_failed\", failureMessage(status, failure, batch.length, attempt), batch.length, status, { cause: failure }));\n return;\n }\n const delay = parseRetryAfter(retryAfter) !== null ? backoffDelay(attempt, retryAfter) : jitter(backoffDelay(attempt, null, DEFAULT_BACKOFF), Math.random());\n await this.sleep(delay);\n }\n }\n\n private async send(body: string) {\n const controller = new AbortController();\n this.inflight = controller;\n const timeout = setTimeout(() => controller.abort(new Error(`request timed out after ${this.timeoutMs} ms`)), this.timeoutMs);\n try {\n const res = await this.fetch(this.url, { method: \"POST\", headers: this.headers, body, signal: controller.signal });\n try {\n await res.text?.(); // release the connection\n } catch {\n // the status is what matters\n }\n return res;\n } finally {\n clearTimeout(timeout);\n if (this.inflight === controller) this.inflight = null;\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const s: Sleep = { timer: undefined, resolve };\n s.timer = setTimeout(() => {\n this.sleeps.delete(s);\n resolve();\n }, ms);\n this.sleeps.add(s);\n });\n }\n\n /** Flushes, giving up after `timeoutMs`: the request in flight is aborted and what is left is dropped. */\n private async drain(timeoutMs: number): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = await Promise.race([\n this.startPump(true).then(() => false),\n new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(true), timeoutMs);\n }),\n ]);\n clearTimeout(timer);\n if (!timedOut) return;\n this.epoch++;\n this.inflight?.abort(new Error(\"shutdown timeout\"));\n for (const s of this.sleeps) {\n clearTimeout(s.timer);\n s.resolve();\n }\n this.sleeps.clear();\n const left = this.queue.length;\n this.queue = [];\n if (left > 0) this.report(new GuidelyError(\"delivery_failed\", `${left} queued messages were not sent within ${timeoutMs} ms`, left, 0));\n }\n\n private onBeforeExit(): void {\n if (this.closed || this.exitDrain || (this.queue.length === 0 && !this.pumping)) return;\n this.exitDrain = this.drain(this.shutdownTimeoutMs).finally(() => {\n this.exitDrain = null;\n });\n }\n\n // --- errors --------------------------------------------------------------------------------\n\n private reportDrops(): void {\n if (this.droppedSinceReport === 0) return;\n const n = this.droppedSinceReport;\n this.droppedSinceReport = 0;\n this.report(new GuidelyError(\"queue_full\", `The queue was full (maxQueueSize ${this.maxQueueSize}): dropped the ${n} oldest messages`, n));\n }\n\n private report(error: GuidelyError): void {\n try {\n this.onError(error);\n } catch {\n // an error handler must never break the caller\n }\n }\n}\n\nfunction failureMessage(status: number, failure: unknown, count: number, attempts: number): string {\n const what = `${count} messages were dropped`;\n if (status === 0) return `${what}: Guidely could not be reached in ${attempts} attempt${attempts === 1 ? \"\" : \"s\"} (${failure instanceof Error ? failure.message : String(failure)})`;\n if (status === 401 || status === 403) return `${what}: the key was rejected (HTTP ${status}). Check it is a gly_sk_ key with the events:write scope for this environment`;\n if (attempts > 1) return `${what}: HTTP ${status} after ${attempts} attempts`;\n return `${what}: HTTP ${status}`;\n}\n"],"mappings":"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","names":["id"]}
1
+ {"version":3,"sources":["../src/message.ts","../src/retry.ts","../src/client.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\n\n/** Limits shared by every Guidely server SDK (sdks/conformance/track-vectors.json → limits). */\nexport const LIMITS = {\n /** Most messages in one request (the API accepts 1–100). */\n maxBatchMessages: 100,\n /** Most serialized message bytes in one request (the API accepts bodies up to 512 KB). */\n maxBatchBytes: 500_000,\n /** A message larger than this when serialized is dropped and reported. */\n maxMessageBytes: 32_768,\n maxEventLength: 120,\n maxIdLength: 256,\n} as const;\n\nexport type MessageType = \"identify\" | \"track\" | \"group\";\n\n/** JSON-compatible values for traits and properties. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\nexport type Properties = Record<string, unknown>;\n/** A caller-supplied time: a `Date`, epoch milliseconds or an ISO 8601 string with an offset. */\nexport type Timestamp = Date | number | string;\n\nexport interface IdentifyMessage {\n /** Your user id (1–256 characters), the one your frontend passes to `identify()`. */\n userId: string | number;\n /** Merged into the user's traits. */\n traits?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n /** When it happened (default: now). */\n timestamp?: Timestamp;\n}\n\nexport interface TrackMessage {\n /** Your user id; `track` needs `userId` or `anonymousId`. */\n userId?: string | number | null;\n /** The browser SDK's anonymous id (`Guidely.anonymousId`) for visitors who have not signed in. */\n anonymousId?: string | number | null;\n /** The event name (1–120 characters), e.g. `\"Report Exported\"`. */\n event: string;\n properties?: Properties | null;\n /** Links the user to this account. */\n accountId?: string | number | null;\n timestamp?: Timestamp;\n}\n\nexport interface GroupMessage {\n /** The account id (1–256 characters). */\n accountId: string | number;\n /** Merged into the account's traits (`name` also becomes the account name). */\n traits?: Properties | null;\n /** Adds this user to the account. */\n userId?: string | number | null;\n timestamp?: Timestamp;\n}\n\n/** A message as it is sent. */\nexport interface WireMessage {\n type: MessageType;\n messageId: string;\n timestamp: string;\n userId?: string;\n anonymousId?: string;\n event?: string;\n accountId?: string;\n traits?: Record<string, unknown>;\n properties?: Record<string, unknown>;\n}\n\nexport class InvalidMessageError extends Error {}\n\nconst ALPHABET = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\n/** 24 random characters from [0-9A-Za-z] (unbiased). */\nexport function newMessageId(): string {\n let id = \"\";\n while (id.length < 24) {\n for (const b of randomBytes(32)) {\n if (b < 248) id += ALPHABET[b % 62]; // 248 = 4 × 62: reject the remainder to stay unbiased\n if (id.length === 24) break;\n }\n }\n return id;\n}\n\n/** ISO 8601 UTC with milliseconds, e.g. `2026-10-26T10:00:00.000Z`. */\nexport function formatTimestamp(value: Timestamp): string {\n let date: Date;\n if (value instanceof Date) date = value;\n else if (typeof value === \"number\") date = new Date(value);\n else if (typeof value === \"string\" && /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})$/i.test(value.trim())) date = new Date(value.trim());\n else throw new InvalidMessageError(\"timestamp must be a Date, epoch milliseconds or an ISO 8601 string with an offset\");\n if (Number.isNaN(date.getTime())) throw new InvalidMessageError(\"timestamp is not a valid date\");\n return date.toISOString();\n}\n\n/** UTF-16 code units, like JavaScript's `length` (and the server's validation). */\nconst length = (s: string) => s.length;\n\n/**\n * An id field. `required` ids must be present; optional ones treat `null`, `undefined` and `\"\"` as\n * absent. Integers become strings; anything else that is not a non-blank string ≤ 256 is invalid.\n */\nfunction id(name: string, value: unknown, required: boolean): string | undefined {\n if (value === undefined || value === null || (!required && value === \"\")) {\n if (required) throw new InvalidMessageError(`${name} is required`);\n return undefined;\n }\n let s: string;\n if (typeof value === \"string\") s = value;\n else if (typeof value === \"number\" && Number.isSafeInteger(value)) s = String(value);\n else throw new InvalidMessageError(`${name} must be a string`);\n if (s.trim() === \"\") throw new InvalidMessageError(`${name} must not be empty`);\n if (length(s) > LIMITS.maxIdLength) throw new InvalidMessageError(`${name} is longer than ${LIMITS.maxIdLength} characters`);\n return s;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n if (typeof v !== \"object\" || v === null || Array.isArray(v)) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction object(name: string, value: unknown): Record<string, unknown> | undefined {\n if (value === undefined || value === null) return undefined;\n if (!isPlainObject(value)) throw new InvalidMessageError(`${name} must be an object`);\n return value;\n}\n\n/**\n * Validates a call and returns the message to send (without serializing it). Throws\n * {@link InvalidMessageError} when the call must be dropped.\n */\nexport function buildMessage(type: MessageType, args: unknown, now: () => Date = () => new Date()): WireMessage {\n if (!isPlainObject(args)) throw new InvalidMessageError(`${type}() needs an object argument`);\n const timestamp = args.timestamp === undefined || args.timestamp === null ? now().toISOString() : formatTimestamp(args.timestamp as Timestamp);\n const base = { type, messageId: newMessageId(), timestamp };\n switch (type) {\n case \"identify\": {\n const userId = id(\"userId\", args.userId, true);\n const traits = object(\"traits\", args.traits);\n const accountId = id(\"accountId\", args.accountId, false);\n return { ...base, userId, ...(traits ? { traits } : {}), ...(accountId ? { accountId } : {}) };\n }\n case \"track\": {\n const userId = id(\"userId\", args.userId, false);\n const anonymousId = id(\"anonymousId\", args.anonymousId, false);\n if (!userId && !anonymousId) throw new InvalidMessageError(\"track() needs a userId or an anonymousId\");\n const event = args.event;\n if (typeof event !== \"string\" || event.trim() === \"\") throw new InvalidMessageError(\"track() needs an event name\");\n if (length(event) > LIMITS.maxEventLength) throw new InvalidMessageError(`event is longer than ${LIMITS.maxEventLength} characters`);\n const properties = object(\"properties\", args.properties);\n const accountId = id(\"accountId\", args.accountId, false);\n return {\n ...base,\n ...(userId ? { userId } : {}),\n ...(anonymousId ? { anonymousId } : {}),\n event,\n ...(properties ? { properties } : {}),\n ...(accountId ? { accountId } : {}),\n };\n }\n case \"group\": {\n const accountId = id(\"accountId\", args.accountId, true);\n const traits = object(\"traits\", args.traits);\n const userId = id(\"userId\", args.userId, false);\n return { ...base, accountId, ...(traits ? { traits } : {}), ...(userId ? { userId } : {}) };\n }\n }\n}\n","/** What to do with a batch after an answer (status 0 = no answer: network error or timeout). */\nexport type ResponseAction = \"ok\" | \"retry\" | \"drop\";\n\nexport interface BackoffOptions {\n baseMs: number;\n maxMs: number;\n retryAfterMaxMs: number;\n}\n\nexport const DEFAULT_BACKOFF: BackoffOptions = { baseMs: 1000, maxMs: 30_000, retryAfterMaxMs: 60_000 };\n\n/** 2xx → ok; no answer, 429 and 5xx → retry; anything else (other 4xx, 3xx) → drop. */\nexport function responseAction(status: number): ResponseAction {\n if (status >= 200 && status < 300) return \"ok\";\n if (status === 0 || status === 429 || (status >= 500 && status < 600)) return \"retry\";\n return \"drop\";\n}\n\n/** Retry-After in whole seconds → milliseconds; anything else (HTTP dates, garbage) → null. */\nexport function parseRetryAfter(value: string | null | undefined): number | null {\n if (value == null) return null;\n const v = value.trim();\n return /^\\d+$/.test(v) ? Number(v) * 1000 : null;\n}\n\n/**\n * The wait before retrying after failed attempt `attempt` (1 = the first send), before jitter:\n * `min(baseMs · 2^(attempt−1), maxMs)`, or Retry-After capped at `retryAfterMaxMs`.\n */\nexport function backoffDelay(attempt: number, retryAfter: string | null | undefined, o: BackoffOptions = DEFAULT_BACKOFF): number {\n const ra = parseRetryAfter(retryAfter);\n if (ra !== null) return Math.min(ra, o.retryAfterMaxMs);\n return Math.min(o.baseMs * 2 ** Math.max(0, attempt - 1), o.maxMs);\n}\n\n/** ±20 % jitter on a computed backoff (not on Retry-After). `random` is in [0, 1). */\nexport function jitter(delayMs: number, random: number): number {\n return Math.round(delayMs * (0.8 + 0.4 * random));\n}\n","import { buildMessage, InvalidMessageError, LIMITS, type GroupMessage, type IdentifyMessage, type MessageType, type TrackMessage } from \"./message\";\nimport { backoffDelay, DEFAULT_BACKOFF, jitter, parseRetryAfter, responseAction } from \"./retry\";\n\nexport const SDK_NAME = \"guidely-node\";\nexport const SDK_VERSION = \"0.16.0\";\nexport const DEFAULT_API_URL = \"https://guidelytech.com\";\nexport const BATCH_PATH = \"/api/v1/sdk/server/batch\";\n\nexport type Environment = \"development\" | \"staging\" | \"production\";\nconst ENVIRONMENTS: readonly string[] = [\"development\", \"staging\", \"production\"];\n\n/** The subset of `fetch` the SDK uses, so any implementation (undici, node-fetch, a test fake) fits. */\nexport type FetchLike = (\n url: string,\n init: { method: \"POST\"; headers: Record<string, string>; body: string; signal: AbortSignal },\n) => Promise<{ status: number; headers: { get(name: string): string | null }; text?: () => Promise<string> }>;\n\nexport type GuidelyErrorCode =\n /** A call was invalid (missing userId, empty event…) and dropped. */\n | \"invalid_message\"\n /** A message serialized to more than 32 KB and was dropped. */\n | \"message_too_large\"\n /** The queue was full (`maxQueueSize`); the oldest messages were dropped. */\n | \"queue_full\"\n /** A batch was rejected (4xx), or failed after every retry, or was cut short by a shutdown timeout. */\n | \"delivery_failed\"\n /** A call was made after `shutdown()`. */\n | \"closed\";\n\n/** Reported to `onError`; never thrown by tracking calls. */\nexport class GuidelyError extends Error {\n override readonly name = \"GuidelyError\";\n constructor(\n readonly code: GuidelyErrorCode,\n message: string,\n /** How many messages were dropped. */\n readonly count = 1,\n /** The HTTP status, for `delivery_failed` (0 when no answer was received). */\n readonly status?: number,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n }\n}\n\nexport interface GuidelyOptions {\n /** A secret API key (`gly_sk_…`) with the `events:write` scope. Never ship it to a browser. */\n secretKey: string;\n /** The environment events are written to. */\n environment: Environment;\n /** API origin (default `https://guidelytech.com`). */\n apiUrl?: string;\n /** Send a batch when this many messages are queued (default 100; batches hold at most 100). */\n flushAt?: number;\n /** Send what is queued at least this often (default 5000 ms). */\n flushIntervalMs?: number;\n /** Most messages held in memory; beyond it the oldest are dropped (default 10,000). */\n maxQueueSize?: number;\n /** Retries of a batch after network errors, 429 and 5xx (default 5, so up to 6 attempts). */\n maxRetries?: number;\n /** How long `shutdown()` (and the exit hook) waits for queued messages to go out (default 10,000 ms). */\n shutdownTimeoutMs?: number;\n /** Timeout of one HTTP request (default 10,000 ms). */\n timeoutMs?: number;\n /** Receives every problem (default: `console.warn`). Called synchronously; must not throw. */\n onError?: (error: GuidelyError) => void;\n /** `fetch` implementation (default: the global `fetch` of Node 18+). */\n fetch?: FetchLike;\n /** Flush on the process's `beforeExit` event (default true). Signals and `process.exit()` skip it: call `shutdown()`. */\n handleExit?: boolean;\n}\n\ninterface Queued {\n json: string;\n bytes: number;\n}\n\ninterface Sleep {\n timer: ReturnType<typeof setTimeout> | undefined;\n resolve: () => void;\n}\n\nconst int = (value: number | undefined, fallback: number, min: number) =>\n typeof value === \"number\" && Number.isFinite(value) ? Math.max(min, Math.floor(value)) : fallback;\n\n/**\n * Server-side Guidely client: `identify`, `track` and `group` from your backend.\n *\n * ```ts\n * const guidely = new Guidely({ secretKey: process.env.GUIDELY_SECRET_KEY!, environment: \"production\" });\n * guidely.track({ userId: \"u_42\", event: \"Report Exported\", properties: { format: \"csv\" } });\n * await guidely.shutdown(); // before the process exits\n * ```\n *\n * Calls validate, queue and return: they never throw and never wait for the network. Messages go out\n * in batches (every `flushAt` messages, every `flushIntervalMs`, on `flush()`), one request at a time,\n * retried with backoff when Guidely is unreachable.\n */\nexport class Guidely {\n private readonly url: string;\n private readonly headers: Record<string, string>;\n private readonly flushAt: number;\n private readonly flushIntervalMs: number;\n private readonly maxQueueSize: number;\n private readonly maxRetries: number;\n private readonly shutdownTimeoutMs: number;\n private readonly timeoutMs: number;\n private readonly onError: (error: GuidelyError) => void;\n private readonly fetch: FetchLike;\n private readonly sdkJson = JSON.stringify({ name: SDK_NAME, version: SDK_VERSION });\n\n private queue: Queued[] = [];\n private droppedSinceReport = 0;\n private timer: ReturnType<typeof setTimeout> | undefined;\n private pumping: Promise<void> | null = null;\n private drainAll = false;\n /** Bumped to abandon the running pump (shutdown timeout): its request is aborted and its sleeps end. */\n private epoch = 0;\n private inflight: AbortController | null = null;\n private readonly sleeps = new Set<Sleep>();\n private closed = false;\n private shutdownPromise: Promise<void> | null = null;\n private exitDrain: Promise<void> | null = null;\n private readonly exitListener: (() => void) | null = null;\n\n constructor(options: GuidelyOptions) {\n const o = options ?? ({} as GuidelyOptions);\n const key = typeof o.secretKey === \"string\" ? o.secretKey.trim() : \"\";\n if (!key.startsWith(\"gly_sk_\")) {\n throw new TypeError(\"Guidely: secretKey must be a secret API key (gly_sk_…) with the events:write scope\");\n }\n if (!ENVIRONMENTS.includes(o.environment)) {\n throw new TypeError('Guidely: environment must be \"development\", \"staging\" or \"production\"');\n }\n const fetchImpl = o.fetch ?? (typeof globalThis.fetch === \"function\" ? (globalThis.fetch.bind(globalThis) as unknown as FetchLike) : undefined);\n if (!fetchImpl) throw new TypeError(\"Guidely: no global fetch (Node 18+ is required), pass the fetch option\");\n this.fetch = fetchImpl;\n const apiUrl = (o.apiUrl ?? DEFAULT_API_URL).trim().replace(/\\/+$/, \"\");\n this.url = `${apiUrl}${BATCH_PATH}?environment=${encodeURIComponent(o.environment)}`;\n this.headers = {\n Authorization: `Bearer ${key}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"User-Agent\": `${SDK_NAME}/${SDK_VERSION} (Node ${typeof process !== \"undefined\" ? process.version : \"unknown\"})`,\n };\n this.flushAt = int(o.flushAt, 100, 1);\n this.flushIntervalMs = int(o.flushIntervalMs, 5000, 1);\n this.maxQueueSize = int(o.maxQueueSize, 10_000, 1);\n this.maxRetries = int(o.maxRetries, 5, 0);\n this.shutdownTimeoutMs = int(o.shutdownTimeoutMs, 10_000, 0);\n this.timeoutMs = int(o.timeoutMs, 10_000, 1);\n this.onError = o.onError ?? ((e) => console.warn(`[${SDK_NAME}] ${e.message}`));\n if (o.handleExit !== false && typeof process !== \"undefined\" && typeof process.on === \"function\") {\n this.exitListener = () => this.onBeforeExit();\n process.on(\"beforeExit\", this.exitListener);\n }\n }\n\n /** Who a user is: merges `traits` into the user (creating it if needed), optionally linking an account. */\n identify(message: IdentifyMessage): void {\n this.enqueue(\"identify\", message);\n }\n\n /** What a user (or anonymous visitor) did. */\n track(message: TrackMessage): void {\n this.enqueue(\"track\", message);\n }\n\n /** Account traits, and optionally adds `userId` to the account. */\n group(message: GroupMessage): void {\n this.enqueue(\"group\", message);\n }\n\n /** Sends everything queued; resolves once it has been delivered or dropped. Never rejects. */\n flush(): Promise<void> {\n return this.startPump(true);\n }\n\n /**\n * Stops the timer and the exit hook, then flushes, waiting at most `shutdownTimeoutMs`. Later calls\n * are dropped (and reported). Call it before your process exits. Never rejects.\n */\n shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.closed = true;\n this.clearTimer();\n if (this.exitListener) process.removeListener(\"beforeExit\", this.exitListener);\n this.shutdownPromise = this.drain(this.shutdownTimeoutMs);\n return this.shutdownPromise;\n }\n\n // --- queueing ------------------------------------------------------------------------------\n\n private enqueue(type: MessageType, args: unknown): void {\n try {\n if (this.closed) {\n this.report(new GuidelyError(\"closed\", `${type}() was called after shutdown(); the message was dropped`));\n return;\n }\n const message = buildMessage(type, args);\n let json: string;\n try {\n json = JSON.stringify(message);\n } catch (e) {\n throw new InvalidMessageError(`could not be serialized to JSON (${e instanceof Error ? e.message : String(e)})`);\n }\n const bytes = Buffer.byteLength(json, \"utf8\");\n if (bytes > LIMITS.maxMessageBytes) {\n this.report(new GuidelyError(\"message_too_large\", `${type}() message is ${bytes} bytes, more than the ${LIMITS.maxMessageBytes}-byte limit; it was dropped`));\n return;\n }\n if (this.queue.length >= this.maxQueueSize) {\n this.queue.shift();\n this.droppedSinceReport++;\n }\n this.queue.push({ json, bytes });\n if (this.queue.length >= this.flushAt) void this.startPump(false);\n else this.scheduleTimer();\n } catch (e) {\n if (e instanceof InvalidMessageError) this.report(new GuidelyError(\"invalid_message\", `Invalid ${type}() call dropped: ${e.message}`));\n else this.report(new GuidelyError(\"invalid_message\", `${type}() failed: ${e instanceof Error ? e.message : String(e)}`, 1, undefined, { cause: e }));\n }\n }\n\n private scheduleTimer(): void {\n if (this.timer !== undefined || this.closed) return;\n this.timer = setTimeout(() => {\n this.timer = undefined;\n void this.startPump(true);\n }, this.flushIntervalMs);\n (this.timer as { unref?: () => void }).unref?.();\n }\n\n private clearTimer(): void {\n if (this.timer !== undefined) clearTimeout(this.timer);\n this.timer = undefined;\n }\n\n // --- sending -------------------------------------------------------------------------------\n\n /** Starts (or joins) the single sender. `all`: send everything, not only full batches. */\n private startPump(all: boolean): Promise<void> {\n if (all) this.drainAll = true;\n if (!this.pumping) this.pumping = this.pump();\n return this.pumping;\n }\n\n private async pump(): Promise<void> {\n await Promise.resolve(); // `this.pumping` is assigned before the loop can finish\n const epoch = this.epoch;\n try {\n const cap = Math.min(this.flushAt, LIMITS.maxBatchMessages);\n for (;;) {\n this.reportDrops();\n if (epoch !== this.epoch || this.queue.length === 0 || (!this.drainAll && this.queue.length < cap)) break;\n await this.deliver(this.takeBatch(cap), epoch);\n }\n } catch (e) {\n this.report(new GuidelyError(\"delivery_failed\", `Unexpected error while sending: ${e instanceof Error ? e.message : String(e)}`, 0, undefined, { cause: e }));\n } finally {\n this.pumping = null;\n this.drainAll = false;\n if (this.queue.length > 0) this.scheduleTimer();\n }\n }\n\n /** Up to `cap` messages from the head of the queue, at most `maxBatchBytes` of them (at least one). */\n private takeBatch(cap: number): Queued[] {\n let n = 0;\n let size = 0;\n while (n < this.queue.length && n < cap) {\n const next = this.queue[n]!;\n if (n > 0 && size + next.bytes > LIMITS.maxBatchBytes) break;\n size += next.bytes;\n n++;\n }\n return this.queue.splice(0, n);\n }\n\n private async deliver(batch: Queued[], epoch: number): Promise<void> {\n const messages = batch.map((m) => m.json).join(\",\");\n for (let attempt = 1; ; attempt++) {\n if (epoch !== this.epoch) {\n this.report(new GuidelyError(\"delivery_failed\", `${batch.length} messages were not sent before the shutdown timeout`, batch.length, 0));\n return;\n }\n const body = `{\"batch\":[${messages}],\"sentAt\":${JSON.stringify(new Date().toISOString())},\"sdk\":${this.sdkJson}}`;\n let status = 0;\n let retryAfter: string | null = null;\n let failure: unknown;\n try {\n const res = await this.send(body);\n status = res.status;\n retryAfter = res.headers.get(\"retry-after\");\n } catch (e) {\n failure = e;\n }\n const action = responseAction(status);\n if (action === \"ok\") return;\n if (epoch !== this.epoch) continue; // aborted while sending: reported at the top\n if (action === \"drop\" || attempt > this.maxRetries) {\n this.report(new GuidelyError(\"delivery_failed\", failureMessage(status, failure, batch.length, attempt), batch.length, status, { cause: failure }));\n return;\n }\n const delay = parseRetryAfter(retryAfter) !== null ? backoffDelay(attempt, retryAfter) : jitter(backoffDelay(attempt, null, DEFAULT_BACKOFF), Math.random());\n await this.sleep(delay);\n }\n }\n\n private async send(body: string) {\n const controller = new AbortController();\n this.inflight = controller;\n const timeout = setTimeout(() => controller.abort(new Error(`request timed out after ${this.timeoutMs} ms`)), this.timeoutMs);\n try {\n const res = await this.fetch(this.url, { method: \"POST\", headers: this.headers, body, signal: controller.signal });\n try {\n await res.text?.(); // release the connection\n } catch {\n // the status is what matters\n }\n return res;\n } finally {\n clearTimeout(timeout);\n if (this.inflight === controller) this.inflight = null;\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise<void>((resolve) => {\n const s: Sleep = { timer: undefined, resolve };\n s.timer = setTimeout(() => {\n this.sleeps.delete(s);\n resolve();\n }, ms);\n this.sleeps.add(s);\n });\n }\n\n /** Flushes, giving up after `timeoutMs`: the request in flight is aborted and what is left is dropped. */\n private async drain(timeoutMs: number): Promise<void> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = await Promise.race([\n this.startPump(true).then(() => false),\n new Promise<boolean>((resolve) => {\n timer = setTimeout(() => resolve(true), timeoutMs);\n }),\n ]);\n clearTimeout(timer);\n if (!timedOut) return;\n this.epoch++;\n this.inflight?.abort(new Error(\"shutdown timeout\"));\n for (const s of this.sleeps) {\n clearTimeout(s.timer);\n s.resolve();\n }\n this.sleeps.clear();\n const left = this.queue.length;\n this.queue = [];\n if (left > 0) this.report(new GuidelyError(\"delivery_failed\", `${left} queued messages were not sent within ${timeoutMs} ms`, left, 0));\n }\n\n private onBeforeExit(): void {\n if (this.closed || this.exitDrain || (this.queue.length === 0 && !this.pumping)) return;\n this.exitDrain = this.drain(this.shutdownTimeoutMs).finally(() => {\n this.exitDrain = null;\n });\n }\n\n // --- errors --------------------------------------------------------------------------------\n\n private reportDrops(): void {\n if (this.droppedSinceReport === 0) return;\n const n = this.droppedSinceReport;\n this.droppedSinceReport = 0;\n this.report(new GuidelyError(\"queue_full\", `The queue was full (maxQueueSize ${this.maxQueueSize}): dropped the ${n} oldest messages`, n));\n }\n\n private report(error: GuidelyError): void {\n try {\n this.onError(error);\n } catch {\n // an error handler must never break the caller\n }\n }\n}\n\nfunction failureMessage(status: number, failure: unknown, count: number, attempts: number): string {\n const what = `${count} messages were dropped`;\n if (status === 0) return `${what}: Guidely could not be reached in ${attempts} attempt${attempts === 1 ? \"\" : \"s\"} (${failure instanceof Error ? failure.message : String(failure)})`;\n if (status === 401 || status === 403) return `${what}: the key was rejected (HTTP ${status}). Check it is a gly_sk_ key with the events:write scope for this environment`;\n if (attempts > 1) return `${what}: HTTP ${status} after ${attempts} attempts`;\n return `${what}: HTTP ${status}`;\n}\n"],"mappings":"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","names":["id"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guidelytech/node",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Guidely server-side SDK for Node.js: identify users, track events and update accounts from your backend",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "tsup": "^8.5.1",
30
30
  "typescript": "^5.9.3",
31
31
  "typescript-eslint": "^8.46.0",
32
- "vitest": "^3.2.7"
32
+ "vitest": "^4.1.12"
33
33
  },
34
34
  "publishConfig": {
35
35
  "access": "public"