@holdyapp/sdk 0.2.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +20 -147
  2. package/dist/auto-capture.d.ts +12 -16
  3. package/dist/auto-capture.d.ts.map +1 -0
  4. package/dist/browser.d.ts +23 -0
  5. package/dist/browser.d.ts.map +1 -0
  6. package/dist/core.d.ts +42 -0
  7. package/dist/core.d.ts.map +1 -0
  8. package/dist/holdy.js +2 -0
  9. package/dist/holdy.js.map +7 -0
  10. package/dist/index.cjs.js +422 -0
  11. package/dist/index.cjs.js.map +7 -0
  12. package/dist/index.d.ts +5 -46
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.mjs +399 -0
  15. package/dist/index.mjs.map +7 -0
  16. package/dist/server.cjs.js +171 -0
  17. package/dist/server.cjs.js.map +7 -0
  18. package/dist/server.d.ts +15 -0
  19. package/dist/server.d.ts.map +1 -0
  20. package/dist/server.mjs +148 -0
  21. package/dist/server.mjs.map +7 -0
  22. package/dist/types.d.ts +41 -109
  23. package/dist/types.d.ts.map +1 -0
  24. package/dist/web-context.d.ts +51 -0
  25. package/dist/web-context.d.ts.map +1 -0
  26. package/dist/web.d.ts +41 -0
  27. package/dist/web.d.ts.map +1 -0
  28. package/dist/web.mjs +389 -0
  29. package/dist/web.mjs.map +7 -0
  30. package/package.json +24 -20
  31. package/src/auto-capture.ts +51 -78
  32. package/src/browser.ts +55 -0
  33. package/src/core.test.ts +147 -0
  34. package/src/core.ts +172 -0
  35. package/src/index.ts +4 -427
  36. package/src/server.ts +19 -0
  37. package/src/types.ts +38 -162
  38. package/src/web-context.test.ts +71 -0
  39. package/src/web-context.ts +227 -0
  40. package/src/web.ts +108 -0
  41. package/LICENSE +0 -21
  42. package/dist/anchor.d.ts +0 -8
  43. package/dist/holdy.cjs.js +0 -255
  44. package/dist/holdy.cjs.js.map +0 -7
  45. package/dist/holdy.esm.js +0 -255
  46. package/dist/holdy.esm.js.map +0 -7
  47. package/dist/holdy.iife.js +0 -255
  48. package/dist/holdy.iife.js.map +0 -7
  49. package/dist/outcome.d.ts +0 -19
  50. package/dist/queue.d.ts +0 -54
  51. package/dist/render/kinds/banner.d.ts +0 -3
  52. package/dist/render/kinds/checklist.d.ts +0 -19
  53. package/dist/render/kinds/cta.d.ts +0 -8
  54. package/dist/render/kinds/modal.d.ts +0 -16
  55. package/dist/render/kinds/shared.d.ts +0 -16
  56. package/dist/render/kinds/spotlight.d.ts +0 -17
  57. package/dist/render/kinds/tooltip.d.ts +0 -3
  58. package/dist/render/root.d.ts +0 -3
  59. package/dist/render/styles.d.ts +0 -5
  60. package/dist/store.d.ts +0 -36
  61. package/dist/transport.d.ts +0 -59
  62. package/src/anchor.ts +0 -72
  63. package/src/outcome.ts +0 -51
  64. package/src/queue.ts +0 -296
  65. package/src/render/kinds/banner.ts +0 -52
  66. package/src/render/kinds/checklist.ts +0 -175
  67. package/src/render/kinds/cta.ts +0 -53
  68. package/src/render/kinds/modal.ts +0 -129
  69. package/src/render/kinds/shared.ts +0 -67
  70. package/src/render/kinds/spotlight.ts +0 -176
  71. package/src/render/kinds/tooltip.ts +0 -82
  72. package/src/render/root.ts +0 -69
  73. package/src/render/styles.ts +0 -280
  74. package/src/store.ts +0 -106
  75. package/src/transport.ts +0 -182
