@hipnation-truth/sdk 0.1.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/react.js ADDED
@@ -0,0 +1,367 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __pow = Math.pow;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+ var __async = (__this, __arguments, generator) => {
22
+ return new Promise((resolve, reject) => {
23
+ var fulfilled = (value) => {
24
+ try {
25
+ step(generator.next(value));
26
+ } catch (e) {
27
+ reject(e);
28
+ }
29
+ };
30
+ var rejected = (value) => {
31
+ try {
32
+ step(generator.throw(value));
33
+ } catch (e) {
34
+ reject(e);
35
+ }
36
+ };
37
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
38
+ step((generator = generator.apply(__this, __arguments)).next());
39
+ });
40
+ };
41
+
42
+ // src/react.ts
43
+ var react_exports = {};
44
+ __export(react_exports, {
45
+ TruthProvider: () => TruthProvider,
46
+ TruthTrackingProvider: () => TruthTrackingProvider,
47
+ useAppointment: () => useAppointment,
48
+ useAppointmentByElationId: () => useAppointmentByElationId,
49
+ useAppointments: () => useAppointments,
50
+ usePatient: () => usePatient,
51
+ usePatientByElationId: () => usePatientByElationId,
52
+ usePatientByHintId: () => usePatientByHintId,
53
+ usePatients: () => usePatients,
54
+ useTruth: () => useTruth
55
+ });
56
+ module.exports = __toCommonJS(react_exports);
57
+
58
+ // src/react/hooks.ts
59
+ var import_react = require("convex/react");
60
+ var import_server = require("convex/server");
61
+ var patientsListRef = (0, import_server.makeFunctionReference)("patients:list");
62
+ var patientsGetRef = (0, import_server.makeFunctionReference)("patients:get");
63
+ var patientsByElationIdRef = (0, import_server.makeFunctionReference)("patients:getByElationId");
64
+ var patientsByHintIdRef = (0, import_server.makeFunctionReference)("patients:getByHintId");
65
+ var appointmentsListRef = (0, import_server.makeFunctionReference)("appointments:list");
66
+ var appointmentsGetRef = (0, import_server.makeFunctionReference)("appointments:get");
67
+ var appointmentsByElationIdRef = (0, import_server.makeFunctionReference)("appointments:getByElationId");
68
+ function usePatients(options) {
69
+ return (0, import_react.useQuery)(patientsListRef, options != null ? options : {});
70
+ }
71
+ function usePatient(id) {
72
+ return (0, import_react.useQuery)(patientsGetRef, { id });
73
+ }
74
+ function usePatientByElationId(elationId) {
75
+ return (0, import_react.useQuery)(patientsByElationIdRef, {
76
+ elationId
77
+ });
78
+ }
79
+ function usePatientByHintId(hintId) {
80
+ return (0, import_react.useQuery)(patientsByHintIdRef, {
81
+ hintId
82
+ });
83
+ }
84
+ function useAppointments(options) {
85
+ return (0, import_react.useQuery)(
86
+ appointmentsListRef,
87
+ options != null ? options : {}
88
+ );
89
+ }
90
+ function useAppointment(id) {
91
+ return (0, import_react.useQuery)(appointmentsGetRef, { id });
92
+ }
93
+ function useAppointmentByElationId(elationId) {
94
+ return (0, import_react.useQuery)(appointmentsByElationIdRef, {
95
+ elationId
96
+ });
97
+ }
98
+
99
+ // src/react/provider.ts
100
+ var import_react2 = require("convex/react");
101
+ var import_react3 = require("react");
102
+ var CONVEX_URLS = {
103
+ local: "http://localhost:3210",
104
+ staging: "https://courteous-duck-623.convex.cloud",
105
+ sandbox: "https://courteous-duck-623.convex.cloud",
106
+ uat: "https://courteous-duck-623.convex.cloud",
107
+ production: "https://courteous-duck-623.convex.cloud"
108
+ };
109
+ function TruthProvider({
110
+ environment = "sandbox",
111
+ convexUrl,
112
+ children
113
+ }) {
114
+ var _a;
115
+ const url = (_a = convexUrl != null ? convexUrl : CONVEX_URLS[environment]) != null ? _a : CONVEX_URLS.sandbox;
116
+ const client = (0, import_react3.useMemo)(() => new import_react2.ConvexReactClient(url), [url]);
117
+ return (0, import_react3.createElement)(import_react2.ConvexProvider, { client }, children);
118
+ }
119
+
120
+ // src/react/tracking.ts
121
+ var import_react4 = require("react");
122
+
123
+ // src/tracking/tracker.ts
124
+ function generateUuidV7() {
125
+ const now = Date.now();
126
+ const timeBytes = new Uint8Array(6);
127
+ let ts = now;
128
+ for (let i = 5; i >= 0; i--) {
129
+ timeBytes[i] = ts & 255;
130
+ ts = Math.floor(ts / 256);
131
+ }
132
+ const randomBytes = new Uint8Array(10);
133
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
134
+ globalThis.crypto.getRandomValues(randomBytes);
135
+ } else {
136
+ for (let i = 0; i < 10; i++) {
137
+ randomBytes[i] = Math.floor(Math.random() * 256);
138
+ }
139
+ }
140
+ const bytes = new Uint8Array(16);
141
+ bytes.set(timeBytes, 0);
142
+ bytes.set(randomBytes, 6);
143
+ bytes[6] = bytes[6] & 15 | 112;
144
+ bytes[8] = bytes[8] & 63 | 128;
145
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
146
+ return [
147
+ hex.slice(0, 8),
148
+ hex.slice(8, 12),
149
+ hex.slice(12, 16),
150
+ hex.slice(16, 20),
151
+ hex.slice(20, 32)
152
+ ].join("-");
153
+ }
154
+ var API_URLS = {
155
+ local: "http://localhost:3000",
156
+ staging: "https://app.sandbox.communication-hub.com",
157
+ sandbox: "https://app.sandbox.communication-hub.com",
158
+ uat: "https://app.sandbox.communication-hub.com",
159
+ production: "https://app.truth.communication-hub.com"
160
+ };
161
+ var MAX_RETRIES = 3;
162
+ var BASE_RETRY_DELAY_MS = 500;
163
+ var Tracker = class {
164
+ constructor(config) {
165
+ this.queue = [];
166
+ this.flushTimer = null;
167
+ this.isFlushing = false;
168
+ this.isShutdown = false;
169
+ var _a, _b;
170
+ this.config = config;
171
+ this.apiUrl = (_b = (_a = config.apiBaseUrl) != null ? _a : API_URLS[config.environment]) != null ? _b : API_URLS.local;
172
+ this.startFlushInterval();
173
+ this.registerShutdownHooks();
174
+ }
175
+ /**
176
+ * Set the default actor context for subsequent events.
177
+ */
178
+ setActor(actor) {
179
+ this.defaultActor = actor;
180
+ }
181
+ /**
182
+ * Enqueue a typed event for delivery. This is fire-and-forget from
183
+ * the caller's perspective -- events are buffered and flushed in batches.
184
+ */
185
+ track(eventType, payload, options) {
186
+ var _a, _b, _c;
187
+ if (this.isShutdown) {
188
+ return;
189
+ }
190
+ const now = (/* @__PURE__ */ new Date()).toISOString();
191
+ const envelope = {
192
+ event_id: generateUuidV7(),
193
+ event_type: eventType,
194
+ schema_version: 1,
195
+ occurred_at: (_a = options == null ? void 0 : options.occurredAt) != null ? _a : now,
196
+ received_at: now,
197
+ source: this.config.source,
198
+ source_version: this.config.sourceVersion,
199
+ tenant_id: (_b = options == null ? void 0 : options.tenantId) != null ? _b : this.config.tenantId,
200
+ actor: (_c = options == null ? void 0 : options.actor) != null ? _c : this.defaultActor ? {
201
+ actor_id: this.defaultActor.actorId,
202
+ actor_type: this.defaultActor.actorType
203
+ } : void 0,
204
+ subject: options == null ? void 0 : options.subject,
205
+ compliance: options == null ? void 0 : options.compliance,
206
+ payload
207
+ };
208
+ this.queue.push(envelope);
209
+ if (this.queue.length >= this.config.batchSize) {
210
+ void this.flush();
211
+ }
212
+ }
213
+ /**
214
+ * Force an immediate flush of all buffered events.
215
+ * Returns a promise that resolves when the flush completes.
216
+ */
217
+ flush() {
218
+ return __async(this, null, function* () {
219
+ if (this.queue.length === 0 || this.isFlushing) {
220
+ return;
221
+ }
222
+ this.isFlushing = true;
223
+ const batch = this.queue.splice(0, this.config.batchSize);
224
+ try {
225
+ yield this.sendBatch(batch);
226
+ } catch (e) {
227
+ this.queue.unshift(...batch);
228
+ } finally {
229
+ this.isFlushing = false;
230
+ }
231
+ if (this.queue.length >= this.config.batchSize) {
232
+ yield this.flush();
233
+ }
234
+ });
235
+ }
236
+ /**
237
+ * Gracefully shut down the tracker. Flushes remaining events and
238
+ * clears the flush interval.
239
+ */
240
+ shutdown() {
241
+ return __async(this, null, function* () {
242
+ this.isShutdown = true;
243
+ this.stopFlushInterval();
244
+ yield this.flush();
245
+ });
246
+ }
247
+ /**
248
+ * Send a batch of events to the Truth API with exponential backoff retry.
249
+ */
250
+ sendBatch(batch) {
251
+ return __async(this, null, function* () {
252
+ let lastError;
253
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
254
+ try {
255
+ const response = yield fetch(`${this.apiUrl}/api/events/ingest`, {
256
+ method: "POST",
257
+ headers: {
258
+ "Content-Type": "application/json",
259
+ Authorization: `Bearer ${this.config.apiKey}`
260
+ },
261
+ body: JSON.stringify({ events: batch })
262
+ });
263
+ if (response.ok) {
264
+ return;
265
+ }
266
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
267
+ return;
268
+ }
269
+ lastError = new Error(
270
+ `HTTP ${response.status}: ${response.statusText}`
271
+ );
272
+ } catch (error) {
273
+ lastError = error;
274
+ }
275
+ if (attempt < MAX_RETRIES) {
276
+ const delay = BASE_RETRY_DELAY_MS * __pow(2, attempt);
277
+ const jitter = Math.random() * delay * 0.5;
278
+ yield sleep(delay + jitter);
279
+ }
280
+ }
281
+ throw lastError;
282
+ });
283
+ }
284
+ startFlushInterval() {
285
+ if (this.config.flushIntervalMs > 0) {
286
+ this.flushTimer = setInterval(() => {
287
+ void this.flush();
288
+ }, this.config.flushIntervalMs);
289
+ if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
290
+ this.flushTimer.unref();
291
+ }
292
+ }
293
+ }
294
+ stopFlushInterval() {
295
+ if (this.flushTimer !== null) {
296
+ clearInterval(this.flushTimer);
297
+ this.flushTimer = null;
298
+ }
299
+ }
300
+ registerShutdownHooks() {
301
+ if (typeof globalThis.process !== "undefined" && globalThis.process.on) {
302
+ const shutdownHandler = () => {
303
+ void this.shutdown();
304
+ };
305
+ globalThis.process.on("beforeExit", shutdownHandler);
306
+ globalThis.process.on("SIGTERM", shutdownHandler);
307
+ }
308
+ }
309
+ };
310
+ function sleep(ms) {
311
+ return new Promise((resolve) => setTimeout(resolve, ms));
312
+ }
313
+
314
+ // src/react/tracking.ts
315
+ var TruthTrackingContext = (0, import_react4.createContext)(
316
+ null
317
+ );
318
+ function TruthTrackingProvider({
319
+ environment = "sandbox",
320
+ source = "communication-hub.frontend",
321
+ sourceVersion = "unknown",
322
+ tenantId = "hipnation",
323
+ apiKey = "",
324
+ children
325
+ }) {
326
+ const value = (0, import_react4.useMemo)(() => {
327
+ const tracker = new Tracker({
328
+ apiKey,
329
+ environment,
330
+ source,
331
+ sourceVersion,
332
+ tenantId,
333
+ batchSize: 10,
334
+ flushIntervalMs: 5e3
335
+ });
336
+ return {
337
+ track: (eventType, payload, options) => {
338
+ tracker.track(eventType, payload, options);
339
+ },
340
+ identify: (actorId, actorType) => {
341
+ tracker.setActor({ actorId, actorType });
342
+ }
343
+ };
344
+ }, [apiKey, environment, source, sourceVersion, tenantId]);
345
+ return (0, import_react4.createElement)(TruthTrackingContext.Provider, { value }, children);
346
+ }
347
+ function useTruth() {
348
+ const ctx = (0, import_react4.useContext)(TruthTrackingContext);
349
+ if (!ctx) {
350
+ throw new Error("useTruth must be used within a TruthTrackingProvider");
351
+ }
352
+ return ctx;
353
+ }
354
+ // Annotate the CommonJS export names for ESM import in node:
355
+ 0 && (module.exports = {
356
+ TruthProvider,
357
+ TruthTrackingProvider,
358
+ useAppointment,
359
+ useAppointmentByElationId,
360
+ useAppointments,
361
+ usePatient,
362
+ usePatientByElationId,
363
+ usePatientByHintId,
364
+ usePatients,
365
+ useTruth
366
+ });
367
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react.ts","../src/react/hooks.ts","../src/react/provider.ts","../src/react/tracking.ts","../src/tracking/tracker.ts"],"sourcesContent":["/**\n * @hipnation-truth/sdk/react\n *\n * React hooks and provider for Truth SDK — real-time Convex-backed data.\n *\n * @example\n * ```tsx\n * import { TruthProvider, usePatients, useAppointments } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * return (\n * <TruthProvider environment=\"sandbox\">\n * <PatientList />\n * </TruthProvider>\n * );\n * }\n *\n * function PatientList() {\n * const patients = usePatients({ limit: 20 });\n * if (!patients) return <div>Loading...</div>;\n * return patients.map(p => <div key={p._id}>{p.firstName}</div>);\n * }\n * ```\n */\n\n// Re-export types consumers commonly need\nexport type {\n UseAppointmentListOptions,\n UsePatientListOptions,\n} from \"./react/hooks\";\n// Patient hooks\n// Appointment hooks\nexport {\n useAppointment,\n useAppointmentByElationId,\n useAppointments,\n usePatient,\n usePatientByElationId,\n usePatientByHintId,\n usePatients,\n} from \"./react/hooks\";\nexport type { TruthProviderProps } from \"./react/provider\";\n// Provider\nexport { TruthProvider } from \"./react/provider\";\nexport type {\n TruthTrackingContextValue,\n TruthTrackingProviderProps,\n} from \"./react/tracking\";\n// Tracking\nexport { TruthTrackingProvider, useTruth } from \"./react/tracking\";\n// Re-export event types for track() calls\nexport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./tracking/events\";\nexport type { Appointment, AppointmentListOptions } from \"./types/appointment\";\nexport type { Patient, PatientListOptions } from \"./types/patient\";\n","/**\n * React hooks for Truth SDK — real-time Convex-backed data access.\n *\n * These hooks use Convex React subscriptions for live-updating data.\n * Must be used within a ConvexProvider (see TruthProvider).\n *\n * @example\n * ```tsx\n * import { usePatients, useAppointments } from '@hipnation-truth/sdk/react';\n *\n * function PatientList() {\n * const patients = usePatients({ limit: 20 });\n * return patients?.map(p => <div key={p._id}>{p.firstName} {p.lastName}</div>);\n * }\n * ```\n */\n\n\"use client\";\n\nimport { useQuery } from \"convex/react\";\n\n// ---------------------------------------------------------------------------\n// Convex function references\n// ---------------------------------------------------------------------------\n// We use string-based references to avoid importing the generated API\n// (which lives in the consuming app, not the SDK). Convex supports this\n// via the `api` module pattern, but for a published SDK we use\n// makeFunctionReference to stay decoupled from the app's codegen.\n\nimport type { FunctionReference } from \"convex/server\";\nimport { makeFunctionReference } from \"convex/server\";\n\n// Patient query references\nconst patientsListRef = makeFunctionReference<\n \"query\",\n {\n search?: string;\n lastName?: string;\n cursor?: string;\n limit?: number;\n },\n unknown[]\n>(\"patients:list\");\n\nconst patientsGetRef = makeFunctionReference<\n \"query\",\n { id: string },\n unknown | null\n>(\"patients:get\");\n\nconst patientsByElationIdRef = makeFunctionReference<\n \"query\",\n { elationId: string },\n unknown | null\n>(\"patients:getByElationId\");\n\nconst patientsByHintIdRef = makeFunctionReference<\n \"query\",\n { hintId: string },\n unknown | null\n>(\"patients:getByHintId\");\n\n// Appointment query references\nconst appointmentsListRef = makeFunctionReference<\n \"query\",\n {\n patientId?: string;\n status?: string;\n startDate?: string;\n endDate?: string;\n limit?: number;\n },\n unknown[]\n>(\"appointments:list\");\n\nconst appointmentsGetRef = makeFunctionReference<\n \"query\",\n { id: string },\n unknown | null\n>(\"appointments:get\");\n\nconst appointmentsByElationIdRef = makeFunctionReference<\n \"query\",\n { elationId: string },\n unknown | null\n>(\"appointments:getByElationId\");\n\n// ---------------------------------------------------------------------------\n// Patient hooks\n// ---------------------------------------------------------------------------\n\ninterface UsePatientListOptions {\n search?: string;\n lastName?: string;\n limit?: number;\n}\n\n/**\n * Subscribe to a list of patients with optional search and filtering.\n * Returns undefined while loading, then an array of patients.\n */\nfunction usePatients(options?: UsePatientListOptions) {\n return useQuery(patientsListRef as FunctionReference<\"query\">, options ?? {});\n}\n\n/**\n * Subscribe to a single patient by Convex document ID.\n */\nfunction usePatient(id: string) {\n return useQuery(patientsGetRef as FunctionReference<\"query\">, { id });\n}\n\n/**\n * Subscribe to a patient by their Elation ID.\n */\nfunction usePatientByElationId(elationId: string) {\n return useQuery(patientsByElationIdRef as FunctionReference<\"query\">, {\n elationId,\n });\n}\n\n/**\n * Subscribe to a patient by their Hint ID.\n */\nfunction usePatientByHintId(hintId: string) {\n return useQuery(patientsByHintIdRef as FunctionReference<\"query\">, {\n hintId,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Appointment hooks\n// ---------------------------------------------------------------------------\n\ninterface UseAppointmentListOptions {\n patientId?: string;\n status?: string;\n startDate?: string;\n endDate?: string;\n limit?: number;\n}\n\n/**\n * Subscribe to a list of appointments with optional filtering.\n */\nfunction useAppointments(options?: UseAppointmentListOptions) {\n return useQuery(\n appointmentsListRef as FunctionReference<\"query\">,\n options ?? {},\n );\n}\n\n/**\n * Subscribe to a single appointment by Convex document ID.\n */\nfunction useAppointment(id: string) {\n return useQuery(appointmentsGetRef as FunctionReference<\"query\">, { id });\n}\n\n/**\n * Subscribe to an appointment by its Elation ID.\n */\nfunction useAppointmentByElationId(elationId: string) {\n return useQuery(appointmentsByElationIdRef as FunctionReference<\"query\">, {\n elationId,\n });\n}\n\nexport {\n usePatients,\n usePatient,\n usePatientByElationId,\n usePatientByHintId,\n useAppointments,\n useAppointment,\n useAppointmentByElationId,\n};\nexport type { UsePatientListOptions, UseAppointmentListOptions };\n","/**\n * TruthProvider — Convex React provider pre-configured for Truth.\n *\n * Wraps children in a ConvexProvider connected to the correct\n * Truth Convex deployment for the given environment.\n *\n * @example\n * ```tsx\n * import { TruthProvider } from '@hipnation-truth/sdk/react';\n *\n * function App() {\n * return (\n * <TruthProvider environment=\"sandbox\">\n * <MyApp />\n * </TruthProvider>\n * );\n * }\n * ```\n */\n\n\"use client\";\n\nimport { ConvexProvider, ConvexReactClient } from \"convex/react\";\nimport type { ReactNode } from \"react\";\nimport { createElement, useMemo } from \"react\";\n\nconst CONVEX_URLS: Record<string, string> = {\n local: \"http://localhost:3210\",\n staging: \"https://courteous-duck-623.convex.cloud\",\n sandbox: \"https://courteous-duck-623.convex.cloud\",\n uat: \"https://courteous-duck-623.convex.cloud\",\n production: \"https://courteous-duck-623.convex.cloud\",\n};\n\ninterface TruthProviderProps {\n /** Truth environment — determines which Convex deployment to connect to */\n environment?: string;\n /** Override the Convex URL directly */\n convexUrl?: string;\n children: ReactNode;\n}\n\nfunction TruthProvider({\n environment = \"sandbox\",\n convexUrl,\n children,\n}: TruthProviderProps) {\n const url = convexUrl ?? CONVEX_URLS[environment] ?? CONVEX_URLS.sandbox;\n\n const client = useMemo(() => new ConvexReactClient(url), [url]);\n\n return createElement(ConvexProvider, { client }, children);\n}\n\nexport { TruthProvider };\nexport type { TruthProviderProps };\n","/**\n * React tracking hook — fire-and-forget event tracking from client components.\n *\n * Creates a lightweight Tracker instance scoped to the browser session\n * and exposes a useTrack() hook for emitting events to Truth's Kinesis stream.\n *\n * @example\n * ```tsx\n * import { useTruth } from '@hipnation-truth/sdk/react';\n *\n * function ConversationView({ conversationId }) {\n * const truth = useTruth();\n *\n * useEffect(() => {\n * truth.track('conversation.marked_read.v1', {\n * read_by_actor_id: userId,\n * unread_count_before: 5,\n * unread_count_after: 0,\n * }, { subject: { conversation_id: conversationId } });\n * }, [conversationId]);\n * }\n * ```\n */\n\n\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { createContext, createElement, useContext, useMemo } from \"react\";\nimport type {\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"../tracking/events\";\nimport { Tracker } from \"../tracking/tracker\";\nimport type { ActorContext } from \"../types/config\";\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ninterface TruthTrackingContextValue {\n track: <T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ) => void;\n identify: (actorId: string, actorType: ActorContext[\"actorType\"]) => void;\n}\n\nconst TruthTrackingContext = createContext<TruthTrackingContextValue | null>(\n null,\n);\n\n// ---------------------------------------------------------------------------\n// Provider\n// ---------------------------------------------------------------------------\n\ninterface TruthTrackingProviderProps {\n /** Truth environment — determines API URL for event delivery */\n environment?: string;\n /** Event source identifier */\n source?: string;\n /** Source version (git SHA) */\n sourceVersion?: string;\n /** Default tenant ID */\n tenantId?: string;\n /** API key for authentication */\n apiKey?: string;\n children: ReactNode;\n}\n\nfunction TruthTrackingProvider({\n environment = \"sandbox\",\n source = \"communication-hub.frontend\",\n sourceVersion = \"unknown\",\n tenantId = \"hipnation\",\n apiKey = \"\",\n children,\n}: TruthTrackingProviderProps) {\n const value = useMemo<TruthTrackingContextValue>(() => {\n const tracker = new Tracker({\n apiKey,\n environment,\n source,\n sourceVersion,\n tenantId,\n batchSize: 10,\n flushIntervalMs: 5000,\n });\n\n return {\n track: (eventType, payload, options) => {\n tracker.track(eventType, payload, options);\n },\n identify: (actorId, actorType) => {\n tracker.setActor({ actorId, actorType });\n },\n };\n }, [apiKey, environment, source, sourceVersion, tenantId]);\n\n return createElement(TruthTrackingContext.Provider, { value }, children);\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Access the Truth tracking context. Must be within a TruthTrackingProvider.\n * Returns `{ track, identify }` for emitting events to Kinesis.\n */\nfunction useTruth(): TruthTrackingContextValue {\n const ctx = useContext(TruthTrackingContext);\n if (!ctx) {\n throw new Error(\"useTruth must be used within a TruthTrackingProvider\");\n }\n return ctx;\n}\n\nexport { TruthTrackingProvider, useTruth };\nexport type { TruthTrackingProviderProps, TruthTrackingContextValue };\n","/**\n * Event tracker with batching, retry, and flush support.\n *\n * Buffers events in an internal queue and flushes them to the Truth API\n * endpoint. Flushes occur when the buffer reaches `batchSize` or every\n * `flushIntervalMs` milliseconds, whichever comes first.\n */\n\nimport type { ActorContext } from \"../types/config\";\nimport type {\n EventEnvelope,\n EventPayloadMap,\n EventType,\n TrackOptions,\n} from \"./events\";\n\n// ---------------------------------------------------------------------------\n// UUID v7 helper (no external dependency)\n// ---------------------------------------------------------------------------\n\n/**\n * Generates a UUID v7 string. Uses crypto.randomUUID where available,\n * falling back to a timestamp + random bytes implementation.\n *\n * UUID v7 layout (RFC 9562):\n * 48 bits - unix timestamp (ms)\n * 4 bits - version (0b0111)\n * 12 bits - random\n * 2 bits - variant (0b10)\n * 62 bits - random\n */\nfunction generateUuidV7(): string {\n const now = Date.now();\n\n // 6 bytes of timestamp\n const timeBytes = new Uint8Array(6);\n let ts = now;\n for (let i = 5; i >= 0; i--) {\n timeBytes[i] = ts & 0xff;\n ts = Math.floor(ts / 256);\n }\n\n // 10 bytes of random\n const randomBytes = new Uint8Array(10);\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n globalThis.crypto.getRandomValues\n ) {\n globalThis.crypto.getRandomValues(randomBytes);\n } else {\n for (let i = 0; i < 10; i++) {\n randomBytes[i] = Math.floor(Math.random() * 256);\n }\n }\n\n // Assemble 16 bytes\n const bytes = new Uint8Array(16);\n bytes.set(timeBytes, 0);\n bytes.set(randomBytes, 6);\n\n // Set version (bits 48-51 to 0b0111)\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n\n // Set variant (bits 64-65 to 0b10)\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n // Format as hex string with dashes\n const hex = Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32),\n ].join(\"-\");\n}\n\n// ---------------------------------------------------------------------------\n// Environment-based API URL resolution\n// ---------------------------------------------------------------------------\n\nconst API_URLS: Record<string, string> = {\n local: \"http://localhost:3000\",\n staging: \"https://app.sandbox.communication-hub.com\",\n sandbox: \"https://app.sandbox.communication-hub.com\",\n uat: \"https://app.sandbox.communication-hub.com\",\n production: \"https://app.truth.communication-hub.com\",\n};\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_BATCH_SIZE = 25;\nconst DEFAULT_FLUSH_INTERVAL_MS = 5_000;\nconst MAX_RETRIES = 3;\nconst BASE_RETRY_DELAY_MS = 500;\n\n// ---------------------------------------------------------------------------\n// Tracker configuration\n// ---------------------------------------------------------------------------\n\ninterface TrackerConfig {\n apiKey: string;\n environment: string;\n source: string;\n sourceVersion: string;\n tenantId: string;\n batchSize: number;\n flushIntervalMs: number;\n apiBaseUrl?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tracker class\n// ---------------------------------------------------------------------------\n\nclass Tracker {\n private readonly config: TrackerConfig;\n readonly apiUrl: string;\n private queue: EventEnvelope[] = [];\n private flushTimer: ReturnType<typeof setInterval> | null = null;\n private defaultActor: ActorContext | undefined;\n private isFlushing = false;\n private isShutdown = false;\n\n constructor(config: TrackerConfig) {\n this.config = config;\n this.apiUrl =\n config.apiBaseUrl ?? API_URLS[config.environment] ?? API_URLS.local;\n\n this.startFlushInterval();\n this.registerShutdownHooks();\n }\n\n /**\n * Set the default actor context for subsequent events.\n */\n setActor(actor: ActorContext): void {\n this.defaultActor = actor;\n }\n\n /**\n * Enqueue a typed event for delivery. This is fire-and-forget from\n * the caller's perspective -- events are buffered and flushed in batches.\n */\n track<T extends EventType>(\n eventType: T,\n payload: EventPayloadMap[T],\n options?: TrackOptions,\n ): void {\n if (this.isShutdown) {\n return;\n }\n\n const now = new Date().toISOString();\n\n const envelope: EventEnvelope = {\n event_id: generateUuidV7(),\n event_type: eventType,\n schema_version: 1,\n occurred_at: options?.occurredAt ?? now,\n received_at: now,\n source: this.config.source,\n source_version: this.config.sourceVersion,\n tenant_id: options?.tenantId ?? this.config.tenantId,\n actor:\n options?.actor ??\n (this.defaultActor\n ? {\n actor_id: this.defaultActor.actorId,\n actor_type: this.defaultActor.actorType,\n }\n : undefined),\n subject: options?.subject,\n compliance: options?.compliance,\n payload: payload as unknown as Record<string, unknown>,\n };\n\n this.queue.push(envelope);\n\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Force an immediate flush of all buffered events.\n * Returns a promise that resolves when the flush completes.\n */\n async flush(): Promise<void> {\n if (this.queue.length === 0 || this.isFlushing) {\n return;\n }\n\n this.isFlushing = true;\n const batch = this.queue.splice(0, this.config.batchSize);\n\n try {\n await this.sendBatch(batch);\n } catch {\n // Re-queue events that failed to send (prepend to maintain ordering)\n this.queue.unshift(...batch);\n } finally {\n this.isFlushing = false;\n }\n\n // If there are still events in the queue, flush again\n if (this.queue.length >= this.config.batchSize) {\n await this.flush();\n }\n }\n\n /**\n * Gracefully shut down the tracker. Flushes remaining events and\n * clears the flush interval.\n */\n async shutdown(): Promise<void> {\n this.isShutdown = true;\n this.stopFlushInterval();\n await this.flush();\n }\n\n /**\n * Send a batch of events to the Truth API with exponential backoff retry.\n */\n private async sendBatch(batch: EventEnvelope[]): Promise<void> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {\n try {\n const response = await fetch(`${this.apiUrl}/api/events/ingest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({ events: batch }),\n });\n\n if (response.ok) {\n return;\n }\n\n // Don't retry 4xx client errors (except 429)\n if (\n response.status >= 400 &&\n response.status < 500 &&\n response.status !== 429\n ) {\n return;\n }\n\n lastError = new Error(\n `HTTP ${response.status}: ${response.statusText}`,\n );\n } catch (error) {\n lastError = error;\n }\n\n // Exponential backoff with jitter\n if (attempt < MAX_RETRIES) {\n const delay = BASE_RETRY_DELAY_MS * 2 ** attempt;\n const jitter = Math.random() * delay * 0.5;\n await sleep(delay + jitter);\n }\n }\n\n throw lastError;\n }\n\n private startFlushInterval(): void {\n if (this.config.flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush();\n }, this.config.flushIntervalMs);\n\n // Unref the timer so it doesn't prevent Node.js from exiting\n if (typeof this.flushTimer === \"object\" && \"unref\" in this.flushTimer) {\n this.flushTimer.unref();\n }\n }\n }\n\n private stopFlushInterval(): void {\n if (this.flushTimer !== null) {\n clearInterval(this.flushTimer);\n this.flushTimer = null;\n }\n }\n\n private registerShutdownHooks(): void {\n // Only register in Node.js environments\n if (typeof globalThis.process !== \"undefined\" && globalThis.process.on) {\n const shutdownHandler = () => {\n void this.shutdown();\n };\n\n globalThis.process.on(\"beforeExit\", shutdownHandler);\n globalThis.process.on(\"SIGTERM\", shutdownHandler);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport {\n Tracker,\n generateUuidV7,\n DEFAULT_BATCH_SIZE,\n DEFAULT_FLUSH_INTERVAL_MS,\n};\nexport type { TrackerConfig };\n"],"mappings":"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","names":["import_react","import_react"]}