@holdyapp/sdk 1.1.0 → 1.6.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/README.md +53 -1
- package/dist/arm.d.ts +5 -0
- package/dist/arm.d.ts.map +1 -0
- package/dist/changes.d.ts +42 -0
- package/dist/changes.d.ts.map +1 -0
- package/dist/core.d.ts +17 -6
- package/dist/core.d.ts.map +1 -1
- package/dist/friction.d.ts +91 -0
- package/dist/friction.d.ts.map +1 -0
- package/dist/holdy.js +6 -1
- package/dist/holdy.js.map +4 -4
- package/dist/index.cjs +871 -26
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +871 -26
- package/dist/index.mjs.map +4 -4
- package/dist/run.d.ts +37 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/server.cjs +127 -23
- package/dist/server.cjs.map +3 -3
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.mjs +124 -22
- package/dist/server.mjs.map +3 -3
- package/dist/skeleton.d.ts +50 -0
- package/dist/skeleton.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web.d.ts +26 -0
- package/dist/web.d.ts.map +1 -1
- package/dist/web.mjs +824 -26
- package/dist/web.mjs.map +4 -4
- package/package.json +1 -1
- package/src/arm.test.ts +47 -0
- package/src/arm.ts +25 -0
- package/src/changes.test.ts +54 -0
- package/src/changes.ts +357 -0
- package/src/core.test.ts +78 -4
- package/src/core.ts +95 -31
- package/src/friction.test.ts +223 -0
- package/src/friction.ts +419 -0
- package/src/index.ts +1 -0
- package/src/run.test.ts +47 -0
- package/src/run.ts +80 -0
- package/src/server.ts +17 -0
- package/src/skeleton.test.ts +60 -0
- package/src/skeleton.ts +173 -0
- package/src/types.ts +2 -0
- package/src/web.ts +71 -1
package/dist/server.cjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/server.ts", "../src/core.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * @holdyapp/sdk (server) \u00B7 Node/Edge (source: sdk_server).\n *\n * Stateless por default: user_id/accountKey v\u00E3o por chamada em overrides.\n * identify()/group() de sess\u00E3o existem mas em server multi-tenant prefira\n * overrides \u2014 estado compartilhado entre requests vaza identidade.\n */\n\nimport { HoldyCore } from './core'\nimport type { HoldyOptions } from './types'\n\nexport class Holdy extends HoldyCore {\n constructor(opts: HoldyOptions) {\n super('sdk_server', opts)\n }\n}\n\nexport { HoldyCore, DEFAULT_BASE_URL } from './core'\nexport type { HoldyOptions, Properties, TrackOverrides, CanonicalEvent } from './types'\n", "/**\n * Core do @holdyapp/sdk \u00B7 fila + flush pra POST /api/v1/events/bulk.\n *\n * Retry de batch inteiro \u00E9 seguro (partial accept + dedupe server-side).\n * Fila \u00E9 bounded: acima de MAX_QUEUE dropa os mais antigos (telemetria nunca\n * derruba o app do cliente \u2014 nenhum m\u00E9todo p\u00FAblico lan\u00E7a).\n */\n\nimport type { CanonicalEvent, HoldyOptions, Properties, TrackOverrides } from './types'\n\nexport const DEFAULT_BASE_URL = 'https://holdy.chat/api/v1'\nconst DEFAULT_FLUSH_AT = 20\nconst DEFAULT_FLUSH_INTERVAL_MS = 5000\nconst MAX_QUEUE = 1000\nconst BULK_LIMIT = 500\n\nexport class HoldyCore {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly flushAt: number\n private readonly debug: boolean\n private readonly source: 'sdk_web' | 'sdk_server'\n\n private queue: CanonicalEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private flushing: Promise<void> | null = null\n\n protected userId: string | null = null\n protected anonymousId: string | null = null\n protected accountKey: string | null = null\n\n constructor(source: 'sdk_web' | 'sdk_server', opts: HoldyOptions) {\n this.source = source\n this.apiKey = opts.apiKey\n this.accountKey = opts.accountKey ?? null\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.flushAt = Math.max(1, opts.flushAt ?? DEFAULT_FLUSH_AT)\n this.debug = opts.debug ?? false\n\n const intervalMs = opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.flush(), intervalMs)\n // Node: n\u00E3o segura o processo vivo s\u00F3 pelo timer do SDK.\n if (typeof (this.timer as { unref?: () => void }).unref === 'function') {\n ;(this.timer as unknown as { unref: () => void }).unref()\n }\n }\n }\n\n track(name: string, properties?: Properties, overrides?: TrackOverrides): void {\n if (!name || typeof name !== 'string') return\n\n const userId = overrides?.userId ?? this.userId ?? undefined\n const accountKey = overrides?.accountKey ?? this.accountKey ?? undefined\n const occurredAt = overrides?.occurredAt\n ? new Date(overrides.occurredAt).toISOString()\n : new Date().toISOString()\n\n // Ordem: ambiente observado pelo SDK \u2192 context da chamada \u2192 carrier de\n // account. O que o caller passou vence o inferido; o carrier vence tudo,\n // porque \u00E9 o que a Data layer usa pra resolver a conta.\n const context: Properties = {\n ...this.environmentContext(),\n ...(overrides?.context ?? {}),\n }\n if (accountKey) context.account_external_id = accountKey\n\n const event: CanonicalEvent = {\n event: name,\n occurred_at: occurredAt,\n source: this.source,\n ...(userId ? { user_id: userId } : {}),\n ...(this.anonymousId ? { anonymous_id: this.anonymousId } : {}),\n properties: properties ?? {},\n ...(Object.keys(context).length > 0 ? { context } : {}),\n }\n\n this.queue.push(event)\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE)\n if (this.queue.length >= this.flushAt) void this.flush()\n }\n\n identify(userId: string, traits?: Properties): void {\n if (!userId || typeof userId !== 'string') return\n this.userId = userId\n this.track('$identify', traits ?? {})\n }\n\n group(accountKey: string, traits?: Properties): void {\n if (!accountKey || typeof accountKey !== 'string') return\n this.accountKey = accountKey\n this.track('$group', traits ?? {})\n }\n\n /** Drena a fila. Nunca lan\u00E7a \u2014 falha re-enfileira e loga em debug. */\n async flush(): Promise<void> {\n // Serializa flushes concorrentes (timer + flushAt + manual).\n if (this.flushing) return this.flushing\n if (this.queue.length === 0) return\n\n this.flushing = this.drain().finally(() => {\n this.flushing = null\n })\n return this.flushing\n }\n\n /** Flush final + para o timer. Chamar em shutdown do server / unload do web. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n await this.flush()\n }\n\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, BULK_LIMIT)\n const ok = await this.send(batch)\n if (!ok) {\n // Devolve pra frente da fila e para \u2014 pr\u00F3ximo flush re-tenta (dedupe-safe).\n this.queue.unshift(...batch)\n return\n }\n }\n }\n\n private async send(events: CanonicalEvent[]): Promise<boolean> {\n try {\n const res = await fetch(`${this.baseUrl}/events/bulk`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events }),\n ...(this.source === 'sdk_web' ? { keepalive: true } : {}),\n })\n if (!res.ok) {\n // 4xx: batch estruturalmente inv\u00E1lido n\u00E3o re-tenta (evita loop infinito).\n if (res.status >= 400 && res.status < 500) {\n this.log(`bulk rejected ${res.status} \u00B7 batch dropped`)\n return true\n }\n this.log(`bulk failed ${res.status} \u00B7 will retry`)\n return false\n }\n return true\n } catch (err) {\n this.log(`bulk network error: ${(err as Error).message} \u00B7 will retry`)\n return false\n }\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[holdy] ${msg}`)\n }\n\n /**\n * Ambiente que o SDK observa sozinho (url, referrer, browser, utm\u2026).\n * Vazio no core; `HoldyWeb` preenche. O server n\u00E3o infere ambiente \u2014 n\u00E3o\n * existe \"p\u00E1gina atual\" num worker.\n */\n protected environmentContext(): Properties {\n return {}\n }\n\n /** Interno \u00B7 exposto pra testes. */\n get pending(): number {\n return this.queue.length\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;
|
|
3
|
+
"sources": ["../src/server.ts", "../src/core.ts", "../src/run.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * @holdyapp/sdk (server) \u00B7 Node/Edge (source: sdk_server).\n *\n * Stateless por default: user_id/accountKey v\u00E3o por chamada em overrides.\n * identify()/group() de sess\u00E3o existem mas em server multi-tenant prefira\n * overrides \u2014 estado compartilhado entre requests vaza identidade.\n */\n\nimport { HoldyCore } from './core'\nimport { withRun, type RunHandle, type RunOptions } from './run'\nimport type { HoldyOptions } from './types'\n\nexport class Holdy extends HoldyCore {\n constructor(opts: HoldyOptions) {\n super('sdk_server', opts)\n }\n\n /**\n * Executa `fn` como UMA execu\u00E7\u00E3o de agente: todo evento l\u00E1 dentro carrega a\n * mesma `ai_run_id`, e `<name>.started` / `.completed` / `.failed` saem\n * sozinhos.\n *\n * await holdy.run({ name: 'support_agent', accountKey: 'acme' }, async (run) => {\n * run.track('ticket_drafted', { ai_tokens: 1200, ai_cost_usd: 0.02 })\n * return resolveTicket(ticket)\n * })\n */\n run<T>(opts: RunOptions, fn: (run: RunHandle) => Promise<T> | T): Promise<T> {\n return withRun((name, properties, overrides) => this.track(name, properties, overrides), opts, fn)\n }\n}\n\nexport { HoldyCore, DEFAULT_BASE_URL } from './core'\nexport type { HoldyOptions, Properties, TrackOverrides, CanonicalEvent } from './types'\nexport { newRunId, runEventName } from './run'\nexport type { RunHandle, RunOptions } from './run'\n", "/**\n * Core do @holdyapp/sdk \u00B7 fila + flush pra POST /api/v1/events/bulk.\n *\n * Todo evento sai com `external_id` pr\u00F3prio, ent\u00E3o reenviar um lote nunca\n * duplica (dedupe server-side por source + external_id + occurred_at).\n * 429 respeita Retry-After; 5xx e rede esperam com backoff exponencial; o lote\n * volta pra frente da fila em vez de sumir. Chave inv\u00E1lida ou origem recusada\n * avisam uma vez no console, mesmo sem debug. Fila \u00E9 bounded: acima de\n * MAX_QUEUE dropa os mais antigos (telemetria nunca derruba o app do cliente \u2014\n * nenhum m\u00E9todo p\u00FAblico lan\u00E7a).\n */\n\nimport type { CanonicalEvent, HoldyOptions, Properties, TrackOverrides } from './types'\n\nexport const DEFAULT_BASE_URL = 'https://holdy.chat/api/v1'\nconst DEFAULT_FLUSH_AT = 20\nconst DEFAULT_FLUSH_INTERVAL_MS = 5000\nconst MAX_QUEUE = 1000\nconst BULK_LIMIT = 500\nconst MAX_BACKOFF_MS = 60_000\n\ntype SendOutcome = { kind: 'sent' } | { kind: 'dropped' } | { kind: 'retry'; afterMs: number | null }\n\nexport class HoldyCore {\n protected readonly apiKey: string\n protected readonly baseUrl: string\n private readonly flushAt: number\n private readonly debug: boolean\n private readonly source: 'sdk_web' | 'sdk_server'\n\n private queue: CanonicalEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private flushing: Promise<void> | null = null\n private retryAt = 0\n private failures = 0\n private readonly warned = new Set<string>()\n\n protected userId: string | null = null\n protected anonymousId: string | null = null\n protected accountKey: string | null = null\n\n constructor(source: 'sdk_web' | 'sdk_server', opts: HoldyOptions) {\n this.source = source\n this.apiKey = opts.apiKey\n this.accountKey = opts.accountKey ?? null\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.flushAt = Math.max(1, opts.flushAt ?? DEFAULT_FLUSH_AT)\n this.debug = opts.debug ?? false\n\n const intervalMs = opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.flush(), intervalMs)\n // Node: n\u00E3o segura o processo vivo s\u00F3 pelo timer do SDK.\n if (typeof (this.timer as { unref?: () => void }).unref === 'function') {\n ;(this.timer as unknown as { unref: () => void }).unref()\n }\n }\n }\n\n track(name: string, properties?: Properties, overrides?: TrackOverrides): void {\n if (!name || typeof name !== 'string') return\n\n const userId = overrides?.userId ?? this.userId ?? undefined\n const accountKey = overrides?.accountKey ?? this.accountKey ?? undefined\n const occurredAt = overrides?.occurredAt\n ? new Date(overrides.occurredAt).toISOString()\n : new Date().toISOString()\n\n // Ordem: ambiente observado pelo SDK \u2192 context da chamada \u2192 carrier de\n // account. O que o caller passou vence o inferido; o carrier vence tudo,\n // porque \u00E9 o que a Data layer usa pra resolver a conta.\n const context: Properties = {\n ...this.environmentContext(),\n ...(overrides?.context ?? {}),\n }\n if (accountKey) context.account_external_id = accountKey\n\n const event: CanonicalEvent = {\n event: name,\n occurred_at: occurredAt,\n source: this.source,\n external_id: newEventId(),\n ...(userId ? { user_id: userId } : {}),\n ...(this.anonymousId ? { anonymous_id: this.anonymousId } : {}),\n properties: properties ?? {},\n ...(Object.keys(context).length > 0 ? { context } : {}),\n }\n\n this.queue.push(event)\n this.trimQueue()\n if (this.queue.length >= this.flushAt) void this.flush()\n }\n\n identify(userId: string, traits?: Properties): void {\n if (!userId || typeof userId !== 'string') return\n this.userId = userId\n this.track('$identify', traits ?? {})\n }\n\n group(accountKey: string, traits?: Properties): void {\n if (!accountKey || typeof accountKey !== 'string') return\n this.accountKey = accountKey\n this.track('$group', traits ?? {})\n }\n\n /** Drena a fila. Nunca lan\u00E7a \u2014 falha re-enfileira e espera antes de tentar de novo. */\n async flush(): Promise<void> {\n return this.startFlush(false)\n }\n\n /** Flush final + para o timer. Chamar em shutdown do server / unload do web. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n await this.startFlush(true)\n }\n\n private startFlush(force: boolean): Promise<void> {\n // Serializa flushes concorrentes (timer + flushAt + manual).\n if (this.flushing) return this.flushing\n if (this.queue.length === 0) return Promise.resolve()\n if (!force && Date.now() < this.retryAt) return Promise.resolve()\n\n this.flushing = this.drain().finally(() => {\n this.flushing = null\n })\n return this.flushing\n }\n\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, BULK_LIMIT)\n const outcome = await this.send(batch)\n if (outcome.kind === 'retry') {\n this.queue.unshift(...batch)\n this.trimQueue()\n this.scheduleRetry(outcome.afterMs)\n return\n }\n this.failures = 0\n this.retryAt = 0\n }\n }\n\n private scheduleRetry(afterMs: number | null): void {\n this.failures += 1\n if (afterMs !== null) {\n this.retryAt = Date.now() + afterMs\n return\n }\n const backoff = Math.min(MAX_BACKOFF_MS, 1000 * 2 ** (this.failures - 1))\n this.retryAt = Date.now() + backoff + Math.floor(Math.random() * backoff * 0.2)\n }\n\n private trimQueue(): void {\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE)\n }\n\n private async send(events: CanonicalEvent[]): Promise<SendOutcome> {\n try {\n const res = await fetch(`${this.baseUrl}/events/bulk`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events }),\n ...(this.source === 'sdk_web' ? { keepalive: true } : {}),\n })\n if (res.ok) return { kind: 'sent' }\n\n if (res.status === 429) {\n this.log('rate limited \u00B7 will retry after Retry-After')\n return { kind: 'retry', afterMs: retryAfterMs(res) }\n }\n if (res.status >= 500) {\n this.log(`bulk failed ${res.status} \u00B7 will retry with backoff`)\n return { kind: 'retry', afterMs: null }\n }\n if (res.status === 401) {\n this.warnOnce('401', 'API key inv\u00E1lida ou revogada \u00B7 eventos descartados. Confira a chave em Configura\u00E7\u00F5es \u203A SDK.')\n return { kind: 'dropped' }\n }\n if (res.status === 403) {\n this.warnOnce('403', 'Origem n\u00E3o permitida ou chave sem escopo events:write \u00B7 eventos descartados.')\n return { kind: 'dropped' }\n }\n this.log(`bulk rejected ${res.status} \u00B7 batch dropped`)\n return { kind: 'dropped' }\n } catch (err) {\n this.log(`bulk network error: ${(err as Error).message} \u00B7 will retry with backoff`)\n return { kind: 'retry', afterMs: null }\n }\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[holdy] ${msg}`)\n }\n\n private warnOnce(key: string, msg: string): void {\n if (this.warned.has(key)) return\n this.warned.add(key)\n console.warn(`[holdy] ${msg}`)\n }\n\n /**\n * Ambiente que o SDK observa sozinho (url, referrer, browser, utm\u2026).\n * Vazio no core; `HoldyWeb` preenche. O server n\u00E3o infere ambiente \u2014 n\u00E3o\n * existe \"p\u00E1gina atual\" num worker.\n */\n protected environmentContext(): Properties {\n return {}\n }\n\n /** Interno \u00B7 exposto pra testes. */\n get pending(): number {\n return this.queue.length\n }\n}\n\nfunction retryAfterMs(res: Response): number | null {\n const raw = typeof res.headers?.get === 'function' ? res.headers.get('Retry-After') : null\n if (!raw) return null\n const seconds = Number(raw)\n if (!Number.isFinite(seconds) || seconds < 0) return null\n return Math.min(MAX_BACKOFF_MS, seconds * 1000)\n}\n\nfunction newEventId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (typeof c?.randomUUID === 'function') return c.randomUUID()\n const rand = () => Math.random().toString(36).slice(2, 12)\n return `${Date.now().toString(36)}-${rand()}-${rand()}`\n}\n", "/**\n * Execu\u00E7\u00E3o como unidade de trabalho (source: sdk_server).\n *\n * Produto de agente n\u00E3o tem sess\u00E3o de browser: o trabalho roda no servidor,\n * por API, em background. Pra Holdy dizer \"come\u00E7ou e concluiu na mesma\n * execu\u00E7\u00E3o\" e dividir recurso por resultado entregue, todos os eventos de uma\n * tarefa carregam a mesma `ai_run_id` \u2014 a chave que o composer usa por padr\u00E3o\n * no gr\u00E3o de execu\u00E7\u00E3o (lib/knowledge/compose-definition.ts).\n *\n * N\u00E3o inventa vocabul\u00E1rio: in\u00EDcio e fim saem com o nome que o cliente deu \u00E0\n * execu\u00E7\u00E3o (`support_agent.started`), e todo passo no meio \u00E9 nome dele. O\n * retorno passa direto e o erro propaga; s\u00F3 o NOME do erro vira propriedade,\n * porque a mensagem carrega dado do cliente com frequ\u00EAncia demais.\n */\n\nimport type { Properties, TrackOverrides } from './types'\n\nexport interface RunOptions {\n /** Nome da execu\u00E7\u00E3o no vocabul\u00E1rio do produto, ex.: 'support_agent'. */\n name: string\n /** Conta (id externo) dona da execu\u00E7\u00E3o. */\n accountKey?: string\n userId?: string\n /** Chave pr\u00F3pria (trace id do agente); ausente = gerada aqui. */\n runId?: string\n /** Propriedades repetidas em todos os eventos da execu\u00E7\u00E3o. */\n properties?: Properties\n}\n\nexport interface RunHandle {\n /** A chave que agrupa tudo desta execu\u00E7\u00E3o. */\n id: string\n /** Um passo dentro da execu\u00E7\u00E3o, j\u00E1 carimbado com ai_run_id. */\n track(event: string, properties?: Properties): void\n}\n\nexport type TrackFn = (name: string, properties?: Properties, overrides?: TrackOverrides) => void\n\nexport function newRunId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (typeof c?.randomUUID === 'function') return c.randomUUID()\n return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`\n}\n\nexport function runEventName(name: string, suffix: 'started' | 'completed' | 'failed'): string {\n const base = name.trim().replace(/\\.$/, '')\n return `${base || 'run'}.${suffix}`\n}\n\nexport async function withRun<T>(\n track: TrackFn,\n opts: RunOptions,\n fn: (run: RunHandle) => Promise<T> | T,\n): Promise<T> {\n if (!opts || !opts.name) throw new Error('[holdy] run: name is required')\n\n const id = opts.runId && opts.runId.length > 0 ? opts.runId : newRunId()\n const base: Properties = { ...(opts.properties ?? {}), ai_run_id: id }\n const overrides: TrackOverrides = {\n ...(opts.accountKey ? { accountKey: opts.accountKey } : {}),\n ...(opts.userId ? { userId: opts.userId } : {}),\n }\n const emit = (event: string, properties?: Properties) =>\n track(event, { ...base, ...(properties ?? {}), ai_run_id: id }, overrides)\n\n const startedAt = Date.now()\n emit(runEventName(opts.name, 'started'))\n\n try {\n const result = await fn({ id, track: emit })\n emit(runEventName(opts.name, 'completed'), { duration_ms: Date.now() - startedAt })\n return result\n } catch (err) {\n emit(runEventName(opts.name, 'failed'), {\n duration_ms: Date.now() - startedAt,\n error: err instanceof Error ? err.name : 'Error',\n })\n throw err\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -6,10 +6,24 @@
|
|
|
6
6
|
* overrides — estado compartilhado entre requests vaza identidade.
|
|
7
7
|
*/
|
|
8
8
|
import { HoldyCore } from './core';
|
|
9
|
+
import { type RunHandle, type RunOptions } from './run';
|
|
9
10
|
import type { HoldyOptions } from './types';
|
|
10
11
|
export declare class Holdy extends HoldyCore {
|
|
11
12
|
constructor(opts: HoldyOptions);
|
|
13
|
+
/**
|
|
14
|
+
* Executa `fn` como UMA execução de agente: todo evento lá dentro carrega a
|
|
15
|
+
* mesma `ai_run_id`, e `<name>.started` / `.completed` / `.failed` saem
|
|
16
|
+
* sozinhos.
|
|
17
|
+
*
|
|
18
|
+
* await holdy.run({ name: 'support_agent', accountKey: 'acme' }, async (run) => {
|
|
19
|
+
* run.track('ticket_drafted', { ai_tokens: 1200, ai_cost_usd: 0.02 })
|
|
20
|
+
* return resolveTicket(ticket)
|
|
21
|
+
* })
|
|
22
|
+
*/
|
|
23
|
+
run<T>(opts: RunOptions, fn: (run: RunHandle) => Promise<T> | T): Promise<T>;
|
|
12
24
|
}
|
|
13
25
|
export { HoldyCore, DEFAULT_BASE_URL } from './core';
|
|
14
26
|
export type { HoldyOptions, Properties, TrackOverrides, CanonicalEvent } from './types';
|
|
27
|
+
export { newRunId, runEventName } from './run';
|
|
28
|
+
export type { RunHandle, RunOptions } from './run';
|
|
15
29
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,qBAAa,KAAM,SAAQ,SAAS;gBACtB,IAAI,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,EAAW,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,OAAO,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,qBAAa,KAAM,SAAQ,SAAS;gBACtB,IAAI,EAAE,YAAY;IAI9B;;;;;;;;;OASG;IACH,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAG7E;AAED,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AACpD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACvF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,OAAO,CAAA;AAC9C,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA"}
|
package/dist/server.mjs
CHANGED
|
@@ -4,6 +4,7 @@ var DEFAULT_FLUSH_AT = 20;
|
|
|
4
4
|
var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
|
|
5
5
|
var MAX_QUEUE = 1e3;
|
|
6
6
|
var BULK_LIMIT = 500;
|
|
7
|
+
var MAX_BACKOFF_MS = 6e4;
|
|
7
8
|
var HoldyCore = class {
|
|
8
9
|
apiKey;
|
|
9
10
|
baseUrl;
|
|
@@ -13,6 +14,9 @@ var HoldyCore = class {
|
|
|
13
14
|
queue = [];
|
|
14
15
|
timer = null;
|
|
15
16
|
flushing = null;
|
|
17
|
+
retryAt = 0;
|
|
18
|
+
failures = 0;
|
|
19
|
+
warned = /* @__PURE__ */ new Set();
|
|
16
20
|
userId = null;
|
|
17
21
|
anonymousId = null;
|
|
18
22
|
accountKey = null;
|
|
@@ -46,13 +50,14 @@ var HoldyCore = class {
|
|
|
46
50
|
event: name,
|
|
47
51
|
occurred_at: occurredAt,
|
|
48
52
|
source: this.source,
|
|
53
|
+
external_id: newEventId(),
|
|
49
54
|
...userId ? { user_id: userId } : {},
|
|
50
55
|
...this.anonymousId ? { anonymous_id: this.anonymousId } : {},
|
|
51
56
|
properties: properties ?? {},
|
|
52
57
|
...Object.keys(context).length > 0 ? { context } : {}
|
|
53
58
|
};
|
|
54
59
|
this.queue.push(event);
|
|
55
|
-
|
|
60
|
+
this.trimQueue();
|
|
56
61
|
if (this.queue.length >= this.flushAt) void this.flush();
|
|
57
62
|
}
|
|
58
63
|
identify(userId, traits) {
|
|
@@ -65,14 +70,9 @@ var HoldyCore = class {
|
|
|
65
70
|
this.accountKey = accountKey;
|
|
66
71
|
this.track("$group", traits ?? {});
|
|
67
72
|
}
|
|
68
|
-
/** Drena a fila. Nunca lança — falha re-enfileira e
|
|
73
|
+
/** Drena a fila. Nunca lança — falha re-enfileira e espera antes de tentar de novo. */
|
|
69
74
|
async flush() {
|
|
70
|
-
|
|
71
|
-
if (this.queue.length === 0) return;
|
|
72
|
-
this.flushing = this.drain().finally(() => {
|
|
73
|
-
this.flushing = null;
|
|
74
|
-
});
|
|
75
|
-
return this.flushing;
|
|
75
|
+
return this.startFlush(false);
|
|
76
76
|
}
|
|
77
77
|
/** Flush final + para o timer. Chamar em shutdown do server / unload do web. */
|
|
78
78
|
async shutdown() {
|
|
@@ -80,17 +80,42 @@ var HoldyCore = class {
|
|
|
80
80
|
clearInterval(this.timer);
|
|
81
81
|
this.timer = null;
|
|
82
82
|
}
|
|
83
|
-
await this.
|
|
83
|
+
await this.startFlush(true);
|
|
84
|
+
}
|
|
85
|
+
startFlush(force) {
|
|
86
|
+
if (this.flushing) return this.flushing;
|
|
87
|
+
if (this.queue.length === 0) return Promise.resolve();
|
|
88
|
+
if (!force && Date.now() < this.retryAt) return Promise.resolve();
|
|
89
|
+
this.flushing = this.drain().finally(() => {
|
|
90
|
+
this.flushing = null;
|
|
91
|
+
});
|
|
92
|
+
return this.flushing;
|
|
84
93
|
}
|
|
85
94
|
async drain() {
|
|
86
95
|
while (this.queue.length > 0) {
|
|
87
96
|
const batch = this.queue.splice(0, BULK_LIMIT);
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
97
|
+
const outcome = await this.send(batch);
|
|
98
|
+
if (outcome.kind === "retry") {
|
|
90
99
|
this.queue.unshift(...batch);
|
|
100
|
+
this.trimQueue();
|
|
101
|
+
this.scheduleRetry(outcome.afterMs);
|
|
91
102
|
return;
|
|
92
103
|
}
|
|
104
|
+
this.failures = 0;
|
|
105
|
+
this.retryAt = 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
scheduleRetry(afterMs) {
|
|
109
|
+
this.failures += 1;
|
|
110
|
+
if (afterMs !== null) {
|
|
111
|
+
this.retryAt = Date.now() + afterMs;
|
|
112
|
+
return;
|
|
93
113
|
}
|
|
114
|
+
const backoff = Math.min(MAX_BACKOFF_MS, 1e3 * 2 ** (this.failures - 1));
|
|
115
|
+
this.retryAt = Date.now() + backoff + Math.floor(Math.random() * backoff * 0.2);
|
|
116
|
+
}
|
|
117
|
+
trimQueue() {
|
|
118
|
+
if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE);
|
|
94
119
|
}
|
|
95
120
|
async send(events) {
|
|
96
121
|
try {
|
|
@@ -103,23 +128,38 @@ var HoldyCore = class {
|
|
|
103
128
|
body: JSON.stringify({ events }),
|
|
104
129
|
...this.source === "sdk_web" ? { keepalive: true } : {}
|
|
105
130
|
});
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
if (res.ok) return { kind: "sent" };
|
|
132
|
+
if (res.status === 429) {
|
|
133
|
+
this.log("rate limited \xB7 will retry after Retry-After");
|
|
134
|
+
return { kind: "retry", afterMs: retryAfterMs(res) };
|
|
135
|
+
}
|
|
136
|
+
if (res.status >= 500) {
|
|
137
|
+
this.log(`bulk failed ${res.status} \xB7 will retry with backoff`);
|
|
138
|
+
return { kind: "retry", afterMs: null };
|
|
113
139
|
}
|
|
114
|
-
|
|
140
|
+
if (res.status === 401) {
|
|
141
|
+
this.warnOnce("401", "API key inv\xE1lida ou revogada \xB7 eventos descartados. Confira a chave em Configura\xE7\xF5es \u203A SDK.");
|
|
142
|
+
return { kind: "dropped" };
|
|
143
|
+
}
|
|
144
|
+
if (res.status === 403) {
|
|
145
|
+
this.warnOnce("403", "Origem n\xE3o permitida ou chave sem escopo events:write \xB7 eventos descartados.");
|
|
146
|
+
return { kind: "dropped" };
|
|
147
|
+
}
|
|
148
|
+
this.log(`bulk rejected ${res.status} \xB7 batch dropped`);
|
|
149
|
+
return { kind: "dropped" };
|
|
115
150
|
} catch (err) {
|
|
116
|
-
this.log(`bulk network error: ${err.message} \xB7 will retry`);
|
|
117
|
-
return
|
|
151
|
+
this.log(`bulk network error: ${err.message} \xB7 will retry with backoff`);
|
|
152
|
+
return { kind: "retry", afterMs: null };
|
|
118
153
|
}
|
|
119
154
|
}
|
|
120
155
|
log(msg) {
|
|
121
156
|
if (this.debug) console.warn(`[holdy] ${msg}`);
|
|
122
157
|
}
|
|
158
|
+
warnOnce(key, msg) {
|
|
159
|
+
if (this.warned.has(key)) return;
|
|
160
|
+
this.warned.add(key);
|
|
161
|
+
console.warn(`[holdy] ${msg}`);
|
|
162
|
+
}
|
|
123
163
|
/**
|
|
124
164
|
* Ambiente que o SDK observa sozinho (url, referrer, browser, utm…).
|
|
125
165
|
* Vazio no core; `HoldyWeb` preenche. O server não infere ambiente — não
|
|
@@ -133,16 +173,78 @@ var HoldyCore = class {
|
|
|
133
173
|
return this.queue.length;
|
|
134
174
|
}
|
|
135
175
|
};
|
|
176
|
+
function retryAfterMs(res) {
|
|
177
|
+
const raw = typeof res.headers?.get === "function" ? res.headers.get("Retry-After") : null;
|
|
178
|
+
if (!raw) return null;
|
|
179
|
+
const seconds = Number(raw);
|
|
180
|
+
if (!Number.isFinite(seconds) || seconds < 0) return null;
|
|
181
|
+
return Math.min(MAX_BACKOFF_MS, seconds * 1e3);
|
|
182
|
+
}
|
|
183
|
+
function newEventId() {
|
|
184
|
+
const c = globalThis.crypto;
|
|
185
|
+
if (typeof c?.randomUUID === "function") return c.randomUUID();
|
|
186
|
+
const rand = () => Math.random().toString(36).slice(2, 12);
|
|
187
|
+
return `${Date.now().toString(36)}-${rand()}-${rand()}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/run.ts
|
|
191
|
+
function newRunId() {
|
|
192
|
+
const c = globalThis.crypto;
|
|
193
|
+
if (typeof c?.randomUUID === "function") return c.randomUUID();
|
|
194
|
+
return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
195
|
+
}
|
|
196
|
+
function runEventName(name, suffix) {
|
|
197
|
+
const base = name.trim().replace(/\.$/, "");
|
|
198
|
+
return `${base || "run"}.${suffix}`;
|
|
199
|
+
}
|
|
200
|
+
async function withRun(track, opts, fn) {
|
|
201
|
+
if (!opts || !opts.name) throw new Error("[holdy] run: name is required");
|
|
202
|
+
const id = opts.runId && opts.runId.length > 0 ? opts.runId : newRunId();
|
|
203
|
+
const base = { ...opts.properties ?? {}, ai_run_id: id };
|
|
204
|
+
const overrides = {
|
|
205
|
+
...opts.accountKey ? { accountKey: opts.accountKey } : {},
|
|
206
|
+
...opts.userId ? { userId: opts.userId } : {}
|
|
207
|
+
};
|
|
208
|
+
const emit = (event, properties) => track(event, { ...base, ...properties ?? {}, ai_run_id: id }, overrides);
|
|
209
|
+
const startedAt = Date.now();
|
|
210
|
+
emit(runEventName(opts.name, "started"));
|
|
211
|
+
try {
|
|
212
|
+
const result = await fn({ id, track: emit });
|
|
213
|
+
emit(runEventName(opts.name, "completed"), { duration_ms: Date.now() - startedAt });
|
|
214
|
+
return result;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
emit(runEventName(opts.name, "failed"), {
|
|
217
|
+
duration_ms: Date.now() - startedAt,
|
|
218
|
+
error: err instanceof Error ? err.name : "Error"
|
|
219
|
+
});
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
136
223
|
|
|
137
224
|
// src/server.ts
|
|
138
225
|
var Holdy = class extends HoldyCore {
|
|
139
226
|
constructor(opts) {
|
|
140
227
|
super("sdk_server", opts);
|
|
141
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Executa `fn` como UMA execução de agente: todo evento lá dentro carrega a
|
|
231
|
+
* mesma `ai_run_id`, e `<name>.started` / `.completed` / `.failed` saem
|
|
232
|
+
* sozinhos.
|
|
233
|
+
*
|
|
234
|
+
* await holdy.run({ name: 'support_agent', accountKey: 'acme' }, async (run) => {
|
|
235
|
+
* run.track('ticket_drafted', { ai_tokens: 1200, ai_cost_usd: 0.02 })
|
|
236
|
+
* return resolveTicket(ticket)
|
|
237
|
+
* })
|
|
238
|
+
*/
|
|
239
|
+
run(opts, fn) {
|
|
240
|
+
return withRun((name, properties, overrides) => this.track(name, properties, overrides), opts, fn);
|
|
241
|
+
}
|
|
142
242
|
};
|
|
143
243
|
export {
|
|
144
244
|
DEFAULT_BASE_URL,
|
|
145
245
|
Holdy,
|
|
146
|
-
HoldyCore
|
|
246
|
+
HoldyCore,
|
|
247
|
+
newRunId,
|
|
248
|
+
runEventName
|
|
147
249
|
};
|
|
148
250
|
//# sourceMappingURL=server.mjs.map
|
package/dist/server.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/core.ts", "../src/server.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Core do @holdyapp/sdk \u00B7 fila + flush pra POST /api/v1/events/bulk.\n *\n * Retry de batch inteiro \u00E9 seguro (partial accept + dedupe server-side).\n * Fila \u00E9 bounded: acima de MAX_QUEUE dropa os mais antigos (telemetria nunca\n * derruba o app do cliente \u2014 nenhum m\u00E9todo p\u00FAblico lan\u00E7a).\n */\n\nimport type { CanonicalEvent, HoldyOptions, Properties, TrackOverrides } from './types'\n\nexport const DEFAULT_BASE_URL = 'https://holdy.chat/api/v1'\nconst DEFAULT_FLUSH_AT = 20\nconst DEFAULT_FLUSH_INTERVAL_MS = 5000\nconst MAX_QUEUE = 1000\nconst BULK_LIMIT = 500\n\nexport class HoldyCore {\n private readonly apiKey: string\n private readonly baseUrl: string\n private readonly flushAt: number\n private readonly debug: boolean\n private readonly source: 'sdk_web' | 'sdk_server'\n\n private queue: CanonicalEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private flushing: Promise<void> | null = null\n\n protected userId: string | null = null\n protected anonymousId: string | null = null\n protected accountKey: string | null = null\n\n constructor(source: 'sdk_web' | 'sdk_server', opts: HoldyOptions) {\n this.source = source\n this.apiKey = opts.apiKey\n this.accountKey = opts.accountKey ?? null\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.flushAt = Math.max(1, opts.flushAt ?? DEFAULT_FLUSH_AT)\n this.debug = opts.debug ?? false\n\n const intervalMs = opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.flush(), intervalMs)\n // Node: n\u00E3o segura o processo vivo s\u00F3 pelo timer do SDK.\n if (typeof (this.timer as { unref?: () => void }).unref === 'function') {\n ;(this.timer as unknown as { unref: () => void }).unref()\n }\n }\n }\n\n track(name: string, properties?: Properties, overrides?: TrackOverrides): void {\n if (!name || typeof name !== 'string') return\n\n const userId = overrides?.userId ?? this.userId ?? undefined\n const accountKey = overrides?.accountKey ?? this.accountKey ?? undefined\n const occurredAt = overrides?.occurredAt\n ? new Date(overrides.occurredAt).toISOString()\n : new Date().toISOString()\n\n // Ordem: ambiente observado pelo SDK \u2192 context da chamada \u2192 carrier de\n // account. O que o caller passou vence o inferido; o carrier vence tudo,\n // porque \u00E9 o que a Data layer usa pra resolver a conta.\n const context: Properties = {\n ...this.environmentContext(),\n ...(overrides?.context ?? {}),\n }\n if (accountKey) context.account_external_id = accountKey\n\n const event: CanonicalEvent = {\n event: name,\n occurred_at: occurredAt,\n source: this.source,\n ...(userId ? { user_id: userId } : {}),\n ...(this.anonymousId ? { anonymous_id: this.anonymousId } : {}),\n properties: properties ?? {},\n ...(Object.keys(context).length > 0 ? { context } : {}),\n }\n\n this.queue.push(event)\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE)\n if (this.queue.length >= this.flushAt) void this.flush()\n }\n\n identify(userId: string, traits?: Properties): void {\n if (!userId || typeof userId !== 'string') return\n this.userId = userId\n this.track('$identify', traits ?? {})\n }\n\n group(accountKey: string, traits?: Properties): void {\n if (!accountKey || typeof accountKey !== 'string') return\n this.accountKey = accountKey\n this.track('$group', traits ?? {})\n }\n\n /** Drena a fila. Nunca lan\u00E7a \u2014 falha re-enfileira e loga em debug. */\n async flush(): Promise<void> {\n // Serializa flushes concorrentes (timer + flushAt + manual).\n if (this.flushing) return this.flushing\n if (this.queue.length === 0) return\n\n this.flushing = this.drain().finally(() => {\n this.flushing = null\n })\n return this.flushing\n }\n\n /** Flush final + para o timer. Chamar em shutdown do server / unload do web. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n await this.flush()\n }\n\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, BULK_LIMIT)\n const ok = await this.send(batch)\n if (!ok) {\n // Devolve pra frente da fila e para \u2014 pr\u00F3ximo flush re-tenta (dedupe-safe).\n this.queue.unshift(...batch)\n return\n }\n }\n }\n\n private async send(events: CanonicalEvent[]): Promise<boolean> {\n try {\n const res = await fetch(`${this.baseUrl}/events/bulk`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events }),\n ...(this.source === 'sdk_web' ? { keepalive: true } : {}),\n })\n if (!res.ok) {\n // 4xx: batch estruturalmente inv\u00E1lido n\u00E3o re-tenta (evita loop infinito).\n if (res.status >= 400 && res.status < 500) {\n this.log(`bulk rejected ${res.status} \u00B7 batch dropped`)\n return true\n }\n this.log(`bulk failed ${res.status} \u00B7 will retry`)\n return false\n }\n return true\n } catch (err) {\n this.log(`bulk network error: ${(err as Error).message} \u00B7 will retry`)\n return false\n }\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[holdy] ${msg}`)\n }\n\n /**\n * Ambiente que o SDK observa sozinho (url, referrer, browser, utm\u2026).\n * Vazio no core; `HoldyWeb` preenche. O server n\u00E3o infere ambiente \u2014 n\u00E3o\n * existe \"p\u00E1gina atual\" num worker.\n */\n protected environmentContext(): Properties {\n return {}\n }\n\n /** Interno \u00B7 exposto pra testes. */\n get pending(): number {\n return this.queue.length\n }\n}\n", "/**\n * @holdyapp/sdk (server) \u00B7 Node/Edge (source: sdk_server).\n *\n * Stateless por default: user_id/accountKey v\u00E3o por chamada em overrides.\n * identify()/group() de sess\u00E3o existem mas em server multi-tenant prefira\n * overrides \u2014 estado compartilhado entre requests vaza identidade.\n */\n\nimport { HoldyCore } from './core'\nimport type { HoldyOptions } from './types'\n\nexport class Holdy extends HoldyCore {\n constructor(opts: HoldyOptions) {\n super('sdk_server', opts)\n }\n}\n\nexport { HoldyCore, DEFAULT_BASE_URL } from './core'\nexport type { HoldyOptions, Properties, TrackOverrides, CanonicalEvent } from './types'\n"],
|
|
5
|
-
"mappings": ";
|
|
3
|
+
"sources": ["../src/core.ts", "../src/run.ts", "../src/server.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Core do @holdyapp/sdk \u00B7 fila + flush pra POST /api/v1/events/bulk.\n *\n * Todo evento sai com `external_id` pr\u00F3prio, ent\u00E3o reenviar um lote nunca\n * duplica (dedupe server-side por source + external_id + occurred_at).\n * 429 respeita Retry-After; 5xx e rede esperam com backoff exponencial; o lote\n * volta pra frente da fila em vez de sumir. Chave inv\u00E1lida ou origem recusada\n * avisam uma vez no console, mesmo sem debug. Fila \u00E9 bounded: acima de\n * MAX_QUEUE dropa os mais antigos (telemetria nunca derruba o app do cliente \u2014\n * nenhum m\u00E9todo p\u00FAblico lan\u00E7a).\n */\n\nimport type { CanonicalEvent, HoldyOptions, Properties, TrackOverrides } from './types'\n\nexport const DEFAULT_BASE_URL = 'https://holdy.chat/api/v1'\nconst DEFAULT_FLUSH_AT = 20\nconst DEFAULT_FLUSH_INTERVAL_MS = 5000\nconst MAX_QUEUE = 1000\nconst BULK_LIMIT = 500\nconst MAX_BACKOFF_MS = 60_000\n\ntype SendOutcome = { kind: 'sent' } | { kind: 'dropped' } | { kind: 'retry'; afterMs: number | null }\n\nexport class HoldyCore {\n protected readonly apiKey: string\n protected readonly baseUrl: string\n private readonly flushAt: number\n private readonly debug: boolean\n private readonly source: 'sdk_web' | 'sdk_server'\n\n private queue: CanonicalEvent[] = []\n private timer: ReturnType<typeof setInterval> | null = null\n private flushing: Promise<void> | null = null\n private retryAt = 0\n private failures = 0\n private readonly warned = new Set<string>()\n\n protected userId: string | null = null\n protected anonymousId: string | null = null\n protected accountKey: string | null = null\n\n constructor(source: 'sdk_web' | 'sdk_server', opts: HoldyOptions) {\n this.source = source\n this.apiKey = opts.apiKey\n this.accountKey = opts.accountKey ?? null\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '')\n this.flushAt = Math.max(1, opts.flushAt ?? DEFAULT_FLUSH_AT)\n this.debug = opts.debug ?? false\n\n const intervalMs = opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS\n if (intervalMs > 0) {\n this.timer = setInterval(() => void this.flush(), intervalMs)\n // Node: n\u00E3o segura o processo vivo s\u00F3 pelo timer do SDK.\n if (typeof (this.timer as { unref?: () => void }).unref === 'function') {\n ;(this.timer as unknown as { unref: () => void }).unref()\n }\n }\n }\n\n track(name: string, properties?: Properties, overrides?: TrackOverrides): void {\n if (!name || typeof name !== 'string') return\n\n const userId = overrides?.userId ?? this.userId ?? undefined\n const accountKey = overrides?.accountKey ?? this.accountKey ?? undefined\n const occurredAt = overrides?.occurredAt\n ? new Date(overrides.occurredAt).toISOString()\n : new Date().toISOString()\n\n // Ordem: ambiente observado pelo SDK \u2192 context da chamada \u2192 carrier de\n // account. O que o caller passou vence o inferido; o carrier vence tudo,\n // porque \u00E9 o que a Data layer usa pra resolver a conta.\n const context: Properties = {\n ...this.environmentContext(),\n ...(overrides?.context ?? {}),\n }\n if (accountKey) context.account_external_id = accountKey\n\n const event: CanonicalEvent = {\n event: name,\n occurred_at: occurredAt,\n source: this.source,\n external_id: newEventId(),\n ...(userId ? { user_id: userId } : {}),\n ...(this.anonymousId ? { anonymous_id: this.anonymousId } : {}),\n properties: properties ?? {},\n ...(Object.keys(context).length > 0 ? { context } : {}),\n }\n\n this.queue.push(event)\n this.trimQueue()\n if (this.queue.length >= this.flushAt) void this.flush()\n }\n\n identify(userId: string, traits?: Properties): void {\n if (!userId || typeof userId !== 'string') return\n this.userId = userId\n this.track('$identify', traits ?? {})\n }\n\n group(accountKey: string, traits?: Properties): void {\n if (!accountKey || typeof accountKey !== 'string') return\n this.accountKey = accountKey\n this.track('$group', traits ?? {})\n }\n\n /** Drena a fila. Nunca lan\u00E7a \u2014 falha re-enfileira e espera antes de tentar de novo. */\n async flush(): Promise<void> {\n return this.startFlush(false)\n }\n\n /** Flush final + para o timer. Chamar em shutdown do server / unload do web. */\n async shutdown(): Promise<void> {\n if (this.timer) {\n clearInterval(this.timer)\n this.timer = null\n }\n await this.startFlush(true)\n }\n\n private startFlush(force: boolean): Promise<void> {\n // Serializa flushes concorrentes (timer + flushAt + manual).\n if (this.flushing) return this.flushing\n if (this.queue.length === 0) return Promise.resolve()\n if (!force && Date.now() < this.retryAt) return Promise.resolve()\n\n this.flushing = this.drain().finally(() => {\n this.flushing = null\n })\n return this.flushing\n }\n\n private async drain(): Promise<void> {\n while (this.queue.length > 0) {\n const batch = this.queue.splice(0, BULK_LIMIT)\n const outcome = await this.send(batch)\n if (outcome.kind === 'retry') {\n this.queue.unshift(...batch)\n this.trimQueue()\n this.scheduleRetry(outcome.afterMs)\n return\n }\n this.failures = 0\n this.retryAt = 0\n }\n }\n\n private scheduleRetry(afterMs: number | null): void {\n this.failures += 1\n if (afterMs !== null) {\n this.retryAt = Date.now() + afterMs\n return\n }\n const backoff = Math.min(MAX_BACKOFF_MS, 1000 * 2 ** (this.failures - 1))\n this.retryAt = Date.now() + backoff + Math.floor(Math.random() * backoff * 0.2)\n }\n\n private trimQueue(): void {\n if (this.queue.length > MAX_QUEUE) this.queue.splice(0, this.queue.length - MAX_QUEUE)\n }\n\n private async send(events: CanonicalEvent[]): Promise<SendOutcome> {\n try {\n const res = await fetch(`${this.baseUrl}/events/bulk`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ events }),\n ...(this.source === 'sdk_web' ? { keepalive: true } : {}),\n })\n if (res.ok) return { kind: 'sent' }\n\n if (res.status === 429) {\n this.log('rate limited \u00B7 will retry after Retry-After')\n return { kind: 'retry', afterMs: retryAfterMs(res) }\n }\n if (res.status >= 500) {\n this.log(`bulk failed ${res.status} \u00B7 will retry with backoff`)\n return { kind: 'retry', afterMs: null }\n }\n if (res.status === 401) {\n this.warnOnce('401', 'API key inv\u00E1lida ou revogada \u00B7 eventos descartados. Confira a chave em Configura\u00E7\u00F5es \u203A SDK.')\n return { kind: 'dropped' }\n }\n if (res.status === 403) {\n this.warnOnce('403', 'Origem n\u00E3o permitida ou chave sem escopo events:write \u00B7 eventos descartados.')\n return { kind: 'dropped' }\n }\n this.log(`bulk rejected ${res.status} \u00B7 batch dropped`)\n return { kind: 'dropped' }\n } catch (err) {\n this.log(`bulk network error: ${(err as Error).message} \u00B7 will retry with backoff`)\n return { kind: 'retry', afterMs: null }\n }\n }\n\n private log(msg: string): void {\n if (this.debug) console.warn(`[holdy] ${msg}`)\n }\n\n private warnOnce(key: string, msg: string): void {\n if (this.warned.has(key)) return\n this.warned.add(key)\n console.warn(`[holdy] ${msg}`)\n }\n\n /**\n * Ambiente que o SDK observa sozinho (url, referrer, browser, utm\u2026).\n * Vazio no core; `HoldyWeb` preenche. O server n\u00E3o infere ambiente \u2014 n\u00E3o\n * existe \"p\u00E1gina atual\" num worker.\n */\n protected environmentContext(): Properties {\n return {}\n }\n\n /** Interno \u00B7 exposto pra testes. */\n get pending(): number {\n return this.queue.length\n }\n}\n\nfunction retryAfterMs(res: Response): number | null {\n const raw = typeof res.headers?.get === 'function' ? res.headers.get('Retry-After') : null\n if (!raw) return null\n const seconds = Number(raw)\n if (!Number.isFinite(seconds) || seconds < 0) return null\n return Math.min(MAX_BACKOFF_MS, seconds * 1000)\n}\n\nfunction newEventId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (typeof c?.randomUUID === 'function') return c.randomUUID()\n const rand = () => Math.random().toString(36).slice(2, 12)\n return `${Date.now().toString(36)}-${rand()}-${rand()}`\n}\n", "/**\n * Execu\u00E7\u00E3o como unidade de trabalho (source: sdk_server).\n *\n * Produto de agente n\u00E3o tem sess\u00E3o de browser: o trabalho roda no servidor,\n * por API, em background. Pra Holdy dizer \"come\u00E7ou e concluiu na mesma\n * execu\u00E7\u00E3o\" e dividir recurso por resultado entregue, todos os eventos de uma\n * tarefa carregam a mesma `ai_run_id` \u2014 a chave que o composer usa por padr\u00E3o\n * no gr\u00E3o de execu\u00E7\u00E3o (lib/knowledge/compose-definition.ts).\n *\n * N\u00E3o inventa vocabul\u00E1rio: in\u00EDcio e fim saem com o nome que o cliente deu \u00E0\n * execu\u00E7\u00E3o (`support_agent.started`), e todo passo no meio \u00E9 nome dele. O\n * retorno passa direto e o erro propaga; s\u00F3 o NOME do erro vira propriedade,\n * porque a mensagem carrega dado do cliente com frequ\u00EAncia demais.\n */\n\nimport type { Properties, TrackOverrides } from './types'\n\nexport interface RunOptions {\n /** Nome da execu\u00E7\u00E3o no vocabul\u00E1rio do produto, ex.: 'support_agent'. */\n name: string\n /** Conta (id externo) dona da execu\u00E7\u00E3o. */\n accountKey?: string\n userId?: string\n /** Chave pr\u00F3pria (trace id do agente); ausente = gerada aqui. */\n runId?: string\n /** Propriedades repetidas em todos os eventos da execu\u00E7\u00E3o. */\n properties?: Properties\n}\n\nexport interface RunHandle {\n /** A chave que agrupa tudo desta execu\u00E7\u00E3o. */\n id: string\n /** Um passo dentro da execu\u00E7\u00E3o, j\u00E1 carimbado com ai_run_id. */\n track(event: string, properties?: Properties): void\n}\n\nexport type TrackFn = (name: string, properties?: Properties, overrides?: TrackOverrides) => void\n\nexport function newRunId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto\n if (typeof c?.randomUUID === 'function') return c.randomUUID()\n return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`\n}\n\nexport function runEventName(name: string, suffix: 'started' | 'completed' | 'failed'): string {\n const base = name.trim().replace(/\\.$/, '')\n return `${base || 'run'}.${suffix}`\n}\n\nexport async function withRun<T>(\n track: TrackFn,\n opts: RunOptions,\n fn: (run: RunHandle) => Promise<T> | T,\n): Promise<T> {\n if (!opts || !opts.name) throw new Error('[holdy] run: name is required')\n\n const id = opts.runId && opts.runId.length > 0 ? opts.runId : newRunId()\n const base: Properties = { ...(opts.properties ?? {}), ai_run_id: id }\n const overrides: TrackOverrides = {\n ...(opts.accountKey ? { accountKey: opts.accountKey } : {}),\n ...(opts.userId ? { userId: opts.userId } : {}),\n }\n const emit = (event: string, properties?: Properties) =>\n track(event, { ...base, ...(properties ?? {}), ai_run_id: id }, overrides)\n\n const startedAt = Date.now()\n emit(runEventName(opts.name, 'started'))\n\n try {\n const result = await fn({ id, track: emit })\n emit(runEventName(opts.name, 'completed'), { duration_ms: Date.now() - startedAt })\n return result\n } catch (err) {\n emit(runEventName(opts.name, 'failed'), {\n duration_ms: Date.now() - startedAt,\n error: err instanceof Error ? err.name : 'Error',\n })\n throw err\n }\n}\n", "/**\n * @holdyapp/sdk (server) \u00B7 Node/Edge (source: sdk_server).\n *\n * Stateless por default: user_id/accountKey v\u00E3o por chamada em overrides.\n * identify()/group() de sess\u00E3o existem mas em server multi-tenant prefira\n * overrides \u2014 estado compartilhado entre requests vaza identidade.\n */\n\nimport { HoldyCore } from './core'\nimport { withRun, type RunHandle, type RunOptions } from './run'\nimport type { HoldyOptions } from './types'\n\nexport class Holdy extends HoldyCore {\n constructor(opts: HoldyOptions) {\n super('sdk_server', opts)\n }\n\n /**\n * Executa `fn` como UMA execu\u00E7\u00E3o de agente: todo evento l\u00E1 dentro carrega a\n * mesma `ai_run_id`, e `<name>.started` / `.completed` / `.failed` saem\n * sozinhos.\n *\n * await holdy.run({ name: 'support_agent', accountKey: 'acme' }, async (run) => {\n * run.track('ticket_drafted', { ai_tokens: 1200, ai_cost_usd: 0.02 })\n * return resolveTicket(ticket)\n * })\n */\n run<T>(opts: RunOptions, fn: (run: RunHandle) => Promise<T> | T): Promise<T> {\n return withRun((name, properties, overrides) => this.track(name, properties, overrides), opts, fn)\n }\n}\n\nexport { HoldyCore, DEFAULT_BASE_URL } from './core'\nexport type { HoldyOptions, Properties, TrackOverrides, CanonicalEvent } from './types'\nexport { newRunId, runEventName } from './run'\nexport type { RunHandle, RunOptions } from './run'\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const SKELETON_SELECTOR = "a[href], button, input, select, textarea, [role=\"button\"], [role=\"link\"], [role=\"tab\"], [role=\"menuitem\"], [role=\"checkbox\"], [role=\"radio\"], [role=\"switch\"], [data-testid]";
|
|
2
|
+
export declare const SKELETON_MAX_ELEMENTS = 80;
|
|
3
|
+
export declare const SKELETON_DELAY_MS = 1500;
|
|
4
|
+
export interface SkeletonRect {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SkeletonElementLike {
|
|
11
|
+
tagName: string;
|
|
12
|
+
getAttribute(name: string): string | null;
|
|
13
|
+
getBoundingClientRect?(): SkeletonRect;
|
|
14
|
+
}
|
|
15
|
+
export interface SkeletonRootLike {
|
|
16
|
+
querySelectorAll(selector: string): ArrayLike<SkeletonElementLike>;
|
|
17
|
+
}
|
|
18
|
+
export interface SkeletonViewport {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
scrollY: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SkeletonElement {
|
|
24
|
+
t: string;
|
|
25
|
+
r?: string;
|
|
26
|
+
id?: string;
|
|
27
|
+
n?: string;
|
|
28
|
+
l?: string;
|
|
29
|
+
k?: string;
|
|
30
|
+
b?: [number, number, number, number];
|
|
31
|
+
}
|
|
32
|
+
export interface PageSkeleton {
|
|
33
|
+
path: string;
|
|
34
|
+
hash: string;
|
|
35
|
+
count: number;
|
|
36
|
+
elements: SkeletonElement[];
|
|
37
|
+
}
|
|
38
|
+
export declare function fnv1a(input: string): string;
|
|
39
|
+
export declare function describeSkeletonElement(el: SkeletonElementLike, viewport: SkeletonViewport | null): SkeletonElement;
|
|
40
|
+
export declare function collectSkeleton(root: SkeletonRootLike, path: string, viewport: SkeletonViewport | null): PageSkeleton;
|
|
41
|
+
export interface SkeletonCaptureOptions {
|
|
42
|
+
onSkeleton: (skeleton: PageSkeleton) => void;
|
|
43
|
+
delayMs?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface SkeletonCapture {
|
|
46
|
+
onNavigate: (path: string) => void;
|
|
47
|
+
teardown: () => void;
|
|
48
|
+
}
|
|
49
|
+
export declare function installSkeletonCapture(opts: SkeletonCaptureOptions): SkeletonCapture;
|
|
50
|
+
//# sourceMappingURL=skeleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skeleton.d.ts","sourceRoot":"","sources":["../src/skeleton.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,+LACkJ,CAAA;AAChL,eAAO,MAAM,qBAAqB,KAAK,CAAA;AACvC,eAAO,MAAM,iBAAiB,OAAO,CAAA;AAIrC,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACzC,qBAAqB,CAAC,IAAI,YAAY,CAAA;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC,mBAAmB,CAAC,CAAA;CACnE;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,eAAe,EAAE,CAAA;CAC5B;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO3C;AAoBD,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,mBAAmB,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAAG,eAAe,CAgBnH;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,GAAG,YAAY,CAYrH;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB;AAoBD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,GAAG,eAAe,CAsCpF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface CanonicalEvent {
|
|
|
10
10
|
event: string;
|
|
11
11
|
occurred_at: string;
|
|
12
12
|
source: 'sdk_web' | 'sdk_server';
|
|
13
|
+
/** Id do evento gerado no SDK · torna o reenvio de um lote idempotente. */
|
|
14
|
+
external_id?: string;
|
|
13
15
|
user_id?: string;
|
|
14
16
|
anonymous_id?: string;
|
|
15
17
|
properties: Properties;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEhD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,UAAU,CAAA;IACtB,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gCAAgC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;IAC1B,sCAAsC;IACtC,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEhD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;IAChC,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,EAAE,UAAU,CAAA;IACtB,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gCAAgC;IAChC,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;IAC1B,sCAAsC;IACtC,OAAO,CAAC,EAAE,UAAU,CAAA;CACrB"}
|
package/dist/web.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* carregamento inicial e cada mudança de rota, inclusive em SPA.
|
|
12
12
|
*/
|
|
13
13
|
import { HoldyCore } from './core';
|
|
14
|
+
import { type FrictionCaptureOptions } from './friction';
|
|
14
15
|
import type { HoldyOptions, Properties } from './types';
|
|
15
16
|
export interface HoldyWebOptions extends HoldyOptions {
|
|
16
17
|
/**
|
|
@@ -18,9 +19,33 @@ export interface HoldyWebOptions extends HoldyOptions {
|
|
|
18
19
|
* Desligue só se o produto já chama `page()` na mão em cada tela.
|
|
19
20
|
*/
|
|
20
21
|
autoCapture?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Capturar fricção sozinho · default true. Emite `$dead_click`, `$rage_click`,
|
|
24
|
+
* `$form_abandon`, `$js_error`, `$slow_request`, `$nav_loop` e `$http_error`
|
|
25
|
+
* (requisição com status ≥ 400 ou sem resposta, e a própria página que abre
|
|
26
|
+
* com erro) — eventos determinísticos, sem DOM nem replay. Objeto ajusta os
|
|
27
|
+
* limiares; a URL da Holdy nunca conta como erro.
|
|
28
|
+
*/
|
|
29
|
+
friction?: boolean | Omit<FrictionCaptureOptions, 'onFriction'>;
|
|
30
|
+
/**
|
|
31
|
+
* Aplicar as mudanças de texto e dica que a org pôs no ar na Holdy · default
|
|
32
|
+
* true. Parte das contas fica de fora por sorteio pra medir; sem conta
|
|
33
|
+
* identificada (`group()` ou `accountKey`) nada é aplicado.
|
|
34
|
+
*/
|
|
35
|
+
productChanges?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Mandar o esqueleto da página · default true. Uma vez por (rota, estrutura)
|
|
38
|
+
* por aba: `$page_skeleton` com os elementos interativos (tag, role,
|
|
39
|
+
* data-testid, name, aria-label, tipo, posição relativa). Sem texto livre,
|
|
40
|
+
* sem valor de input, sem pixel.
|
|
41
|
+
*/
|
|
42
|
+
skeleton?: boolean;
|
|
21
43
|
}
|
|
22
44
|
export declare class HoldyWeb extends HoldyCore {
|
|
23
45
|
private teardownAutoCapture;
|
|
46
|
+
private friction;
|
|
47
|
+
private changes;
|
|
48
|
+
private skeleton;
|
|
24
49
|
constructor(opts: HoldyWebOptions);
|
|
25
50
|
/**
|
|
26
51
|
* Pageview. O `$` marca evento emitido pelo SDK, não pelo produto (mesma
|
|
@@ -30,6 +55,7 @@ export declare class HoldyWeb extends HoldyCore {
|
|
|
30
55
|
* que é da página como conteúdo (nome e título).
|
|
31
56
|
*/
|
|
32
57
|
page(name?: string, properties?: Properties): void;
|
|
58
|
+
group(accountKey: string, traits?: Properties): void;
|
|
33
59
|
reset(): void;
|
|
34
60
|
/** Flush final + desinstala o auto-capture (devolve `history` ao original). */
|
|
35
61
|
shutdown(): Promise<void>;
|
package/dist/web.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAElC,OAAO,EAAgD,KAAK,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAItG,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAIvD,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,sBAAsB,EAAE,YAAY,CAAC,CAAA;IAC/D;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,qBAAa,QAAS,SAAQ,SAAS;IACrC,OAAO,CAAC,mBAAmB,CAA4B;IACvD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,QAAQ,CAA+B;gBAEnC,IAAI,EAAE,eAAe;IAiDjC;;;;;;OAMG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI;IAQzC,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI;IAK7D,KAAK,IAAI,IAAI;IAYb,+EAA+E;IAChE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;cAYrB,kBAAkB,IAAI,UAAU;CAGpD;AAgBD,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,IAAI,EAAE,eAAe,GAAG,QAAQ,CAYzD;AAED,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,CAAA"}
|