package/src/store.ts DELETED
@@ -1,106 +0,0 @@
1
- import type { InterventionKind } from './types'
2
-
3
- interface ActiveSlot {
4
- decisionId: string
5
- confidence: number
6
- kind: InterventionKind
7
- dismiss: () => void
8
- }
9
-
10
- /**
11
- * Active intervention slots · prioridade por confidence.
12
- *
13
- * Caps globais:
14
- * - 1 banner global
15
- * - 1 modal global (Wave 2 · UX bloqueia tudo, único)
16
- * - 1 checklist global (Wave 2 · sticky bottom-right)
17
- * - N tooltips, 1 por anchor
18
- * - N spotlights, 1 por anchor (Wave 2)
19
- * - 1 CTA global (sticky bottom-right)
20
- *
21
- * Modal e checklist + cta competem pelo mesmo "espaço mental" do usuário ·
22
- * a próxima implementação pode consolidar (1 floating element no canto).
23
- * Wave 2 mantém slots separados pra simplicidade.
24
- *
25
- * Quando 2 decisions reivindicam o mesmo slot, a de menor confidence perde.
26
- */
27
- export class InterventionStore {
28
- private readonly tooltipsByAnchor = new Map<string, ActiveSlot>()
29
- private readonly spotlightsByAnchor = new Map<string, ActiveSlot>()
30
- private banner: ActiveSlot | null = null
31
- private modal: ActiveSlot | null = null
32
- private checklist: ActiveSlot | null = null
33
- private cta: ActiveSlot | null = null
34
-
35
- /** Tenta reivindicar slot. Retorna previous slot que precisa ser dismissado (caller). */
36
- claim(
37
- kind: InterventionKind,
38
- decisionId: string,
39
- confidence: number,
40
- anchorSelector: string | undefined,
41
- dismiss: () => void,
42
- ): ActiveSlot | null {
43
- const slot: ActiveSlot = { decisionId, confidence, kind, dismiss }
44
-
45
- if (kind === 'tooltip') {
46
- const key = anchorSelector ?? '__no_anchor__'
47
- const prev = this.tooltipsByAnchor.get(key)
48
- if (prev && prev.confidence >= confidence) return slot
49
- this.tooltipsByAnchor.set(key, slot)
50
- return prev ?? null
51
- }
52
-
53
- if (kind === 'spotlight') {
54
- const key = anchorSelector ?? '__no_anchor__'
55
- const prev = this.spotlightsByAnchor.get(key)
56
- if (prev && prev.confidence >= confidence) return slot
57
- this.spotlightsByAnchor.set(key, slot)
58
- return prev ?? null
59
- }
60
-
61
- if (kind === 'banner') {
62
- const prev = this.banner
63
- if (prev && prev.confidence >= confidence) return slot
64
- this.banner = slot
65
- return prev ?? null
66
- }
67
-
68
- if (kind === 'modal') {
69
- const prev = this.modal
70
- if (prev && prev.confidence >= confidence) return slot
71
- this.modal = slot
72
- return prev ?? null
73
- }
74
-
75
- if (kind === 'checklist') {
76
- const prev = this.checklist
77
- if (prev && prev.confidence >= confidence) return slot
78
- this.checklist = slot
79
- return prev ?? null
80
- }
81
-
82
- if (kind === 'cta') {
83
- const prev = this.cta
84
- if (prev && prev.confidence >= confidence) return slot
85
- this.cta = slot
86
- return prev ?? null
87
- }
88
-
89
- return null
90
- }
91
-
92
- release(decisionId: string): void {
93
- if (this.banner?.decisionId === decisionId) this.banner = null
94
- if (this.modal?.decisionId === decisionId) this.modal = null
95
- if (this.checklist?.decisionId === decisionId) this.checklist = null
96
- if (this.cta?.decisionId === decisionId) this.cta = null
97
- for (const map of [this.tooltipsByAnchor, this.spotlightsByAnchor]) {
98
- for (const [k, slot] of map) {
99
- if (slot.decisionId === decisionId) {
100
- map.delete(k)
101
- break
102
- }
103
- }
104
- }
105
- }
106
- }
package/src/transport.ts DELETED
@@ -1,182 +0,0 @@
1
- import type {
2
- AccountIdentity,
3
- CanonicalPrimitive,
4
- DecisionResult,
5
- HoldyConfig,
6
- RuntimeConfig,
7
- UserIdentity,
8
- } from './types'
9
- import { EventQueue } from './queue'
10
-
11
- const DEFAULT_API_URL = 'https://app.holdy.com.br/api/v1'
12
-
13
- export class Transport {
14
- private readonly apiKey: string
15
- private readonly apiUrl: string
16
- private readonly debug: boolean
17
- private readonly queue: EventQueue
18
-
19
- constructor(config: HoldyConfig) {
20
- this.apiKey = config.apiKey
21
- this.apiUrl = (config.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '')
22
- this.debug = config.debug ?? false
23
- this.queue = new EventQueue(
24
- (body) => this.sendBatch(body.events),
25
- (...args) => this.log(...args),
26
- )
27
- }
28
-
29
- private headers(): Record<string, string> {
30
- return {
31
- 'content-type': 'application/json',
32
- authorization: `Bearer ${this.apiKey}`,
33
- }
34
- }
35
-
36
- async fetchRuntimeConfig(): Promise<RuntimeConfig | null> {
37
- try {
38
- const res = await fetch(`${this.apiUrl}/runtime/config`, {
39
- method: 'GET',
40
- headers: this.headers(),
41
- credentials: 'omit',
42
- mode: 'cors',
43
- })
44
- if (!res.ok) {
45
- this.log('runtime/config failed', res.status)
46
- return null
47
- }
48
- return (await res.json()) as RuntimeConfig
49
- } catch (e) {
50
- this.log('runtime/config error', e)
51
- return null
52
- }
53
- }
54
-
55
- async evaluate(input: {
56
- account_id: string
57
- current_workflow?: string
58
- recent_primitives?: Array<{
59
- primitive: CanonicalPrimitive
60
- payload: Record<string, unknown>
61
- occurred_at: string
62
- }>
63
- recent_sentiment?: 'positivo' | 'neutro' | 'negativo' | 'urgente'
64
- consumer?: 'ui' | 'csm' | 'agent'
65
- }): Promise<DecisionResult | null> {
66
- try {
67
- const res = await fetch(`${this.apiUrl}/decisions/evaluate`, {
68
- method: 'POST',
69
- headers: this.headers(),
70
- body: JSON.stringify({ consumer: 'ui', ...input }),
71
- credentials: 'omit',
72
- mode: 'cors',
73
- })
74
- if (!res.ok) {
75
- this.log('decisions/evaluate failed', res.status)
76
- return null
77
- }
78
- return (await res.json()) as DecisionResult
79
- } catch (e) {
80
- this.log('decisions/evaluate error', e)
81
- return null
82
- }
83
- }
84
-
85
- /**
86
- * Event ingest · delegado pra EventQueue (IndexedDB + exponential backoff).
87
- * Tenta envio imediato; se falhar, persiste e retry em background.
88
- */
89
- ingestBatch(events: Array<{
90
- primitive: CanonicalPrimitive
91
- occurred_at: string
92
- account_id?: string
93
- user_id?: string
94
- session_id?: string
95
- payload: Record<string, unknown>
96
- idempotency_key?: string
97
- }>): void {
98
- if (events.length === 0) return
99
- void this.queue.enqueue(events)
100
- }
101
-
102
- /**
103
- * Send raw · usado pelo queue. Retorna true em sucesso (2xx), false em
104
- * falha (rede ou 4xx/5xx).
105
- *
106
- * IMPORTANTE: 4xx é tratado como falha permanente — não vale retry. Mas
107
- * a fila não distingue (mantém pra retry simples). MAX_ATTEMPTS = 5 limita
108
- * o desperdício. Refinamento (4xx → drop imediato) fica pra Wave 3.
109
- */
110
- private async sendBatch(events: unknown[]): Promise<boolean> {
111
- try {
112
- const res = await fetch(`${this.apiUrl}/events/batch`, {
113
- method: 'POST',
114
- headers: this.headers(),
115
- body: JSON.stringify({ events }),
116
- credentials: 'omit',
117
- mode: 'cors',
118
- keepalive: true,
119
- })
120
- return res.ok
121
- } catch (e) {
122
- this.log('events/batch network error', e)
123
- return false
124
- }
125
- }
126
-
127
- /**
128
- * Upsert account + opcional user via /v1/accounts/upsert.
129
- *
130
- * Best-effort fire-and-forget. Backend COALESCE não sobrescreve campos não-nulos.
131
- * Retorna account_id (UUID Holdy) pra hidratar config local; null em falha.
132
- */
133
- async upsertAccount(payload: {
134
- account: AccountIdentity
135
- user?: UserIdentity
136
- }): Promise<{ account_id: string; account_user_id: string | null } | null> {
137
- try {
138
- const res = await fetch(`${this.apiUrl}/accounts/upsert`, {
139
- method: 'POST',
140
- headers: this.headers(),
141
- body: JSON.stringify(payload),
142
- credentials: 'omit',
143
- mode: 'cors',
144
- keepalive: true,
145
- })
146
- if (!res.ok) {
147
- this.log('accounts/upsert failed', res.status)
148
- return null
149
- }
150
- return (await res.json()) as { account_id: string; account_user_id: string | null }
151
- } catch (e) {
152
- this.log('accounts/upsert error', e)
153
- return null
154
- }
155
- }
156
-
157
- reportOutcome(
158
- decisionId: string,
159
- outcome: 'executed' | 'dismissed' | 'converted' | 'no_show' | 'failed',
160
- metadata?: Record<string, unknown>,
161
- ): void {
162
- const url = `${this.apiUrl}/decisions/${encodeURIComponent(decisionId)}/outcome`
163
- fetch(url, {
164
- method: 'POST',
165
- headers: this.headers(),
166
- body: JSON.stringify({ outcome, metadata }),
167
- credentials: 'omit',
168
- mode: 'cors',
169
- keepalive: true,
170
- }).catch((e) => this.log('decisions/outcome error', e))
171
- }
172
-
173
- private log(...args: unknown[]): void {
174
- if (this.debug && typeof console !== 'undefined') {
175
- console.warn('[holdy]', ...args)
176
- }
177
- }
178
-
179
- destroy(): void {
180
- this.queue.destroy()
181
- }
182
- }