@hipnation-truth/sdk 0.17.1 → 0.18.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 CHANGED
@@ -57,12 +57,17 @@ var __async = (__this, __arguments, generator) => {
57
57
  var react_exports = {};
58
58
  __export(react_exports, {
59
59
  ACTIVE_CALL_STATES: () => ACTIVE_CALL_STATES,
60
+ API_BASE_URLS: () => API_BASE_URLS,
60
61
  CONNECTED_CALL_STATES: () => CONNECTED_CALL_STATES,
62
+ CONVEX_URLS: () => CONVEX_URLS2,
61
63
  DialpadCallState: () => DialpadCallState,
62
64
  RINGING_CALL_STATES: () => RINGING_CALL_STATES,
63
65
  TERMINAL_CALL_STATES: () => TERMINAL_CALL_STATES,
64
66
  TruthProvider: () => TruthProvider,
65
67
  TruthTrackingProvider: () => TruthTrackingProvider,
68
+ getTruthClient: () => getTruthClient,
69
+ resolveApiBaseUrl: () => resolveApiBaseUrl,
70
+ resolveConvexUrl: () => resolveConvexUrl,
66
71
  useActiveCalls: () => useActiveCalls,
67
72
  useAppointment: () => useAppointment,
68
73
  useAppointmentByElationId: () => useAppointmentByElationId,
@@ -82,6 +87,7 @@ __export(react_exports, {
82
87
  useDialpadCallsForConversation: () => useDialpadCallsForConversation,
83
88
  useMessages: () => useMessages,
84
89
  useNotifications: () => useNotifications,
90
+ useNotificationsActions: () => useNotificationsActions,
85
91
  usePatient: () => usePatient,
86
92
  usePatientBasic: () => usePatientBasic,
87
93
  usePatientByElationId: () => usePatientByElationId,
@@ -98,9 +104,12 @@ __export(react_exports, {
98
104
  usePhysiciansByElationIds: () => usePhysiciansByElationIds,
99
105
  useRemindersForConversations: () => useRemindersForConversations,
100
106
  useTruth: () => useTruth,
107
+ useTruthClient: () => useTruthClient,
108
+ useTruthSdkContext: () => useTruthSdkContext,
101
109
  useUnreadAggregate: () => useUnreadAggregate,
102
110
  useUnreadCount: () => useUnreadCount,
103
111
  useUserSettings: () => useUserSettings,
112
+ useUserSync: () => useUserSync,
104
113
  useVoicemailUrl: () => useVoicemailUrl
105
114
  });
106
115
  module.exports = __toCommonJS(react_exports);
@@ -149,10 +158,7 @@ function toResult(value, skipped) {
149
158
  };
150
159
  }
151
160
  function useActiveCalls(options) {
152
- const result = (0, import_react.useQuery)(
153
- listActiveRef,
154
- options != null ? options : {}
155
- );
161
+ const result = (0, import_react.useQuery)(listActiveRef, options != null ? options : {});
156
162
  return toResult(result, false);
157
163
  }
158
164
  function useDialpadCallsForConversation(conversationId, options) {
@@ -294,7 +300,7 @@ function useUnreadAggregate(userId, options) {
294
300
  function useMemoizedPhones(phones) {
295
301
  const key = phones ? [...phones].sort().join("|") : "";
296
302
  return (0, import_react4.useMemo)(
297
- () => phones && phones.length ? [...phones].sort() : void 0,
303
+ () => (phones == null ? void 0 : phones.length) ? [...phones].sort() : void 0,
298
304
  // eslint-disable-next-line react-hooks/exhaustive-deps
299
305
  [key]
300
306
  );
@@ -341,318 +347,2356 @@ function useConversationTasksByPhonePair(phonePair) {
341
347
  }
342
348
 
343
349
  // src/react/hooks.ts
350
+ var import_react7 = require("convex/react");
351
+ var import_react8 = require("react");
352
+
353
+ // src/react/provider.ts
344
354
  var import_react5 = require("convex/react");
345
355
  var import_react6 = require("react");
346
- var import_server4 = require("convex/server");
347
- var patientsListRef = (0, import_server4.makeFunctionReference)("patients:list");
348
- var patientsGetRef = (0, import_server4.makeFunctionReference)("patients:get");
349
- var patientsByElationIdRef = (0, import_server4.makeFunctionReference)("patients:getByElationId");
350
- var patientsByHintIdRef = (0, import_server4.makeFunctionReference)("patients:getByHintId");
351
- var appointmentsListRef = (0, import_server4.makeFunctionReference)("appointments:list");
352
- var appointmentsGetRef = (0, import_server4.makeFunctionReference)("appointments:get");
353
- var appointmentsByElationIdRef = (0, import_server4.makeFunctionReference)("appointments:getByElationId");
354
- function usePatients(options) {
355
- return (0, import_react5.useQuery)(patientsListRef, options != null ? options : {});
356
- }
357
- function usePatient(id) {
358
- return (0, import_react5.useQuery)(patientsGetRef, { id });
359
- }
360
- function usePatientByElationId(elationId) {
361
- return (0, import_react5.useQuery)(patientsByElationIdRef, {
362
- elationId
363
- });
364
- }
365
- function usePatientByHintId(hintId) {
366
- return (0, import_react5.useQuery)(patientsByHintIdRef, {
367
- hintId
368
- });
369
- }
370
- function useAppointments(options) {
371
- return (0, import_react5.useQuery)(
372
- appointmentsListRef,
373
- options != null ? options : {}
374
- );
375
- }
376
- function useAppointment(id) {
377
- return (0, import_react5.useQuery)(appointmentsGetRef, { id });
378
- }
379
- function useAppointmentByElationId(elationId) {
380
- return (0, import_react5.useQuery)(appointmentsByElationIdRef, {
381
- elationId
382
- });
383
- }
384
- var physiciansGetByElationIdsRef = (0, import_server4.makeFunctionReference)("physicians:getByElationIds");
385
- var physiciansGetByElationIdRef = (0, import_server4.makeFunctionReference)("physicians:getByElationId");
386
- function usePhysiciansByElationIds(ids) {
387
- return (0, import_react5.useQuery)(
388
- physiciansGetByElationIdsRef,
389
- ids && ids.length > 0 ? { ids } : "skip"
390
- );
391
- }
392
- function usePhysicianByElationId(id) {
393
- return (0, import_react5.useQuery)(
394
- physiciansGetByElationIdRef,
395
- id !== void 0 ? { id } : "skip"
396
- );
397
- }
398
- var medicationsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getMedicationsByElationPatient");
399
- var problemsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getProblemsByElationPatient");
400
- var allergiesByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getAllergiesByElationPatient");
401
- var appointmentsByPatientRef = (0, import_server4.makeFunctionReference)("medicalRecords:getAppointmentsByElationPatient");
402
- function usePatientMedical(elationId, options) {
403
- const medications = (0, import_react5.useQuery)(
404
- medicationsByPatientRef,
405
- elationId !== void 0 ? { elationPatientId: elationId } : "skip"
406
- );
407
- const problems = (0, import_react5.useQuery)(
408
- problemsByPatientRef,
409
- elationId !== void 0 ? { elationPatientId: elationId } : "skip"
410
- );
411
- const allergies = (0, import_react5.useQuery)(
412
- allergiesByPatientRef,
413
- elationId !== void 0 ? { elationPatientId: elationId } : "skip"
414
- );
415
- const appointments = (0, import_react5.useQuery)(
416
- appointmentsByPatientRef,
417
- elationId !== void 0 ? { elationPatientId: elationId } : "skip"
418
- );
419
- (0, import_react6.useEffect)(() => {
420
- if (elationId === void 0 || (options == null ? void 0 : options.skipRefresh)) {
421
- return;
422
- }
423
- const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
424
- const apiKey = options == null ? void 0 : options.apiKey;
425
- if (!apiBaseUrl || !apiKey) {
426
- return;
427
- }
428
- const controller = new AbortController();
429
- void fetch(`${apiBaseUrl}/api/patients/medical/refresh`, {
430
- method: "POST",
431
- headers: {
432
- "Content-Type": "application/json",
433
- "X-API-Key": apiKey
434
- },
435
- body: JSON.stringify({ elationId }),
436
- signal: controller.signal
437
- }).catch(() => {
438
- });
439
- return () => controller.abort();
440
- }, [elationId, options == null ? void 0 : options.apiBaseUrl, options == null ? void 0 : options.apiKey, options == null ? void 0 : options.skipRefresh]);
441
- return { medications, problems, allergies, appointments };
442
- }
443
- var elationPatientByIdRef = (0, import_server4.makeFunctionReference)("elationPatients:getByElationId");
444
- var hintPatientByIdRef = (0, import_server4.makeFunctionReference)("hintPatients:getByHintId");
445
- var pharmacyByNcpdpRef = (0, import_server4.makeFunctionReference)("elationPharmacies:getByNcpdpId");
446
- var patientPhotoByIdRef = (0, import_server4.makeFunctionReference)("elationPatientPhotos:getByElationPatientId");
447
- function usePatientBasic(input, options) {
448
- var _a, _b;
449
- const elationRow = (0, import_react5.useQuery)(
450
- elationPatientByIdRef,
451
- input.elationId !== void 0 ? { elationId: input.elationId } : "skip"
452
- );
453
- const hintRow = (0, import_react5.useQuery)(
454
- hintPatientByIdRef,
455
- input.hintId !== void 0 ? { hintId: input.hintId } : "skip"
456
- );
457
- (0, import_react6.useEffect)(() => {
458
- if (options == null ? void 0 : options.skipRefresh) {
459
- return;
460
- }
461
- if (!input.hintId && input.elationId === void 0) {
462
- return;
463
- }
464
- const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
465
- const apiKey = options == null ? void 0 : options.apiKey;
466
- if (!apiBaseUrl || !apiKey) {
467
- return;
468
- }
469
- const controller = new AbortController();
470
- void fetch(`${apiBaseUrl}/api/patients/basic/refresh`, {
471
- method: "POST",
472
- headers: {
473
- "Content-Type": "application/json",
474
- "X-API-Key": apiKey
475
- },
476
- body: JSON.stringify({
477
- hintId: input.hintId,
478
- elationId: input.elationId
479
- }),
480
- signal: controller.signal
481
- }).catch(() => {
482
- });
483
- return () => controller.abort();
484
- }, [
485
- input.hintId,
486
- input.elationId,
487
- options == null ? void 0 : options.apiBaseUrl,
488
- options == null ? void 0 : options.apiKey,
489
- options == null ? void 0 : options.skipRefresh
490
- ]);
491
- const elationPatient = elationRow === void 0 ? void 0 : elationRow === null ? null : (_a = elationRow.raw) != null ? _a : null;
492
- const hintPatient = hintRow === void 0 ? void 0 : hintRow === null ? null : (_b = hintRow.raw) != null ? _b : null;
493
- const elationLoading = input.elationId !== void 0 && elationRow === void 0;
494
- const hintLoading = input.hintId !== void 0 && hintRow === void 0;
495
- return {
496
- elationPatient,
497
- hintPatient,
498
- elationRow,
499
- hintRow,
500
- loading: elationLoading || hintLoading
501
- };
502
- }
503
- function usePharmacyByNcpdpId(ncpdpId) {
504
- return (0, import_react5.useQuery)(
505
- pharmacyByNcpdpRef,
506
- ncpdpId ? { ncpdpId } : "skip"
507
- );
508
- }
509
- function usePatientPhoto(elationId, options) {
510
- const photo = (0, import_react5.useQuery)(
511
- patientPhotoByIdRef,
512
- elationId !== void 0 ? { elationPatientId: elationId } : "skip"
513
- );
514
- (0, import_react6.useEffect)(() => {
515
- if (options == null ? void 0 : options.skipRefresh) {
516
- return;
517
- }
518
- if (elationId === void 0) {
519
- return;
520
- }
521
- const apiBaseUrl = options == null ? void 0 : options.apiBaseUrl;
522
- const apiKey = options == null ? void 0 : options.apiKey;
523
- if (!apiBaseUrl || !apiKey) {
524
- return;
525
- }
526
- const controller = new AbortController();
527
- void fetch(`${apiBaseUrl}/api/patients/photo/refresh`, {
528
- method: "POST",
529
- headers: {
530
- "Content-Type": "application/json",
531
- "X-API-Key": apiKey
532
- },
533
- body: JSON.stringify({ elationId }),
534
- signal: controller.signal
535
- }).catch(() => {
536
- });
537
- return () => controller.abort();
538
- }, [elationId, options == null ? void 0 : options.apiBaseUrl, options == null ? void 0 : options.apiKey, options == null ? void 0 : options.skipRefresh]);
539
- return photo;
540
- }
541
- var messagesByPhonesRef = (0, import_server4.makeFunctionReference)("conversationMessages:getByPhones");
542
- var messagesByConversationIdRef = (0, import_server4.makeFunctionReference)("conversationMessages:getByConversationId");
543
- function useConversationMessages(input, options) {
544
- const hasPair = !!input.phoneA && !!input.phoneB;
545
- const byPair = (0, import_react5.useQuery)(
546
- messagesByPhonesRef,
547
- hasPair ? {
548
- phoneA: input.phoneA,
549
- phoneB: input.phoneB,
550
- limit: options == null ? void 0 : options.limit
551
- } : "skip"
552
- );
553
- const byConvo = (0, import_react5.useQuery)(
554
- messagesByConversationIdRef,
555
- !hasPair && input.conversationId ? { conversationId: input.conversationId, limit: options == null ? void 0 : options.limit } : "skip"
556
- );
557
- return hasPair ? byPair : byConvo;
558
- }
559
356
 
560
- // src/react/notifications.ts
561
- var import_react7 = require("react");
357
+ // src/client.ts
358
+ var import_browser = require("convex/browser");
562
359
 
563
- // src/web-push.ts
564
- function isWebPushSupported() {
565
- return typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window;
566
- }
567
- function registerServiceWorker(path = "/truth-sw.js") {
568
- return __async(this, null, function* () {
569
- return navigator.serviceWorker.register(path);
570
- });
571
- }
572
- function subscribeToPush(registration, vapidPublicKey) {
573
- return __async(this, null, function* () {
574
- const existing = yield registration.pushManager.getSubscription();
575
- if (existing) {
576
- return existing;
577
- }
578
- return registration.pushManager.subscribe({
579
- userVisibleOnly: true,
580
- applicationServerKey: urlBase64ToUint8Array(
581
- vapidPublicKey
582
- )
583
- });
584
- });
585
- }
586
- function subscriptionToJSON(sub) {
587
- var _a, _b, _c, _d;
588
- const json = sub.toJSON();
589
- return {
590
- endpoint: sub.endpoint,
591
- keys: {
592
- p256dh: (_b = (_a = json.keys) == null ? void 0 : _a.p256dh) != null ? _b : "",
593
- auth: (_d = (_c = json.keys) == null ? void 0 : _c.auth) != null ? _d : ""
594
- }
595
- };
596
- }
597
- function urlBase64ToUint8Array(base64String) {
598
- const padding = "=".repeat((4 - base64String.length % 4) % 4);
599
- const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
600
- const rawData = atob(base64);
601
- const outputArray = new Uint8Array(rawData.length);
602
- for (let i = 0; i < rawData.length; ++i) {
603
- outputArray[i] = rawData.charCodeAt(i);
360
+ // src/resources/appointments.ts
361
+ var AppointmentResource = class {
362
+ constructor(convexClient) {
363
+ this.convex = convexClient;
604
364
  }
605
- return outputArray;
606
- }
607
-
608
- // src/react/notifications.ts
609
- function loadExpo() {
610
- return __async(this, null, function* () {
611
- try {
612
- return require("expo-notifications");
613
- } catch (e) {
614
- return null;
615
- }
616
- });
617
- }
618
- function useNotifications(options) {
619
- var _a;
620
- const [permissionStatus, setPermissionStatus] = (0, import_react7.useState)("unknown");
621
- const [devicePushToken, setDevicePushToken] = (0, import_react7.useState)(null);
622
- const expoRef = (0, import_react7.useRef)(null);
623
- const isWebRef = (0, import_react7.useRef)(false);
624
- const vapidKeyRef = (0, import_react7.useRef)((_a = options.vapidPublicKey) != null ? _a : null);
625
- (0, import_react7.useEffect)(() => {
626
- let mounted = true;
627
- void (() => __async(null, null, function* () {
628
- var _a2;
629
- const expo = yield loadExpo();
630
- if (!mounted) {
631
- return;
365
+ /**
366
+ * Get an appointment by its Truth platform ID.
367
+ */
368
+ get(id) {
369
+ return __async(this, null, function* () {
370
+ try {
371
+ const result = yield this.convex.query(
372
+ "appointments:getById",
373
+ { id }
374
+ );
375
+ return result != null ? result : null;
376
+ } catch (e) {
377
+ return null;
632
378
  }
633
- expoRef.current = expo;
634
- if (expo) {
635
- try {
636
- const perm = yield expo.getPermissionsAsync();
637
- if (!mounted) {
638
- return;
379
+ });
380
+ }
381
+ /**
382
+ * List appointments with optional filters, pagination, and limit.
383
+ */
384
+ list(options) {
385
+ return __async(this, null, function* () {
386
+ try {
387
+ const result = yield this.convex.query(
388
+ "appointments:list",
389
+ {
390
+ patientId: options == null ? void 0 : options.patientId,
391
+ startDate: options == null ? void 0 : options.startDate,
392
+ endDate: options == null ? void 0 : options.endDate,
393
+ status: options == null ? void 0 : options.status,
394
+ limit: options == null ? void 0 : options.limit,
395
+ cursor: options == null ? void 0 : options.cursor
639
396
  }
640
- setPermissionStatus(mapStatus(perm == null ? void 0 : perm.status));
641
- } catch (e) {
642
- setPermissionStatus("unknown");
643
- }
644
- return;
397
+ );
398
+ const typed = result;
399
+ return typed != null ? typed : { data: [], cursor: null, hasMore: false };
400
+ } catch (e) {
401
+ return { data: [], cursor: null, hasMore: false };
645
402
  }
646
- if (isWebPushSupported()) {
403
+ });
404
+ }
405
+ };
406
+
407
+ // src/resources/attachments.ts
408
+ var AttachmentsError = class extends Error {
409
+ constructor(operation, status, message) {
410
+ super(message != null ? message : `Attachment ${operation} failed (HTTP ${status})`);
411
+ this.name = "AttachmentsError";
412
+ this.status = status;
413
+ }
414
+ };
415
+ var AttachmentsResource = class {
416
+ constructor(apiBaseUrl, apiKey, convexClient) {
417
+ this.baseUrl = apiBaseUrl;
418
+ this.apiKey = apiKey;
419
+ this.convex = convexClient;
420
+ }
421
+ post(path, body) {
422
+ return __async(this, null, function* () {
423
+ const res = yield fetch(`${this.baseUrl}/api${path}`, {
424
+ method: "POST",
425
+ headers: {
426
+ "Content-Type": "application/json",
427
+ Accept: "application/json",
428
+ "X-API-Key": this.apiKey
429
+ },
430
+ body: JSON.stringify(body)
431
+ });
432
+ if (!res.ok) {
433
+ const text = yield res.text().catch(() => "");
434
+ throw new AttachmentsError(path, res.status, text.slice(0, 200));
435
+ }
436
+ return yield res.json();
437
+ });
438
+ }
439
+ createUploadUrl(input) {
440
+ return __async(this, null, function* () {
441
+ return yield this.post(
442
+ "/attachments/upload-url",
443
+ input
444
+ );
445
+ });
446
+ }
447
+ getDownloadUrl(s3Key, expiresIn) {
448
+ return __async(this, null, function* () {
449
+ return yield this.post("/attachments/download-url", {
450
+ s3Key,
451
+ expiresIn
452
+ });
453
+ });
454
+ }
455
+ record(input) {
456
+ return __async(this, null, function* () {
457
+ return yield this.convex.mutation(
458
+ "attachments:record",
459
+ input
460
+ );
461
+ });
462
+ }
463
+ get(attachmentId) {
464
+ return __async(this, null, function* () {
465
+ try {
466
+ const row = yield this.convex.query(
467
+ "attachments:getById",
468
+ { attachmentId }
469
+ );
470
+ return row != null ? row : null;
471
+ } catch (e) {
472
+ return null;
473
+ }
474
+ });
475
+ }
476
+ listByConversation(conversationId) {
477
+ return __async(this, null, function* () {
478
+ try {
479
+ const rows = yield this.convex.query(
480
+ "attachments:listByConversation",
481
+ { conversationId }
482
+ );
483
+ return rows != null ? rows : [];
484
+ } catch (e) {
485
+ return [];
486
+ }
487
+ });
488
+ }
489
+ /**
490
+ * One-shot upload: presign → PUT to S3 → record in Convex → return a
491
+ * 7-day signed download URL ready to embed in an outbound SMS. Caller
492
+ * passes the resulting `downloadUrl` to `messages.dialpad.sendSms` (or
493
+ * the new `messages.sendAttachmentMessage`) to actually deliver it.
494
+ *
495
+ * Replaces CommHub's NestJS `/send-attachment` controller in a single
496
+ * SDK call — no base64 round-trip, no legacy `/attachments/:id/download`
497
+ * REST endpoint required.
498
+ */
499
+ upload(input) {
500
+ return __async(this, null, function* () {
501
+ var _a;
502
+ const presigned = yield this.createUploadUrl({
503
+ fileName: input.fileName,
504
+ mimeType: input.mimeType,
505
+ size: input.size,
506
+ conversationId: input.conversationId
507
+ });
508
+ const body = input.file instanceof Blob ? input.file : input.file instanceof Uint8Array ? input.file : new Uint8Array(input.file);
509
+ const abort = new AbortController();
510
+ const timer = setTimeout(() => abort.abort(), 3e4);
511
+ let putRes;
512
+ try {
513
+ putRes = yield fetch(presigned.uploadUrl, {
514
+ method: "PUT",
515
+ headers: { "Content-Type": input.mimeType },
516
+ body,
517
+ signal: abort.signal
518
+ });
519
+ } catch (err) {
520
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
521
+ if (isAbort) {
522
+ throw new AttachmentsError(
523
+ "s3-put",
524
+ 0,
525
+ "S3 upload timed out after 30s"
526
+ );
527
+ }
528
+ throw err;
529
+ } finally {
530
+ clearTimeout(timer);
531
+ }
532
+ if (!putRes.ok) {
533
+ throw new AttachmentsError(
534
+ "s3-put",
535
+ putRes.status,
536
+ `S3 PUT ${putRes.status} for ${presigned.s3Key}`
537
+ );
538
+ }
539
+ const recorded = yield this.record({
540
+ s3Key: presigned.s3Key,
541
+ fileName: input.fileName,
542
+ mimeType: input.mimeType,
543
+ size: input.size,
544
+ conversationId: input.conversationId,
545
+ uploadedBy: input.uploadedBy
546
+ });
547
+ const signed = yield this.getDownloadUrl(
548
+ presigned.s3Key,
549
+ (_a = input.downloadExpiresIn) != null ? _a : 7 * 24 * 3600
550
+ );
551
+ return {
552
+ attachmentId: recorded.attachmentId,
553
+ s3Key: presigned.s3Key,
554
+ downloadUrl: signed.url
555
+ };
556
+ });
557
+ }
558
+ };
559
+
560
+ // src/resources/conversations.ts
561
+ var ConversationsError = class extends Error {
562
+ constructor(operation, status, message) {
563
+ super(message != null ? message : `Conversations ${operation} failed (HTTP ${status})`);
564
+ this.name = "ConversationsError";
565
+ this.status = status;
566
+ }
567
+ };
568
+ function assertConversationAddress(operation, input) {
569
+ if (!input.conversationId && !input.phonePair) {
570
+ throw new ConversationsError(
571
+ operation,
572
+ 0,
573
+ "Either `conversationId` or `phonePair` is required"
574
+ );
575
+ }
576
+ }
577
+ var ConversationNotesSubresource = class {
578
+ constructor(post) {
579
+ this.post = post;
580
+ }
581
+ /** Create a note on a conversation (addressed by id or phonePair). */
582
+ create(input) {
583
+ return __async(this, null, function* () {
584
+ assertConversationAddress("notes.create", input);
585
+ return this.post("/conversations/notes", input);
586
+ });
587
+ }
588
+ };
589
+ var ConversationTasksSubresource = class {
590
+ constructor(post, patch) {
591
+ this.post = post;
592
+ this.patch = patch;
593
+ }
594
+ /** Create a task on a conversation. */
595
+ create(input) {
596
+ return __async(this, null, function* () {
597
+ assertConversationAddress("tasks.create", input);
598
+ return this.post("/conversations/tasks", input);
599
+ });
600
+ }
601
+ /** Mark a task pending or completed. */
602
+ setStatus(input) {
603
+ return __async(this, null, function* () {
604
+ return this.post(
605
+ `/conversations/tasks/${encodeURIComponent(input.taskId)}/status`,
606
+ __spreadValues({
607
+ id: input.taskId,
608
+ status: input.status
609
+ }, input.resolvedBy ? { resolvedBy: input.resolvedBy } : {})
610
+ );
611
+ });
612
+ }
613
+ /**
614
+ * Update task fields (priority, assignee, description, type). Wraps
615
+ * `PATCH /api/conversations/tasks/{id}` so CommHub doesn't have to
616
+ * direct-fetch for non-status field edits.
617
+ */
618
+ update(input) {
619
+ return __async(this, null, function* () {
620
+ return this.patch(
621
+ `/conversations/tasks/${encodeURIComponent(input.taskId)}`,
622
+ __spreadValues(__spreadValues(__spreadValues(__spreadValues({
623
+ id: input.taskId,
624
+ conversationId: input.conversationId,
625
+ author: input.author,
626
+ description: input.description
627
+ }, input.priority ? { priority: input.priority } : {}), input.status ? { status: input.status } : {}), input.assignee !== void 0 ? { assignee: input.assignee } : {}), input.type !== void 0 ? { type: input.type } : {})
628
+ );
629
+ });
630
+ }
631
+ };
632
+ var ConversationMessagesSubresource = class {
633
+ constructor(post) {
634
+ this.post = post;
635
+ }
636
+ /**
637
+ * Send an outbound SMS / MMS via the typed `sendMessage` oRPC procedure.
638
+ *
639
+ * Prefer this over the generic Dialpad proxy (`messages.dialpad.sendSms`)
640
+ * — this hits the validated Truth route and consistently surfaces
641
+ * `ConversationsError` on failure.
642
+ */
643
+ send(input) {
644
+ return __async(this, null, function* () {
645
+ if (!input.message && !input.media) {
646
+ throw new ConversationsError(
647
+ "messages.send",
648
+ 0,
649
+ "send requires `message` or `media`"
650
+ );
651
+ }
652
+ return this.post(
653
+ "/conversations/messages",
654
+ input
655
+ );
656
+ });
657
+ }
658
+ };
659
+ var _ConversationsResource = class _ConversationsResource {
660
+ constructor(apiBaseUrl, apiKey, convex) {
661
+ this.baseUrl = apiBaseUrl;
662
+ this.apiKey = apiKey;
663
+ this.convex = convex != null ? convex : null;
664
+ const post = (path, body) => this.postRequest(path, body);
665
+ const patch = (path, body) => this.patchRequest(path, body);
666
+ this.notes = new ConversationNotesSubresource(post);
667
+ this.tasks = new ConversationTasksSubresource(post, patch);
668
+ this.messages = new ConversationMessagesSubresource(post);
669
+ }
670
+ /**
671
+ * Mark a conversation read for the calling user (zeroes unreadCount,
672
+ * stamps `lastReadAt`). Calls the public Convex `markRead` mutation
673
+ * which enforces self-tenancy on the auth identity.
674
+ */
675
+ markRead(input) {
676
+ return __async(this, null, function* () {
677
+ if (!this.convex) {
678
+ throw new ConversationsError(
679
+ "/markRead",
680
+ 0,
681
+ "ConversationsResource missing Convex client"
682
+ );
683
+ }
684
+ return yield this.convex.mutation(
685
+ "conversations:markRead",
686
+ input
687
+ );
688
+ });
689
+ }
690
+ /**
691
+ * Mark a conversation unread for the calling user (sets unreadCount=1).
692
+ */
693
+ markUnread(input) {
694
+ return __async(this, null, function* () {
695
+ if (!this.convex) {
696
+ throw new ConversationsError(
697
+ "/markUnread",
698
+ 0,
699
+ "ConversationsResource missing Convex client"
700
+ );
701
+ }
702
+ return yield this.convex.mutation(
703
+ "conversations:markUnread",
704
+ input
705
+ );
706
+ });
707
+ }
708
+ /**
709
+ * Zero unread on every conversation the user has reads for, except
710
+ * those whose `providerPhone` is in `excludedProviderPhones`.
711
+ */
712
+ clearAllUnread(input) {
713
+ return __async(this, null, function* () {
714
+ if (!this.convex) {
715
+ throw new ConversationsError(
716
+ "/clearAllUnread",
717
+ 0,
718
+ "ConversationsResource missing Convex client"
719
+ );
720
+ }
721
+ return yield this.convex.mutation(
722
+ "conversations:clearAllUnread",
723
+ input
724
+ );
725
+ });
726
+ }
727
+ postRequest(path, body) {
728
+ return __async(this, null, function* () {
729
+ if (!this.apiKey) {
730
+ throw new ConversationsError(
731
+ path,
732
+ 0,
733
+ "Truth API key not configured \u2014 request blocked"
734
+ );
735
+ }
736
+ const controller = new AbortController();
737
+ const timeout = setTimeout(
738
+ () => controller.abort(),
739
+ _ConversationsResource.REQUEST_TIMEOUT_MS
740
+ );
741
+ let res;
742
+ try {
743
+ res = yield fetch(`${this.baseUrl}/api${path}`, {
744
+ method: "POST",
745
+ headers: {
746
+ "Content-Type": "application/json",
747
+ Accept: "application/json",
748
+ "X-API-Key": this.apiKey
749
+ },
750
+ body: JSON.stringify(body),
751
+ signal: controller.signal
752
+ });
753
+ } catch (err) {
754
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
755
+ const message = isAbort ? `Conversations ${path} timed out after ${_ConversationsResource.REQUEST_TIMEOUT_MS}ms` : err instanceof Error ? err.message : "Conversations request failed before response";
756
+ throw new ConversationsError(path, 0, message);
757
+ } finally {
758
+ clearTimeout(timeout);
759
+ }
760
+ if (!res.ok) {
761
+ const text = yield res.text().catch(() => "");
762
+ throw new ConversationsError(path, res.status, text.slice(0, 200));
763
+ }
764
+ return yield res.json();
765
+ });
766
+ }
767
+ /**
768
+ * PATCH variant of `postRequest`. Mirrors the timeout + API-key
769
+ * handling so callers like `tasks.update()` don't need to roll their
770
+ * own fetch. The Truth task router treats unknown methods as 405, so
771
+ * a dedicated PATCH path is required.
772
+ */
773
+ patchRequest(path, body) {
774
+ return __async(this, null, function* () {
775
+ if (!this.apiKey) {
776
+ throw new ConversationsError(
777
+ path,
778
+ 0,
779
+ "Truth API key not configured \u2014 request blocked"
780
+ );
781
+ }
782
+ const controller = new AbortController();
783
+ const timeout = setTimeout(
784
+ () => controller.abort(),
785
+ _ConversationsResource.REQUEST_TIMEOUT_MS
786
+ );
787
+ let res;
788
+ try {
789
+ res = yield fetch(`${this.baseUrl}/api${path}`, {
790
+ method: "PATCH",
791
+ headers: {
792
+ "Content-Type": "application/json",
793
+ Accept: "application/json",
794
+ "X-API-Key": this.apiKey
795
+ },
796
+ body: JSON.stringify(body),
797
+ signal: controller.signal
798
+ });
799
+ } catch (err) {
800
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
801
+ const message = isAbort ? `Conversations ${path} timed out after ${_ConversationsResource.REQUEST_TIMEOUT_MS}ms` : err instanceof Error ? err.message : String(err);
802
+ throw new ConversationsError(path, 0, message);
803
+ } finally {
804
+ clearTimeout(timeout);
805
+ }
806
+ if (!res.ok) {
807
+ const text = yield res.text().catch(() => "");
808
+ throw new ConversationsError(
809
+ path,
810
+ res.status,
811
+ `Conversations ${path} failed: ${text.slice(0, 200)}`
812
+ );
813
+ }
814
+ return yield res.json();
815
+ });
816
+ }
817
+ };
818
+ /** 30s upstream timeout — matches NotesResource for consistency. */
819
+ _ConversationsResource.REQUEST_TIMEOUT_MS = 3e4;
820
+ var ConversationsResource = _ConversationsResource;
821
+
822
+ // src/resources/dialpad.ts
823
+ var DialpadResource = class {
824
+ constructor(apiBaseUrl, apiKey) {
825
+ this.baseUrl = apiBaseUrl;
826
+ this.apiKey = apiKey;
827
+ }
828
+ /**
829
+ * Send an SMS or MMS message via Dialpad.
830
+ */
831
+ sendSms(params) {
832
+ return __async(this, null, function* () {
833
+ const body = __spreadValues(__spreadValues({
834
+ to_numbers: [params.to_number],
835
+ from_number: params.from_number,
836
+ infer_country_code: false
837
+ }, params.message ? { text: params.message } : {}), params.media ? { media: params.media } : {});
838
+ return this.post("/sms", body);
839
+ });
840
+ }
841
+ /**
842
+ * Initiate an outbound call from a Dialpad user to a phone number.
843
+ */
844
+ initiateCall(userId, phoneNumber) {
845
+ return __async(this, null, function* () {
846
+ return this.post(`/users/${userId}/initiate_call`, {
847
+ phone_number: phoneNumber
848
+ });
849
+ });
850
+ }
851
+ /**
852
+ * Hang up an active call.
853
+ */
854
+ hangupCall(callId) {
855
+ return __async(this, null, function* () {
856
+ yield this.put(`/call/${callId}/actions/hangup`);
857
+ });
858
+ }
859
+ /**
860
+ * Alias for `hangupCall` — mirrors the CommHub `endCall` action name so
861
+ * the SDK swap is a direct rename.
862
+ */
863
+ endCall(callId) {
864
+ return __async(this, null, function* () {
865
+ yield this.hangupCall(callId);
866
+ });
867
+ }
868
+ /**
869
+ * Send an MMS with a pre-uploaded attachment. Takes the S3 key returned
870
+ * by `client.attachments.createUploadUrl(...)` + PUT, fetches a short-
871
+ * lived download URL, and hands it to Dialpad as `media[]`.
872
+ *
873
+ * Replaces CommHub's `sendAttachment` Hasura Action (which stored bytes
874
+ * as base64 in Postgres and served them through a GET endpoint).
875
+ */
876
+ sendAttachmentWithUrl(params) {
877
+ return __async(this, null, function* () {
878
+ return this.sendSms({
879
+ from_number: params.from_number,
880
+ to_number: params.to_number,
881
+ message: params.message,
882
+ media: [params.mediaUrl]
883
+ });
884
+ });
885
+ }
886
+ /**
887
+ * Get the status of a call.
888
+ */
889
+ getCallStatus(callId) {
890
+ return __async(this, null, function* () {
891
+ try {
892
+ return yield this.get(`/call/${callId}`);
893
+ } catch (error) {
894
+ if (error instanceof DialpadProxyError && error.status === 404) {
895
+ return null;
896
+ }
897
+ throw error;
898
+ }
899
+ });
900
+ }
901
+ /**
902
+ * Get a Dialpad user by their user ID.
903
+ */
904
+ getUser(userId) {
905
+ return __async(this, null, function* () {
906
+ try {
907
+ return yield this.get(`/users/${userId}`);
908
+ } catch (error) {
909
+ if (error instanceof DialpadProxyError && error.status === 404) {
910
+ return null;
911
+ }
912
+ throw error;
913
+ }
914
+ });
915
+ }
916
+ /**
917
+ * Find a Dialpad user by email.
918
+ */
919
+ getUserByEmail(email) {
920
+ return __async(this, null, function* () {
921
+ var _a, _b;
922
+ const result = yield this.get("/users", {
923
+ email
924
+ });
925
+ return (_b = (_a = result.items) == null ? void 0 : _a[0]) != null ? _b : null;
926
+ });
927
+ }
928
+ /**
929
+ * Find a Dialpad user by phone number.
930
+ */
931
+ getUserByPhoneNumber(phoneNumber) {
932
+ return __async(this, null, function* () {
933
+ var _a, _b;
934
+ const result = yield this.get("/users", {
935
+ number: phoneNumber
936
+ });
937
+ return (_b = (_a = result.items) == null ? void 0 : _a[0]) != null ? _b : null;
938
+ });
939
+ }
940
+ /**
941
+ * Get information about a Dialpad phone number.
942
+ */
943
+ getNumberInfo(phoneNumber) {
944
+ return __async(this, null, function* () {
945
+ try {
946
+ const cleanNumber = phoneNumber.replace(/[^\d+]/g, "");
947
+ return yield this.get(
948
+ `/numbers/${encodeURIComponent(cleanNumber)}`
949
+ );
950
+ } catch (error) {
951
+ if (error instanceof DialpadProxyError && error.status === 404) {
952
+ return null;
953
+ }
954
+ throw error;
955
+ }
956
+ });
957
+ }
958
+ /**
959
+ * Authenticate a voicemail download URL. Truth appends the Dialpad API key,
960
+ * follows redirects, and returns the clean URL for client-side playback.
961
+ */
962
+ authenticateVoicemail(voicemailLink) {
963
+ return __async(this, null, function* () {
964
+ const url = `${this.baseUrl}/api/messages/dialpad/voicemail/authenticate`;
965
+ const response = yield fetch(url, {
966
+ method: "POST",
967
+ headers: {
968
+ "Content-Type": "application/json",
969
+ "X-API-Key": this.apiKey
970
+ },
971
+ body: JSON.stringify({ voicemail_link: voicemailLink })
972
+ });
973
+ const result = yield response.json();
974
+ if (!response.ok || !result.success) {
975
+ return null;
976
+ }
977
+ return result.authenticated_url;
978
+ });
979
+ }
980
+ // -----------------------------------------------------------------------
981
+ // Internal HTTP helpers
982
+ // -----------------------------------------------------------------------
983
+ get(path, params) {
984
+ return __async(this, null, function* () {
985
+ const url = new URL(`/api/messages/dialpad${path}`, this.baseUrl);
986
+ if (params) {
987
+ for (const [key, value] of Object.entries(params)) {
988
+ if (value !== void 0) {
989
+ url.searchParams.set(key, String(value));
990
+ }
991
+ }
992
+ }
993
+ const response = yield fetch(url.toString(), {
994
+ method: "GET",
995
+ headers: {
996
+ Accept: "application/json",
997
+ "X-API-Key": this.apiKey
998
+ }
999
+ });
1000
+ if (!response.ok) {
1001
+ throw new DialpadProxyError("GET", path, response.status);
1002
+ }
1003
+ return yield response.json();
1004
+ });
1005
+ }
1006
+ post(path, body) {
1007
+ return __async(this, null, function* () {
1008
+ const url = `${this.baseUrl}/api/messages/dialpad${path}`;
1009
+ const response = yield fetch(url, {
1010
+ method: "POST",
1011
+ headers: {
1012
+ "Content-Type": "application/json",
1013
+ Accept: "application/json",
1014
+ "X-API-Key": this.apiKey
1015
+ },
1016
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1017
+ });
1018
+ if (!response.ok) {
1019
+ throw new DialpadProxyError("POST", path, response.status);
1020
+ }
1021
+ return yield response.json();
1022
+ });
1023
+ }
1024
+ put(path, body) {
1025
+ return __async(this, null, function* () {
1026
+ var _a;
1027
+ const url = `${this.baseUrl}/api/messages/dialpad${path}`;
1028
+ const response = yield fetch(url, {
1029
+ method: "PUT",
1030
+ headers: {
1031
+ "Content-Type": "application/json",
1032
+ Accept: "application/json",
1033
+ "X-API-Key": this.apiKey
1034
+ },
1035
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1036
+ });
1037
+ if (!response.ok) {
1038
+ throw new DialpadProxyError("PUT", path, response.status);
1039
+ }
1040
+ if ((_a = response.headers.get("content-type")) == null ? void 0 : _a.includes("json")) {
1041
+ return yield response.json();
1042
+ }
1043
+ return void 0;
1044
+ });
1045
+ }
1046
+ };
1047
+ var MessagesResource = class {
1048
+ constructor(apiBaseUrl, apiKey) {
1049
+ this.dialpad = new DialpadResource(apiBaseUrl, apiKey);
1050
+ this.baseUrl = apiBaseUrl;
1051
+ this.apiKey = apiKey;
1052
+ }
1053
+ /**
1054
+ * Get an authenticated URL for a Dialpad voicemail recording.
1055
+ *
1056
+ * Replaces CommHub's `getAuthenticatedVoicemailUrl` Hasura mutation
1057
+ * (which proxied to the legacy NestJS backend). Truth appends the
1058
+ * Dialpad API key, follows redirects, strips the key from the final
1059
+ * URL, and returns it for client-side audio playback.
1060
+ *
1061
+ * @param voicemailLink The raw Dialpad voicemail download URL (value
1062
+ * of the `voicemail_link` field on the call event row).
1063
+ * @returns An object with `url` (clean playback URL) and `expiresAt`
1064
+ * (ISO-8601 timestamp, ~5 min from request time, informational only).
1065
+ */
1066
+ getVoicemailUrl(voicemailLink) {
1067
+ return __async(this, null, function* () {
1068
+ const res = yield fetch(`${this.baseUrl}/api/conversations/voicemail/url`, {
1069
+ method: "POST",
1070
+ headers: {
1071
+ "Content-Type": "application/json",
1072
+ Accept: "application/json",
1073
+ "X-API-Key": this.apiKey
1074
+ },
1075
+ body: JSON.stringify({ voicemailLink })
1076
+ });
1077
+ if (!res.ok) {
1078
+ const text = yield res.text().catch(() => "");
1079
+ throw new Error(
1080
+ `messages.getVoicemailUrl failed (HTTP ${res.status}): ${text.slice(0, 200)}`
1081
+ );
1082
+ }
1083
+ return yield res.json();
1084
+ });
1085
+ }
1086
+ /**
1087
+ * End a Dialpad call via the typed oRPC procedure (POST hangup, with
1088
+ * 404→`alreadyEnded` so the UI doesn't flash an error on a natural
1089
+ * race). Replaces CommHub's `useEndCallMutation` Hasura action.
1090
+ *
1091
+ * Prefer this over `messages.dialpad.endCall` which goes through the
1092
+ * generic proxy (PUT, no 404 handling).
1093
+ */
1094
+ endCall(callId) {
1095
+ return __async(this, null, function* () {
1096
+ const res = yield fetch(`${this.baseUrl}/api/conversations/calls/end`, {
1097
+ method: "POST",
1098
+ headers: {
1099
+ "Content-Type": "application/json",
1100
+ Accept: "application/json",
1101
+ "X-API-Key": this.apiKey
1102
+ },
1103
+ body: JSON.stringify({ callId })
1104
+ });
1105
+ if (!res.ok) {
1106
+ const text = yield res.text().catch(() => "");
1107
+ throw new Error(
1108
+ `messages.endCall failed (HTTP ${res.status}): ${text.slice(0, 200)}`
1109
+ );
1110
+ }
1111
+ return yield res.json();
1112
+ });
1113
+ }
1114
+ };
1115
+ var DialpadProxyError = class extends Error {
1116
+ constructor(method, path, status) {
1117
+ super(
1118
+ `Dialpad proxy error: ${method} /api/messages/dialpad${path} returned ${status}`
1119
+ );
1120
+ this.name = "DialpadProxyError";
1121
+ this.method = method;
1122
+ this.path = path;
1123
+ this.status = status;
1124
+ }
1125
+ };
1126
+
1127
+ // src/resources/ehr.ts
1128
+ var EhrProviderProxy = class {
1129
+ constructor(apiBaseUrl, provider) {
1130
+ this.baseUrl = apiBaseUrl;
1131
+ this.provider = provider;
1132
+ }
1133
+ /**
1134
+ * GET request to the EHR proxy.
1135
+ * @param path — path relative to the provider root (e.g., "/patients/123/")
1136
+ * @param params — optional query parameters
1137
+ */
1138
+ get(path, params) {
1139
+ return __async(this, null, function* () {
1140
+ const url = new URL(`/api/ehr/${this.provider}${path}`, this.baseUrl);
1141
+ if (params) {
1142
+ for (const [key, value] of Object.entries(params)) {
1143
+ if (value !== void 0) {
1144
+ url.searchParams.set(key, String(value));
1145
+ }
1146
+ }
1147
+ }
1148
+ const response = yield fetch(url.toString(), {
1149
+ method: "GET",
1150
+ headers: { Accept: "application/json" }
1151
+ });
1152
+ if (!response.ok) {
1153
+ throw new EhrProxyError(this.provider, "GET", path, response.status);
1154
+ }
1155
+ return yield response.json();
1156
+ });
1157
+ }
1158
+ /**
1159
+ * POST request to the EHR proxy.
1160
+ */
1161
+ post(path, body) {
1162
+ return __async(this, null, function* () {
1163
+ const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
1164
+ const response = yield fetch(url, {
1165
+ method: "POST",
1166
+ headers: {
1167
+ "Content-Type": "application/json",
1168
+ Accept: "application/json"
1169
+ },
1170
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1171
+ });
1172
+ if (!response.ok) {
1173
+ throw new EhrProxyError(this.provider, "POST", path, response.status);
1174
+ }
1175
+ return yield response.json();
1176
+ });
1177
+ }
1178
+ /**
1179
+ * PUT request to the EHR proxy.
1180
+ */
1181
+ put(path, body) {
1182
+ return __async(this, null, function* () {
1183
+ const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
1184
+ const response = yield fetch(url, {
1185
+ method: "PUT",
1186
+ headers: {
1187
+ "Content-Type": "application/json",
1188
+ Accept: "application/json"
1189
+ },
1190
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1191
+ });
1192
+ if (!response.ok) {
1193
+ throw new EhrProxyError(this.provider, "PUT", path, response.status);
1194
+ }
1195
+ return yield response.json();
1196
+ });
1197
+ }
1198
+ /**
1199
+ * PATCH request to the EHR proxy.
1200
+ */
1201
+ patch(path, body) {
1202
+ return __async(this, null, function* () {
1203
+ const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
1204
+ const response = yield fetch(url, {
1205
+ method: "PATCH",
1206
+ headers: {
1207
+ "Content-Type": "application/json",
1208
+ Accept: "application/json"
1209
+ },
1210
+ body: body !== void 0 ? JSON.stringify(body) : void 0
1211
+ });
1212
+ if (!response.ok) {
1213
+ throw new EhrProxyError(this.provider, "PATCH", path, response.status);
1214
+ }
1215
+ return yield response.json();
1216
+ });
1217
+ }
1218
+ /**
1219
+ * DELETE request to the EHR proxy.
1220
+ */
1221
+ delete(path) {
1222
+ return __async(this, null, function* () {
1223
+ const url = `${this.baseUrl}/api/ehr/${this.provider}${path}`;
1224
+ const response = yield fetch(url, {
1225
+ method: "DELETE",
1226
+ headers: { Accept: "application/json" }
1227
+ });
1228
+ if (!response.ok) {
1229
+ throw new EhrProxyError(this.provider, "DELETE", path, response.status);
1230
+ }
1231
+ return yield response.json();
1232
+ });
1233
+ }
1234
+ };
1235
+ var EhrResource = class {
1236
+ constructor(apiBaseUrl) {
1237
+ this.elation = new EhrProviderProxy(apiBaseUrl, "elation");
1238
+ this.hint = new EhrProviderProxy(apiBaseUrl, "hint");
1239
+ }
1240
+ };
1241
+ var EhrProxyError = class extends Error {
1242
+ constructor(provider, method, path, status) {
1243
+ super(
1244
+ `EHR proxy error: ${method} /api/ehr/${provider}${path} returned ${status}`
1245
+ );
1246
+ this.name = "EhrProxyError";
1247
+ this.provider = provider;
1248
+ this.method = method;
1249
+ this.path = path;
1250
+ this.status = status;
1251
+ }
1252
+ };
1253
+
1254
+ // src/resources/notes.ts
1255
+ var NotesError = class extends Error {
1256
+ constructor(operation, status, message) {
1257
+ super(message != null ? message : `Notes ${operation} failed (HTTP ${status})`);
1258
+ this.name = "NotesError";
1259
+ this.status = status;
1260
+ }
1261
+ };
1262
+ var _NotesResource = class _NotesResource {
1263
+ constructor(apiBaseUrl, apiKey) {
1264
+ this.baseUrl = apiBaseUrl;
1265
+ this.apiKey = apiKey;
1266
+ }
1267
+ post(path, body) {
1268
+ return __async(this, null, function* () {
1269
+ const controller = new AbortController();
1270
+ const timeout = setTimeout(
1271
+ () => controller.abort(),
1272
+ _NotesResource.REQUEST_TIMEOUT_MS
1273
+ );
1274
+ let res;
1275
+ try {
1276
+ res = yield fetch(`${this.baseUrl}/api${path}`, {
1277
+ method: "POST",
1278
+ headers: {
1279
+ "Content-Type": "application/json",
1280
+ Accept: "application/json",
1281
+ "X-API-Key": this.apiKey
1282
+ },
1283
+ body: JSON.stringify(body),
1284
+ signal: controller.signal
1285
+ });
1286
+ } catch (err) {
1287
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError");
1288
+ const message = isAbort ? `Notes ${path} timed out after ${_NotesResource.REQUEST_TIMEOUT_MS}ms` : err instanceof Error ? err.message : "Notes request failed before response";
1289
+ throw new NotesError(path, 0, message);
1290
+ } finally {
1291
+ clearTimeout(timeout);
1292
+ }
1293
+ if (!res.ok) {
1294
+ const text = yield res.text().catch(() => "");
1295
+ throw new NotesError(path, res.status, text.slice(0, 200));
1296
+ }
1297
+ return yield res.json();
1298
+ });
1299
+ }
1300
+ /**
1301
+ * Low-level — caller has already resolved Elation patient / physician
1302
+ * / practice. Use `pushConversationToElation` if you only have a
1303
+ * conversation handle.
1304
+ */
1305
+ pushToElation(input) {
1306
+ return __async(this, null, function* () {
1307
+ return yield this.post(
1308
+ "/notes/push-to-elation",
1309
+ input
1310
+ );
1311
+ });
1312
+ }
1313
+ /**
1314
+ * Orchestrator — pass a conversation handle + pre-assembled note text.
1315
+ * Truth resolves the linked patient via Convex, looks up
1316
+ * physician/practice in Elation, posts the note, and writes an audit
1317
+ * row to `elationSyncEvents`. Replaces CommHub's `pushNotesToElation`
1318
+ * Hasura action.
1319
+ */
1320
+ pushConversationToElation(input) {
1321
+ return __async(this, null, function* () {
1322
+ return yield this.post(
1323
+ "/notes/push-conversation-to-elation",
1324
+ input
1325
+ );
1326
+ });
1327
+ }
1328
+ };
1329
+ /** 30s upstream timeout — Elation API has occasional slow hops; we
1330
+ * don't want a pending mutation to hold the UI thread indefinitely. */
1331
+ _NotesResource.REQUEST_TIMEOUT_MS = 3e4;
1332
+ var NotesResource = _NotesResource;
1333
+
1334
+ // src/resources/notifications.ts
1335
+ var NotificationsError = class extends Error {
1336
+ constructor(operation, status, message) {
1337
+ super(message != null ? message : `Notifications ${operation} failed (HTTP ${status})`);
1338
+ this.name = "NotificationsError";
1339
+ this.status = status;
1340
+ }
1341
+ };
1342
+ var NotificationsResource = class {
1343
+ constructor(apiBaseUrl, apiKey) {
1344
+ this.baseUrl = apiBaseUrl;
1345
+ this.apiKey = apiKey;
1346
+ }
1347
+ post(path, body) {
1348
+ return __async(this, null, function* () {
1349
+ const res = yield fetch(`${this.baseUrl}/api${path}`, {
1350
+ method: "POST",
1351
+ headers: {
1352
+ "Content-Type": "application/json",
1353
+ Accept: "application/json",
1354
+ "X-API-Key": this.apiKey
1355
+ },
1356
+ body: JSON.stringify(body)
1357
+ });
1358
+ if (!res.ok) {
1359
+ const text = yield res.text().catch(() => "");
1360
+ throw new NotificationsError(path, res.status, text.slice(0, 200));
1361
+ }
1362
+ return yield res.json();
1363
+ });
1364
+ }
1365
+ get(path, params) {
1366
+ return __async(this, null, function* () {
1367
+ const url = new URL(`${this.baseUrl}/api${path}`);
1368
+ for (const [k, v] of Object.entries(params)) {
1369
+ url.searchParams.set(k, v);
1370
+ }
1371
+ const res = yield fetch(url.toString(), {
1372
+ method: "GET",
1373
+ headers: {
1374
+ Accept: "application/json",
1375
+ "X-API-Key": this.apiKey
1376
+ }
1377
+ });
1378
+ if (!res.ok) {
1379
+ const text = yield res.text().catch(() => "");
1380
+ throw new NotificationsError(path, res.status, text.slice(0, 200));
1381
+ }
1382
+ return yield res.json();
1383
+ });
1384
+ }
1385
+ delete(path) {
1386
+ return __async(this, null, function* () {
1387
+ const res = yield fetch(`${this.baseUrl}/api${path}`, {
1388
+ method: "DELETE",
1389
+ headers: {
1390
+ Accept: "application/json",
1391
+ "X-API-Key": this.apiKey
1392
+ }
1393
+ });
1394
+ if (!res.ok) {
1395
+ const text = yield res.text().catch(() => "");
1396
+ throw new NotificationsError(path, res.status, text.slice(0, 200));
1397
+ }
1398
+ return yield res.json();
1399
+ });
1400
+ }
1401
+ /**
1402
+ * Register a device (or refresh its metadata) for push delivery.
1403
+ * Safe to call repeatedly — the server dedupes by native token.
1404
+ */
1405
+ registerDevice(input) {
1406
+ return __async(this, null, function* () {
1407
+ return this.post("/notifications/devices/register", input);
1408
+ });
1409
+ }
1410
+ /** Revoke a device — on sign-out or when the OS reports an invalid token. */
1411
+ unregisterDevice(input) {
1412
+ return __async(this, null, function* () {
1413
+ return this.post("/notifications/devices/unregister", input);
1414
+ });
1415
+ }
1416
+ /**
1417
+ * Send a push notification to every active device belonging to
1418
+ * `userId`. Honors the user's notificationPreferences (quiet hours,
1419
+ * DND, channel off) before publishing.
1420
+ */
1421
+ send(input) {
1422
+ return __async(this, null, function* () {
1423
+ return this.post("/notifications/send", input);
1424
+ });
1425
+ }
1426
+ /** Read a user's notification preferences. Returns defaults when no row exists. */
1427
+ getPreferences(userId) {
1428
+ return __async(this, null, function* () {
1429
+ return this.get("/notifications/preferences", { userId });
1430
+ });
1431
+ }
1432
+ updatePreferences(input) {
1433
+ return __async(this, null, function* () {
1434
+ return this.post("/notifications/preferences", input);
1435
+ });
1436
+ }
1437
+ /**
1438
+ * Schedule a future push notification. Convex's native scheduler
1439
+ * fires the send at `scheduledAt` and runs the same delivery
1440
+ * pipeline as `send()` (preferences, devices, history audit).
1441
+ *
1442
+ * Throws `NotificationsError` with status 400 if `scheduledAt` is
1443
+ * not strictly in the future.
1444
+ */
1445
+ schedule(input) {
1446
+ return __async(this, null, function* () {
1447
+ return this.post("/notifications/schedule", input);
1448
+ });
1449
+ }
1450
+ /**
1451
+ * Cancel a pending scheduled notification. Returns `cancelled: false`
1452
+ * (no error) if the job has already executed, was previously
1453
+ * cancelled, or no longer exists — `reason` describes which case.
1454
+ */
1455
+ cancelScheduled(jobId) {
1456
+ return __async(this, null, function* () {
1457
+ return this.delete(`/notifications/schedule/${encodeURIComponent(jobId)}`);
1458
+ });
1459
+ }
1460
+ /**
1461
+ * List scheduled notifications for a user — pending, executed,
1462
+ * cancelled, or failed. Most-recent first. Default limit 100.
1463
+ */
1464
+ listScheduled(userId, options) {
1465
+ return __async(this, null, function* () {
1466
+ const params = { userId };
1467
+ if ((options == null ? void 0 : options.limit) !== void 0) {
1468
+ params.limit = String(options.limit);
1469
+ }
1470
+ return this.get("/notifications/schedule", params);
1471
+ });
1472
+ }
1473
+ getVapidKey() {
1474
+ return __async(this, null, function* () {
1475
+ try {
1476
+ const result = yield this.get(
1477
+ "/notifications/vapid-key",
1478
+ {}
1479
+ );
1480
+ return result.vapidPublicKey;
1481
+ } catch (e) {
1482
+ return null;
1483
+ }
1484
+ });
1485
+ }
1486
+ onPushReceived(callback) {
1487
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
1488
+ return () => {
1489
+ };
1490
+ }
1491
+ const handler = (event) => {
1492
+ var _a;
1493
+ if (((_a = event.data) == null ? void 0 : _a.type) === "TRUTH_PUSH_RECEIVED") {
1494
+ callback(event.data.payload);
1495
+ }
1496
+ };
1497
+ navigator.serviceWorker.addEventListener("message", handler);
1498
+ return () => navigator.serviceWorker.removeEventListener("message", handler);
1499
+ }
1500
+ onPushTapped(callback) {
1501
+ if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
1502
+ return () => {
1503
+ };
1504
+ }
1505
+ const handler = (event) => {
1506
+ var _a;
1507
+ if (((_a = event.data) == null ? void 0 : _a.type) === "TRUTH_PUSH_TAPPED") {
1508
+ callback(event.data.payload);
1509
+ }
1510
+ };
1511
+ navigator.serviceWorker.addEventListener("message", handler);
1512
+ return () => navigator.serviceWorker.removeEventListener("message", handler);
1513
+ }
1514
+ };
1515
+
1516
+ // src/resources/patient-details.ts
1517
+ var PatientDetailsError = class extends Error {
1518
+ constructor(operation, status, message) {
1519
+ super(message != null ? message : `Patient ${operation} failed (HTTP ${status})`);
1520
+ this.name = "PatientDetailsError";
1521
+ this.status = status;
1522
+ }
1523
+ };
1524
+ var PatientDetailsResource = class {
1525
+ constructor(apiBaseUrl, apiKey) {
1526
+ this.baseUrl = apiBaseUrl;
1527
+ this.apiKey = apiKey;
1528
+ }
1529
+ post(path, body) {
1530
+ return __async(this, null, function* () {
1531
+ const res = yield fetch(`${this.baseUrl}/api${path}`, {
1532
+ method: "POST",
1533
+ headers: {
1534
+ "Content-Type": "application/json",
1535
+ Accept: "application/json",
1536
+ "X-API-Key": this.apiKey
1537
+ },
1538
+ body: JSON.stringify(body)
1539
+ });
1540
+ if (!res.ok) {
1541
+ const text = yield res.text().catch(() => "");
1542
+ throw new PatientDetailsError(path, res.status, text.slice(0, 200));
1543
+ }
1544
+ return yield res.json();
1545
+ });
1546
+ }
1547
+ get(input) {
1548
+ return __async(this, null, function* () {
1549
+ return yield this.post("/patients/details", input);
1550
+ });
1551
+ }
1552
+ getBasic(input) {
1553
+ return __async(this, null, function* () {
1554
+ return yield this.post(
1555
+ "/patients/details/basic",
1556
+ input
1557
+ );
1558
+ });
1559
+ }
1560
+ getMedical(elationId) {
1561
+ return __async(this, null, function* () {
1562
+ return yield this.post(
1563
+ "/patients/details/medical",
1564
+ { elationId }
1565
+ );
1566
+ });
1567
+ }
1568
+ /**
1569
+ * Trigger a server-side refresh of the patient's Elation medical
1570
+ * records (medications, problems, allergies, appointments) into the
1571
+ * Convex cache. Fire-and-forget — the UI should read via the Convex-
1572
+ * reactive `usePatientMedical` hook.
1573
+ */
1574
+ refreshMedical(elationId) {
1575
+ return __async(this, null, function* () {
1576
+ return yield this.post("/patients/medical/refresh", { elationId });
1577
+ });
1578
+ }
1579
+ };
1580
+
1581
+ // src/resources/patients.ts
1582
+ var PatientResource = class {
1583
+ constructor(convexClient) {
1584
+ this.convex = convexClient;
1585
+ }
1586
+ /**
1587
+ * Get a patient by their Truth platform ID.
1588
+ */
1589
+ get(id) {
1590
+ return __async(this, null, function* () {
1591
+ try {
1592
+ const result = yield this.convex.query(
1593
+ "patients:getById",
1594
+ {
1595
+ id
1596
+ }
1597
+ );
1598
+ return result != null ? result : null;
1599
+ } catch (e) {
1600
+ return null;
1601
+ }
1602
+ });
1603
+ }
1604
+ /**
1605
+ * Get a patient by their Elation EHR ID.
1606
+ */
1607
+ getByElationId(elationId) {
1608
+ return __async(this, null, function* () {
1609
+ try {
1610
+ const result = yield this.convex.query(
1611
+ "patients:getByElationId",
1612
+ { elationId }
1613
+ );
1614
+ return result != null ? result : null;
1615
+ } catch (e) {
1616
+ return null;
1617
+ }
1618
+ });
1619
+ }
1620
+ /**
1621
+ * Get a patient by their Hint EHR ID.
1622
+ */
1623
+ getByHintId(hintId) {
1624
+ return __async(this, null, function* () {
1625
+ try {
1626
+ const result = yield this.convex.query(
1627
+ "patients:getByHintId",
1628
+ {
1629
+ hintId
1630
+ }
1631
+ );
1632
+ return result != null ? result : null;
1633
+ } catch (e) {
1634
+ return null;
1635
+ }
1636
+ });
1637
+ }
1638
+ /**
1639
+ * List patients with optional search, pagination, and limit.
1640
+ */
1641
+ list(options) {
1642
+ return __async(this, null, function* () {
1643
+ try {
1644
+ const result = yield this.convex.query(
1645
+ "patients:list",
1646
+ {
1647
+ search: options == null ? void 0 : options.search,
1648
+ limit: options == null ? void 0 : options.limit,
1649
+ cursor: options == null ? void 0 : options.cursor
1650
+ }
1651
+ );
1652
+ const typed = result;
1653
+ return typed != null ? typed : { data: [], cursor: null, hasMore: false };
1654
+ } catch (e) {
1655
+ return { data: [], cursor: null, hasMore: false };
1656
+ }
1657
+ });
1658
+ }
1659
+ };
1660
+
1661
+ // src/resources/physicians.ts
1662
+ var PhysiciansResource = class {
1663
+ constructor(convex) {
1664
+ this.convex = convex;
1665
+ }
1666
+ /**
1667
+ * Resolve a batch of physicians by Elation IDs. Missing ids are dropped.
1668
+ */
1669
+ getByElationIds(ids) {
1670
+ return __async(this, null, function* () {
1671
+ if (ids.length === 0) {
1672
+ return [];
1673
+ }
1674
+ try {
1675
+ const rows = yield this.convex.query(
1676
+ "physicians:getByElationIds",
1677
+ { ids }
1678
+ );
1679
+ return rows != null ? rows : [];
1680
+ } catch (e) {
1681
+ return [];
1682
+ }
1683
+ });
1684
+ }
1685
+ getByElationId(id) {
1686
+ return __async(this, null, function* () {
1687
+ try {
1688
+ const row = yield this.convex.query(
1689
+ "physicians:getByElationId",
1690
+ { id }
1691
+ );
1692
+ return row != null ? row : null;
1693
+ } catch (e) {
1694
+ return null;
1695
+ }
1696
+ });
1697
+ }
1698
+ listByPractice(practice, limit) {
1699
+ return __async(this, null, function* () {
1700
+ try {
1701
+ const rows = yield this.convex.query(
1702
+ "physicians:listByPractice",
1703
+ { practice, limit }
1704
+ );
1705
+ return rows != null ? rows : [];
1706
+ } catch (e) {
1707
+ return [];
1708
+ }
1709
+ });
1710
+ }
1711
+ };
1712
+
1713
+ // src/resources/reminders.ts
1714
+ var RemindersResource = class {
1715
+ constructor(convexClient) {
1716
+ this.convex = convexClient;
1717
+ }
1718
+ /**
1719
+ * Schedule a reminder to fire at `remindAt`. Returns the reminder id,
1720
+ * which callers should store if they may want to cancel it later.
1721
+ */
1722
+ schedule(input) {
1723
+ return __async(this, null, function* () {
1724
+ const remindAt = input.remindAt instanceof Date ? input.remindAt.toISOString() : input.remindAt;
1725
+ const result = yield this.convex.mutation(
1726
+ "reminders:schedule",
1727
+ {
1728
+ conversationId: input.conversationId,
1729
+ remindAt,
1730
+ note: input.note,
1731
+ createdBy: input.createdBy
1732
+ }
1733
+ );
1734
+ return result;
1735
+ });
1736
+ }
1737
+ /**
1738
+ * Cancel a pending reminder. No-op if the reminder has already fired or
1739
+ * been cancelled.
1740
+ */
1741
+ cancel(reminderId, cancelledBy) {
1742
+ return __async(this, null, function* () {
1743
+ return yield this.convex.mutation(
1744
+ "reminders:cancel",
1745
+ { reminderId, cancelledBy }
1746
+ );
1747
+ });
1748
+ }
1749
+ /**
1750
+ * List reminders for a conversation (most recent first).
1751
+ */
1752
+ listByConversation(conversationId) {
1753
+ return __async(this, null, function* () {
1754
+ try {
1755
+ const rows = yield this.convex.query(
1756
+ "reminders:listByConversation",
1757
+ { conversationId }
1758
+ );
1759
+ return rows != null ? rows : [];
1760
+ } catch (e) {
1761
+ return [];
1762
+ }
1763
+ });
1764
+ }
1765
+ };
1766
+
1767
+ // src/resources/tasks.ts
1768
+ var TasksResource = class {
1769
+ constructor(convexClient) {
1770
+ this.convex = convexClient;
1771
+ }
1772
+ create(input) {
1773
+ return __async(this, null, function* () {
1774
+ return yield this.convex.mutation(
1775
+ "tasks:create",
1776
+ input
1777
+ );
1778
+ });
1779
+ }
1780
+ updateStatus(input) {
1781
+ return __async(this, null, function* () {
1782
+ return yield this.convex.mutation(
1783
+ "tasks:updateStatus",
1784
+ input
1785
+ );
1786
+ });
1787
+ }
1788
+ get(taskId) {
1789
+ return __async(this, null, function* () {
1790
+ try {
1791
+ const row = yield this.convex.query(
1792
+ "tasks:get",
1793
+ { taskId }
1794
+ );
1795
+ return row != null ? row : null;
1796
+ } catch (e) {
1797
+ return null;
1798
+ }
1799
+ });
1800
+ }
1801
+ listByAssignee(assignedTo, options) {
1802
+ return __async(this, null, function* () {
1803
+ try {
1804
+ const rows = yield this.convex.query(
1805
+ "tasks:listByAssignee",
1806
+ {
1807
+ assignedTo,
1808
+ status: options == null ? void 0 : options.status,
1809
+ limit: options == null ? void 0 : options.limit
1810
+ }
1811
+ );
1812
+ return rows != null ? rows : [];
1813
+ } catch (e) {
1814
+ return [];
1815
+ }
1816
+ });
1817
+ }
1818
+ listOpen(limit) {
1819
+ return __async(this, null, function* () {
1820
+ try {
1821
+ const rows = yield this.convex.query(
1822
+ "tasks:listOpen",
1823
+ { limit }
1824
+ );
1825
+ return rows != null ? rows : [];
1826
+ } catch (e) {
1827
+ return [];
1828
+ }
1829
+ });
1830
+ }
1831
+ /**
1832
+ * Mark a conversation task as seen by the given user. Appends `userId`
1833
+ * to the `seenBy` array on the `conversationTasks` row if not already
1834
+ * present. Replaces CommHub's `useMark_Event_Activity_SeenMutation`.
1835
+ *
1836
+ * @returns `{ ok: true }` on success.
1837
+ * @throws if the task does not exist in Convex.
1838
+ */
1839
+ markSeen(input) {
1840
+ return __async(this, null, function* () {
1841
+ return yield this.convex.mutation(
1842
+ "conversationTasks:markSeen",
1843
+ input
1844
+ );
1845
+ });
1846
+ }
1847
+ };
1848
+
1849
+ // src/resources/translation.ts
1850
+ var TranslationError = class extends Error {
1851
+ constructor(operation, status, message) {
1852
+ super(message != null ? message : `Translation ${operation} failed (HTTP ${status})`);
1853
+ this.name = "TranslationError";
1854
+ this.status = status;
1855
+ this.operation = operation;
1856
+ }
1857
+ };
1858
+ var TranslationResource = class {
1859
+ constructor(apiBaseUrl, apiKey) {
1860
+ this.baseUrl = apiBaseUrl;
1861
+ this.apiKey = apiKey;
1862
+ }
1863
+ post(path, body) {
1864
+ return __async(this, null, function* () {
1865
+ const url = `${this.baseUrl}/api${path}`;
1866
+ const res = yield fetch(url, {
1867
+ method: "POST",
1868
+ headers: {
1869
+ "Content-Type": "application/json",
1870
+ Accept: "application/json",
1871
+ "X-API-Key": this.apiKey
1872
+ },
1873
+ body: JSON.stringify(body)
1874
+ });
1875
+ if (!res.ok) {
1876
+ const text = yield res.text().catch(() => "");
1877
+ throw new TranslationError(path, res.status, text.slice(0, 200));
1878
+ }
1879
+ return yield res.json();
1880
+ });
1881
+ }
1882
+ translate(input) {
1883
+ return __async(this, null, function* () {
1884
+ return yield this.post("/translation/translate", input);
1885
+ });
1886
+ }
1887
+ translateBatch(input) {
1888
+ return __async(this, null, function* () {
1889
+ const response = yield this.post(
1890
+ "/translation/translate-batch",
1891
+ input
1892
+ );
1893
+ return response.results;
1894
+ });
1895
+ }
1896
+ detect(text) {
1897
+ return __async(this, null, function* () {
1898
+ return yield this.post("/translation/detect", { text });
1899
+ });
1900
+ }
1901
+ };
1902
+
1903
+ // src/resources/user-settings.ts
1904
+ var import_server4 = require("convex/server");
1905
+ var upsertNotificationsRef = (0, import_server4.makeFunctionReference)("userSettings:upsertNotifications");
1906
+ var UserSettingsResource = class {
1907
+ constructor(convex) {
1908
+ this.convex = convex;
1909
+ }
1910
+ /**
1911
+ * Upsert notification preferences for a user. Creates the row if it
1912
+ * doesn't exist; patches `notificationsEnabled` + `updatedAt` otherwise.
1913
+ */
1914
+ updateNotifications(input) {
1915
+ return __async(this, null, function* () {
1916
+ return this.convex.mutation(upsertNotificationsRef, input);
1917
+ });
1918
+ }
1919
+ };
1920
+
1921
+ // src/tracking/tracker.ts
1922
+ function generateUuidV7() {
1923
+ const now = Date.now();
1924
+ const timeBytes = new Uint8Array(6);
1925
+ let ts = now;
1926
+ for (let i = 5; i >= 0; i--) {
1927
+ timeBytes[i] = ts & 255;
1928
+ ts = Math.floor(ts / 256);
1929
+ }
1930
+ const randomBytes = new Uint8Array(10);
1931
+ if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
1932
+ globalThis.crypto.getRandomValues(randomBytes);
1933
+ } else {
1934
+ for (let i = 0; i < 10; i++) {
1935
+ randomBytes[i] = Math.floor(Math.random() * 256);
1936
+ }
1937
+ }
1938
+ const bytes = new Uint8Array(16);
1939
+ bytes.set(timeBytes, 0);
1940
+ bytes.set(randomBytes, 6);
1941
+ bytes[6] = bytes[6] & 15 | 112;
1942
+ bytes[8] = bytes[8] & 63 | 128;
1943
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1944
+ return [
1945
+ hex.slice(0, 8),
1946
+ hex.slice(8, 12),
1947
+ hex.slice(12, 16),
1948
+ hex.slice(16, 20),
1949
+ hex.slice(20, 32)
1950
+ ].join("-");
1951
+ }
1952
+ var API_URLS = {
1953
+ local: "http://localhost:3000",
1954
+ staging: "https://app.sandbox.communication-hub.com",
1955
+ stg: "https://app.sandbox.communication-hub.com",
1956
+ sandbox: "https://app.sandbox.communication-hub.com",
1957
+ uat: "https://app.truth.communication-hub.com",
1958
+ production: "https://app.truth.communication-hub.com"
1959
+ };
1960
+ var DEFAULT_BATCH_SIZE = 25;
1961
+ var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
1962
+ var MAX_RETRIES = 3;
1963
+ var BASE_RETRY_DELAY_MS = 500;
1964
+ var Tracker = class {
1965
+ constructor(config) {
1966
+ this.queue = [];
1967
+ this.flushTimer = null;
1968
+ this.isFlushing = false;
1969
+ this.isShutdown = false;
1970
+ var _a, _b;
1971
+ this.config = config;
1972
+ this.apiUrl = (_b = (_a = config.apiBaseUrl) != null ? _a : API_URLS[config.environment]) != null ? _b : API_URLS.local;
1973
+ this.startFlushInterval();
1974
+ this.registerShutdownHooks();
1975
+ }
1976
+ /**
1977
+ * Set the default actor context for subsequent events.
1978
+ */
1979
+ setActor(actor) {
1980
+ this.defaultActor = actor;
1981
+ }
1982
+ /**
1983
+ * Enqueue a typed event for delivery. This is fire-and-forget from
1984
+ * the caller's perspective -- events are buffered and flushed in batches.
1985
+ */
1986
+ track(eventType, payload, options) {
1987
+ var _a, _b, _c;
1988
+ if (this.isShutdown) {
1989
+ return;
1990
+ }
1991
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1992
+ const envelope = {
1993
+ event_id: generateUuidV7(),
1994
+ event_type: eventType,
1995
+ schema_version: 1,
1996
+ occurred_at: (_a = options == null ? void 0 : options.occurredAt) != null ? _a : now,
1997
+ received_at: now,
1998
+ source: this.config.source,
1999
+ source_version: this.config.sourceVersion,
2000
+ tenant_id: (_b = options == null ? void 0 : options.tenantId) != null ? _b : this.config.tenantId,
2001
+ actor: (_c = options == null ? void 0 : options.actor) != null ? _c : this.defaultActor ? {
2002
+ actor_id: this.defaultActor.actorId,
2003
+ actor_type: this.defaultActor.actorType
2004
+ } : void 0,
2005
+ subject: options == null ? void 0 : options.subject,
2006
+ compliance: options == null ? void 0 : options.compliance,
2007
+ payload
2008
+ };
2009
+ this.queue.push(envelope);
2010
+ if (this.queue.length >= this.config.batchSize) {
2011
+ void this.flush();
2012
+ }
2013
+ }
2014
+ /**
2015
+ * Force an immediate flush of all buffered events.
2016
+ * Returns a promise that resolves when the flush completes.
2017
+ */
2018
+ flush() {
2019
+ return __async(this, null, function* () {
2020
+ if (this.queue.length === 0 || this.isFlushing) {
2021
+ return;
2022
+ }
2023
+ this.isFlushing = true;
2024
+ const batch = this.queue.splice(0, this.config.batchSize);
2025
+ try {
2026
+ yield this.sendBatch(batch);
2027
+ } catch (e) {
2028
+ this.queue.unshift(...batch);
2029
+ } finally {
2030
+ this.isFlushing = false;
2031
+ }
2032
+ if (this.queue.length >= this.config.batchSize) {
2033
+ yield this.flush();
2034
+ }
2035
+ });
2036
+ }
2037
+ /**
2038
+ * Gracefully shut down the tracker. Flushes remaining events and
2039
+ * clears the flush interval.
2040
+ */
2041
+ shutdown() {
2042
+ return __async(this, null, function* () {
2043
+ this.isShutdown = true;
2044
+ this.stopFlushInterval();
2045
+ yield this.flush();
2046
+ });
2047
+ }
2048
+ /**
2049
+ * Send a batch of events to the Truth API with exponential backoff retry.
2050
+ */
2051
+ sendBatch(batch) {
2052
+ return __async(this, null, function* () {
2053
+ let lastError;
2054
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
2055
+ try {
2056
+ const response = yield fetch(`${this.apiUrl}/api/events/ingest`, {
2057
+ method: "POST",
2058
+ headers: {
2059
+ "Content-Type": "application/json",
2060
+ "X-API-Key": this.config.apiKey
2061
+ },
2062
+ body: JSON.stringify({ events: batch })
2063
+ });
2064
+ if (response.ok) {
2065
+ return;
2066
+ }
2067
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
2068
+ return;
2069
+ }
2070
+ lastError = new Error(
2071
+ `HTTP ${response.status}: ${response.statusText}`
2072
+ );
2073
+ } catch (error) {
2074
+ lastError = error;
2075
+ }
2076
+ if (attempt < MAX_RETRIES) {
2077
+ const delay = BASE_RETRY_DELAY_MS * __pow(2, attempt);
2078
+ const jitter = Math.random() * delay * 0.5;
2079
+ yield sleep(delay + jitter);
2080
+ }
2081
+ }
2082
+ throw lastError;
2083
+ });
2084
+ }
2085
+ startFlushInterval() {
2086
+ if (this.config.flushIntervalMs > 0) {
2087
+ this.flushTimer = setInterval(() => {
2088
+ void this.flush();
2089
+ }, this.config.flushIntervalMs);
2090
+ if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
2091
+ this.flushTimer.unref();
2092
+ }
2093
+ }
2094
+ }
2095
+ stopFlushInterval() {
2096
+ if (this.flushTimer !== null) {
2097
+ clearInterval(this.flushTimer);
2098
+ this.flushTimer = null;
2099
+ }
2100
+ }
2101
+ registerShutdownHooks() {
2102
+ if (typeof globalThis.process !== "undefined" && globalThis.process.on) {
2103
+ const shutdownHandler = () => {
2104
+ void this.shutdown();
2105
+ };
2106
+ globalThis.process.on("beforeExit", shutdownHandler);
2107
+ globalThis.process.on("SIGTERM", shutdownHandler);
2108
+ }
2109
+ }
2110
+ };
2111
+ function sleep(ms) {
2112
+ return new Promise((resolve) => setTimeout(resolve, ms));
2113
+ }
2114
+
2115
+ // src/web-push.ts
2116
+ function isWebPushSupported() {
2117
+ return typeof window !== "undefined" && "serviceWorker" in navigator && "PushManager" in window;
2118
+ }
2119
+ function registerServiceWorker(path = "/truth-sw.js") {
2120
+ return __async(this, null, function* () {
2121
+ return navigator.serviceWorker.register(path);
2122
+ });
2123
+ }
2124
+ function subscribeToPush(registration, vapidPublicKey) {
2125
+ return __async(this, null, function* () {
2126
+ const existing = yield registration.pushManager.getSubscription();
2127
+ if (existing) {
2128
+ return existing;
2129
+ }
2130
+ return registration.pushManager.subscribe({
2131
+ userVisibleOnly: true,
2132
+ applicationServerKey: urlBase64ToUint8Array(
2133
+ vapidPublicKey
2134
+ )
2135
+ });
2136
+ });
2137
+ }
2138
+ function subscriptionToJSON(sub) {
2139
+ var _a, _b, _c, _d;
2140
+ const json = sub.toJSON();
2141
+ return {
2142
+ endpoint: sub.endpoint,
2143
+ keys: {
2144
+ p256dh: (_b = (_a = json.keys) == null ? void 0 : _a.p256dh) != null ? _b : "",
2145
+ auth: (_d = (_c = json.keys) == null ? void 0 : _c.auth) != null ? _d : ""
2146
+ }
2147
+ };
2148
+ }
2149
+ function urlBase64ToUint8Array(base64String) {
2150
+ const padding = "=".repeat((4 - base64String.length % 4) % 4);
2151
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
2152
+ const rawData = atob(base64);
2153
+ const outputArray = new Uint8Array(rawData.length);
2154
+ for (let i = 0; i < rawData.length; ++i) {
2155
+ outputArray[i] = rawData.charCodeAt(i);
2156
+ }
2157
+ return outputArray;
2158
+ }
2159
+
2160
+ // src/client.ts
2161
+ var CONVEX_URLS = {
2162
+ local: "https://courteous-duck-623.convex.cloud",
2163
+ staging: "https://courteous-duck-623.convex.cloud",
2164
+ stg: "https://courteous-duck-623.convex.cloud",
2165
+ sandbox: "https://courteous-duck-623.convex.cloud",
2166
+ uat: "https://gallant-gecko-217.convex.cloud",
2167
+ production: "https://gallant-gecko-217.convex.cloud"
2168
+ };
2169
+ var TruthClient = class {
2170
+ constructor(config) {
2171
+ this._vapidPublicKey = null;
2172
+ this._webPushReady = null;
2173
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2174
+ const convexUrl = (_b = (_a = config.convexUrl) != null ? _a : CONVEX_URLS[config.environment]) != null ? _b : CONVEX_URLS.local;
2175
+ this.convex = new import_browser.ConvexHttpClient(convexUrl);
2176
+ this.tracker = new Tracker({
2177
+ apiKey: config.apiKey,
2178
+ environment: config.environment,
2179
+ source: (_c = config.source) != null ? _c : "unknown",
2180
+ sourceVersion: (_d = config.sourceVersion) != null ? _d : "unknown",
2181
+ tenantId: (_e = config.tenantId) != null ? _e : "",
2182
+ batchSize: (_f = config.batchSize) != null ? _f : DEFAULT_BATCH_SIZE,
2183
+ flushIntervalMs: (_g = config.flushIntervalMs) != null ? _g : DEFAULT_FLUSH_INTERVAL_MS,
2184
+ apiBaseUrl: config.apiBaseUrl
2185
+ });
2186
+ const apiUrl = this.tracker.apiUrl;
2187
+ this.patients = new PatientResource(this.convex);
2188
+ this.appointments = new AppointmentResource(this.convex);
2189
+ this.ehr = new EhrResource(apiUrl);
2190
+ this.messages = new MessagesResource(apiUrl, config.apiKey);
2191
+ this.reminders = new RemindersResource(this.convex);
2192
+ this.translation = new TranslationResource(apiUrl, config.apiKey);
2193
+ this.tasks = new TasksResource(this.convex);
2194
+ this.patientDetails = new PatientDetailsResource(apiUrl, config.apiKey);
2195
+ this.attachments = new AttachmentsResource(
2196
+ apiUrl,
2197
+ config.apiKey,
2198
+ this.convex
2199
+ );
2200
+ this.notes = new NotesResource(apiUrl, config.apiKey);
2201
+ this.physicians = new PhysiciansResource(this.convex);
2202
+ this.notifications = new NotificationsResource(apiUrl, config.apiKey);
2203
+ this.conversations = new ConversationsResource(
2204
+ apiUrl,
2205
+ config.apiKey,
2206
+ this.convex
2207
+ );
2208
+ this.userSettings = new UserSettingsResource(this.convex);
2209
+ this._serviceWorkerPath = (_h = config.serviceWorkerPath) != null ? _h : "/truth-sw.js";
2210
+ if (typeof window !== "undefined" && isWebPushSupported() && config.autoInitServiceWorker !== false) {
2211
+ this._webPushReady = this.initWebPush();
2212
+ }
2213
+ }
2214
+ get vapidPublicKey() {
2215
+ return this._vapidPublicKey;
2216
+ }
2217
+ get webPushReady() {
2218
+ return this._webPushReady;
2219
+ }
2220
+ initWebPush() {
2221
+ return __async(this, null, function* () {
2222
+ try {
2223
+ const key = yield this.notifications.getVapidKey();
2224
+ if (!key) {
2225
+ return;
2226
+ }
2227
+ this._vapidPublicKey = key;
2228
+ const registration = yield registerServiceWorker(this._serviceWorkerPath);
2229
+ yield navigator.serviceWorker.ready;
2230
+ const subscription = yield subscribeToPush(registration, key);
2231
+ const subJSON = subscriptionToJSON(subscription);
2232
+ yield this.notifications.registerDevice({
2233
+ userId: "__pending__",
2234
+ platform: "web",
2235
+ webPushSubscription: subJSON,
2236
+ locale: navigator.language,
2237
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
2238
+ });
2239
+ } catch (e) {
2240
+ }
2241
+ });
2242
+ }
2243
+ /**
2244
+ * The resolved Truth API base URL for this environment.
2245
+ * Use this when making HTTP calls to Truth's proxy endpoints
2246
+ * (e.g., EHR proxy, messages proxy).
2247
+ *
2248
+ * @example
2249
+ * ```ts
2250
+ * const url = `${truth.apiBaseUrl}/api/ehr/elation/patients/123`;
2251
+ * ```
2252
+ */
2253
+ get apiBaseUrl() {
2254
+ return this.tracker.apiUrl;
2255
+ }
2256
+ /**
2257
+ * Track an event. Fire-and-forget -- the event is buffered internally
2258
+ * and flushed in batches to the Truth API.
2259
+ *
2260
+ * @example
2261
+ * ```ts
2262
+ * truth.track('conversation.message_sent.v1', {
2263
+ * channel: 'sms',
2264
+ * direction: 'outbound',
2265
+ * message_chars: 140,
2266
+ * has_attachment: false,
2267
+ * provider_system: 'dialpad',
2268
+ * });
2269
+ * ```
2270
+ */
2271
+ track(eventType, payload, options) {
2272
+ this.tracker.track(eventType, payload, options);
2273
+ }
2274
+ /**
2275
+ * Set the default actor context for all subsequent tracked events.
2276
+ * Can be overridden per-event via TrackOptions.
2277
+ *
2278
+ * @example
2279
+ * ```ts
2280
+ * truth.identify('user_123', 'user');
2281
+ * ```
2282
+ */
2283
+ identify(actorId, actorType) {
2284
+ this.tracker.setActor({ actorId, actorType });
2285
+ }
2286
+ /**
2287
+ * Flush all buffered events immediately. Returns a Promise that resolves
2288
+ * when the flush completes. Use this for graceful shutdown.
2289
+ *
2290
+ * @example
2291
+ * ```ts
2292
+ * process.on('SIGTERM', async () => {
2293
+ * await truth.flush();
2294
+ * process.exit(0);
2295
+ * });
2296
+ * ```
2297
+ */
2298
+ flush() {
2299
+ return __async(this, null, function* () {
2300
+ yield this.tracker.flush();
2301
+ });
2302
+ }
2303
+ /**
2304
+ * Gracefully shut down the client. Flushes all pending events and
2305
+ * releases resources.
2306
+ */
2307
+ destroy() {
2308
+ return __async(this, null, function* () {
2309
+ yield this.tracker.shutdown();
2310
+ });
2311
+ }
2312
+ };
2313
+
2314
+ // src/react/provider.ts
2315
+ var CONVEX_URLS2 = {
2316
+ local: "https://courteous-duck-623.convex.cloud",
2317
+ staging: "https://courteous-duck-623.convex.cloud",
2318
+ stg: "https://courteous-duck-623.convex.cloud",
2319
+ sandbox: "https://courteous-duck-623.convex.cloud",
2320
+ uat: "https://gallant-gecko-217.convex.cloud",
2321
+ production: "https://gallant-gecko-217.convex.cloud"
2322
+ };
2323
+ var API_BASE_URLS = {
2324
+ local: "https://app.sandbox.communication-hub.com",
2325
+ staging: "https://app.sandbox.communication-hub.com",
2326
+ stg: "https://app.sandbox.communication-hub.com",
2327
+ sandbox: "https://app.sandbox.communication-hub.com",
2328
+ uat: "https://app.truth.communication-hub.com",
2329
+ production: "https://app.truth.communication-hub.com"
2330
+ };
2331
+ function resolveConvexUrl(environment, override) {
2332
+ var _a;
2333
+ if (override) {
2334
+ return override;
2335
+ }
2336
+ const env = environment != null ? environment : "sandbox";
2337
+ return (_a = CONVEX_URLS2[env]) != null ? _a : CONVEX_URLS2.sandbox;
2338
+ }
2339
+ function resolveApiBaseUrl(environment, override) {
2340
+ var _a;
2341
+ if (override) {
2342
+ return override;
2343
+ }
2344
+ const env = environment != null ? environment : "sandbox";
2345
+ return (_a = API_BASE_URLS[env]) != null ? _a : API_BASE_URLS.sandbox;
2346
+ }
2347
+ function readEnv(name) {
2348
+ if (typeof process === "undefined" || !process.env) {
2349
+ return void 0;
2350
+ }
2351
+ const v = process.env[name];
2352
+ return typeof v === "string" && v.length > 0 ? v : void 0;
2353
+ }
2354
+ var TruthSdkContext = (0, import_react6.createContext)(null);
2355
+ function useTruthSdkContext() {
2356
+ return (0, import_react6.useContext)(TruthSdkContext);
2357
+ }
2358
+ function useTruthClient() {
2359
+ const ctx = (0, import_react6.useContext)(TruthSdkContext);
2360
+ if (!ctx) {
2361
+ throw new Error(
2362
+ "useTruthClient() called outside <TruthProvider>. Wrap your app in <TruthProvider> from @hipnation-truth/sdk/react."
2363
+ );
2364
+ }
2365
+ return ctx.client;
2366
+ }
2367
+ var _activeClient = null;
2368
+ function getTruthClient() {
2369
+ if (!_activeClient) {
2370
+ throw new Error(
2371
+ "getTruthClient() called before <TruthProvider> mounted. Either wrap your app in <TruthProvider> or call useTruthClient() inside a component."
2372
+ );
2373
+ }
2374
+ return _activeClient;
2375
+ }
2376
+ function TruthProvider({
2377
+ environment = "sandbox",
2378
+ convexUrl,
2379
+ apiBaseUrl,
2380
+ apiKey,
2381
+ source,
2382
+ sourceVersion,
2383
+ tenantId,
2384
+ children
2385
+ }) {
2386
+ var _a, _b;
2387
+ const url = resolveConvexUrl(environment, convexUrl);
2388
+ const resolvedApiBaseUrl = (_a = apiBaseUrl != null ? apiBaseUrl : readEnv("EXPO_PUBLIC_TRUTH_API_BASE_URL")) != null ? _a : resolveApiBaseUrl(environment);
2389
+ const resolvedApiKey = (_b = apiKey != null ? apiKey : readEnv("EXPO_PUBLIC_TRUTH_API_KEY")) != null ? _b : "";
2390
+ const convexClient = (0, import_react6.useMemo)(() => new import_react5.ConvexReactClient(url), [url]);
2391
+ const truthClient = (0, import_react6.useMemo)(
2392
+ () => new TruthClient({
2393
+ apiKey: resolvedApiKey,
2394
+ environment,
2395
+ apiBaseUrl: resolvedApiBaseUrl,
2396
+ source: source != null ? source : "unknown",
2397
+ sourceVersion: sourceVersion != null ? sourceVersion : "unknown",
2398
+ tenantId: tenantId != null ? tenantId : "",
2399
+ autoInitServiceWorker: false
2400
+ }),
2401
+ [
2402
+ resolvedApiKey,
2403
+ resolvedApiBaseUrl,
2404
+ environment,
2405
+ source,
2406
+ sourceVersion,
2407
+ tenantId
2408
+ ]
2409
+ );
2410
+ (0, import_react6.useEffect)(() => {
2411
+ _activeClient = truthClient;
2412
+ return () => {
2413
+ if (_activeClient === truthClient) {
2414
+ _activeClient = null;
2415
+ }
2416
+ };
2417
+ }, [truthClient]);
2418
+ const sdkContext = (0, import_react6.useMemo)(
2419
+ () => ({
2420
+ apiBaseUrl: resolvedApiBaseUrl,
2421
+ apiKey: resolvedApiKey,
2422
+ environment,
2423
+ client: truthClient
2424
+ }),
2425
+ [resolvedApiBaseUrl, resolvedApiKey, environment, truthClient]
2426
+ );
2427
+ return (0, import_react6.createElement)(
2428
+ TruthSdkContext.Provider,
2429
+ { value: sdkContext },
2430
+ (0, import_react6.createElement)(import_react5.ConvexProvider, { client: convexClient }, children)
2431
+ );
2432
+ }
2433
+
2434
+ // src/react/hooks.ts
2435
+ var import_server5 = require("convex/server");
2436
+ var patientsListRef = (0, import_server5.makeFunctionReference)("patients:list");
2437
+ var patientsGetRef = (0, import_server5.makeFunctionReference)("patients:get");
2438
+ var patientsByElationIdRef = (0, import_server5.makeFunctionReference)("patients:getByElationId");
2439
+ var patientsByHintIdRef = (0, import_server5.makeFunctionReference)("patients:getByHintId");
2440
+ var appointmentsListRef = (0, import_server5.makeFunctionReference)("appointments:list");
2441
+ var appointmentsGetRef = (0, import_server5.makeFunctionReference)("appointments:get");
2442
+ var appointmentsByElationIdRef = (0, import_server5.makeFunctionReference)("appointments:getByElationId");
2443
+ function usePatients(options) {
2444
+ return (0, import_react7.useQuery)(patientsListRef, options != null ? options : {});
2445
+ }
2446
+ function usePatient(id) {
2447
+ return (0, import_react7.useQuery)(patientsGetRef, { id });
2448
+ }
2449
+ function usePatientByElationId(elationId) {
2450
+ return (0, import_react7.useQuery)(patientsByElationIdRef, {
2451
+ elationId
2452
+ });
2453
+ }
2454
+ function usePatientByHintId(hintId) {
2455
+ return (0, import_react7.useQuery)(patientsByHintIdRef, {
2456
+ hintId
2457
+ });
2458
+ }
2459
+ function useAppointments(options) {
2460
+ return (0, import_react7.useQuery)(
2461
+ appointmentsListRef,
2462
+ options != null ? options : {}
2463
+ );
2464
+ }
2465
+ function useAppointment(id) {
2466
+ return (0, import_react7.useQuery)(appointmentsGetRef, { id });
2467
+ }
2468
+ function useAppointmentByElationId(elationId) {
2469
+ return (0, import_react7.useQuery)(appointmentsByElationIdRef, {
2470
+ elationId
2471
+ });
2472
+ }
2473
+ var physiciansGetByElationIdsRef = (0, import_server5.makeFunctionReference)("physicians:getByElationIds");
2474
+ var physiciansGetByElationIdRef = (0, import_server5.makeFunctionReference)("physicians:getByElationId");
2475
+ function usePhysiciansByElationIds(ids) {
2476
+ return (0, import_react7.useQuery)(
2477
+ physiciansGetByElationIdsRef,
2478
+ ids && ids.length > 0 ? { ids } : "skip"
2479
+ );
2480
+ }
2481
+ function usePhysicianByElationId(id) {
2482
+ return (0, import_react7.useQuery)(
2483
+ physiciansGetByElationIdRef,
2484
+ id !== void 0 ? { id } : "skip"
2485
+ );
2486
+ }
2487
+ var medicationsByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getMedicationsByElationPatient");
2488
+ var problemsByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getProblemsByElationPatient");
2489
+ var allergiesByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getAllergiesByElationPatient");
2490
+ var appointmentsByPatientRef = (0, import_server5.makeFunctionReference)("medicalRecords:getAppointmentsByElationPatient");
2491
+ function usePatientMedical(elationId, options) {
2492
+ var _a, _b;
2493
+ const sdkContext = useTruthSdkContext();
2494
+ const apiBaseUrl = (_a = options == null ? void 0 : options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl;
2495
+ const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : sdkContext == null ? void 0 : sdkContext.apiKey;
2496
+ const medications = (0, import_react7.useQuery)(
2497
+ medicationsByPatientRef,
2498
+ elationId !== void 0 ? { elationPatientId: elationId } : "skip"
2499
+ );
2500
+ const problems = (0, import_react7.useQuery)(
2501
+ problemsByPatientRef,
2502
+ elationId !== void 0 ? { elationPatientId: elationId } : "skip"
2503
+ );
2504
+ const allergies = (0, import_react7.useQuery)(
2505
+ allergiesByPatientRef,
2506
+ elationId !== void 0 ? { elationPatientId: elationId } : "skip"
2507
+ );
2508
+ const appointments = (0, import_react7.useQuery)(
2509
+ appointmentsByPatientRef,
2510
+ elationId !== void 0 ? { elationPatientId: elationId } : "skip"
2511
+ );
2512
+ (0, import_react8.useEffect)(() => {
2513
+ if (elationId === void 0 || (options == null ? void 0 : options.skipRefresh)) {
2514
+ return;
2515
+ }
2516
+ if (!apiBaseUrl || !apiKey) {
2517
+ return;
2518
+ }
2519
+ const controller = new AbortController();
2520
+ void fetch(`${apiBaseUrl}/api/patients/medical/refresh`, {
2521
+ method: "POST",
2522
+ headers: {
2523
+ "Content-Type": "application/json",
2524
+ "X-API-Key": apiKey
2525
+ },
2526
+ body: JSON.stringify({ elationId }),
2527
+ signal: controller.signal
2528
+ }).catch(() => {
2529
+ });
2530
+ return () => controller.abort();
2531
+ }, [elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
2532
+ return { medications, problems, allergies, appointments };
2533
+ }
2534
+ var elationPatientByIdRef = (0, import_server5.makeFunctionReference)("elationPatients:getByElationId");
2535
+ var hintPatientByIdRef = (0, import_server5.makeFunctionReference)("hintPatients:getByHintId");
2536
+ var pharmacyByNcpdpRef = (0, import_server5.makeFunctionReference)("elationPharmacies:getByNcpdpId");
2537
+ var patientPhotoByIdRef = (0, import_server5.makeFunctionReference)("elationPatientPhotos:getByElationPatientId");
2538
+ function usePatientBasic(input, options) {
2539
+ var _a, _b, _c, _d;
2540
+ const sdkContext = useTruthSdkContext();
2541
+ const apiBaseUrl = (_a = options == null ? void 0 : options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl;
2542
+ const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : sdkContext == null ? void 0 : sdkContext.apiKey;
2543
+ const elationRow = (0, import_react7.useQuery)(
2544
+ elationPatientByIdRef,
2545
+ input.elationId !== void 0 ? { elationId: input.elationId } : "skip"
2546
+ );
2547
+ const hintRow = (0, import_react7.useQuery)(
2548
+ hintPatientByIdRef,
2549
+ input.hintId !== void 0 ? { hintId: input.hintId } : "skip"
2550
+ );
2551
+ (0, import_react8.useEffect)(() => {
2552
+ if (options == null ? void 0 : options.skipRefresh) {
2553
+ return;
2554
+ }
2555
+ if (!input.hintId && input.elationId === void 0) {
2556
+ return;
2557
+ }
2558
+ if (!apiBaseUrl || !apiKey) {
2559
+ return;
2560
+ }
2561
+ const controller = new AbortController();
2562
+ void fetch(`${apiBaseUrl}/api/patients/basic/refresh`, {
2563
+ method: "POST",
2564
+ headers: {
2565
+ "Content-Type": "application/json",
2566
+ "X-API-Key": apiKey
2567
+ },
2568
+ body: JSON.stringify({
2569
+ hintId: input.hintId,
2570
+ elationId: input.elationId
2571
+ }),
2572
+ signal: controller.signal
2573
+ }).catch(() => {
2574
+ });
2575
+ return () => controller.abort();
2576
+ }, [input.hintId, input.elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
2577
+ const elationPatient = elationRow === void 0 ? void 0 : elationRow === null ? null : (_c = elationRow.raw) != null ? _c : null;
2578
+ const hintPatient = hintRow === void 0 ? void 0 : hintRow === null ? null : (_d = hintRow.raw) != null ? _d : null;
2579
+ const elationLoading = input.elationId !== void 0 && elationRow === void 0;
2580
+ const hintLoading = input.hintId !== void 0 && hintRow === void 0;
2581
+ return {
2582
+ elationPatient,
2583
+ hintPatient,
2584
+ elationRow,
2585
+ hintRow,
2586
+ loading: elationLoading || hintLoading
2587
+ };
2588
+ }
2589
+ function usePharmacyByNcpdpId(ncpdpId) {
2590
+ return (0, import_react7.useQuery)(
2591
+ pharmacyByNcpdpRef,
2592
+ ncpdpId ? { ncpdpId } : "skip"
2593
+ );
2594
+ }
2595
+ function usePatientPhoto(elationId, options) {
2596
+ var _a, _b;
2597
+ const sdkContext = useTruthSdkContext();
2598
+ const apiBaseUrl = (_a = options == null ? void 0 : options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl;
2599
+ const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : sdkContext == null ? void 0 : sdkContext.apiKey;
2600
+ const photo = (0, import_react7.useQuery)(
2601
+ patientPhotoByIdRef,
2602
+ elationId !== void 0 ? { elationPatientId: elationId } : "skip"
2603
+ );
2604
+ (0, import_react8.useEffect)(() => {
2605
+ if (options == null ? void 0 : options.skipRefresh) {
2606
+ return;
2607
+ }
2608
+ if (elationId === void 0) {
2609
+ return;
2610
+ }
2611
+ if (!apiBaseUrl || !apiKey) {
2612
+ return;
2613
+ }
2614
+ const controller = new AbortController();
2615
+ void fetch(`${apiBaseUrl}/api/patients/photo/refresh`, {
2616
+ method: "POST",
2617
+ headers: {
2618
+ "Content-Type": "application/json",
2619
+ "X-API-Key": apiKey
2620
+ },
2621
+ body: JSON.stringify({ elationId }),
2622
+ signal: controller.signal
2623
+ }).catch(() => {
2624
+ });
2625
+ return () => controller.abort();
2626
+ }, [elationId, apiBaseUrl, apiKey, options == null ? void 0 : options.skipRefresh]);
2627
+ return photo;
2628
+ }
2629
+ var messagesByPhonesRef = (0, import_server5.makeFunctionReference)("conversationMessages:getByPhones");
2630
+ var messagesByConversationIdRef = (0, import_server5.makeFunctionReference)("conversationMessages:getByConversationId");
2631
+ function useConversationMessages(input, options) {
2632
+ const hasPair = !!input.phoneA && !!input.phoneB;
2633
+ const byPair = (0, import_react7.useQuery)(
2634
+ messagesByPhonesRef,
2635
+ hasPair ? {
2636
+ phoneA: input.phoneA,
2637
+ phoneB: input.phoneB,
2638
+ limit: options == null ? void 0 : options.limit
2639
+ } : "skip"
2640
+ );
2641
+ const byConvo = (0, import_react7.useQuery)(
2642
+ messagesByConversationIdRef,
2643
+ !hasPair && input.conversationId ? { conversationId: input.conversationId, limit: options == null ? void 0 : options.limit } : "skip"
2644
+ );
2645
+ return hasPair ? byPair : byConvo;
2646
+ }
2647
+
2648
+ // src/react/notifications.ts
2649
+ var import_react9 = require("react");
2650
+ function loadExpo() {
2651
+ return __async(this, null, function* () {
2652
+ try {
2653
+ return require("expo-notifications");
2654
+ } catch (e) {
2655
+ return null;
2656
+ }
2657
+ });
2658
+ }
2659
+ function useNotifications(options) {
2660
+ var _a, _b, _c, _d, _e;
2661
+ const sdkContext = useTruthSdkContext();
2662
+ const apiBaseUrl = (_b = (_a = options.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl) != null ? _b : "";
2663
+ const apiKey = (_d = (_c = options.apiKey) != null ? _c : sdkContext == null ? void 0 : sdkContext.apiKey) != null ? _d : "";
2664
+ const [permissionStatus, setPermissionStatus] = (0, import_react9.useState)("unknown");
2665
+ const [devicePushToken, setDevicePushToken] = (0, import_react9.useState)(null);
2666
+ const expoRef = (0, import_react9.useRef)(null);
2667
+ const isWebRef = (0, import_react9.useRef)(false);
2668
+ const vapidKeyRef = (0, import_react9.useRef)((_e = options.vapidPublicKey) != null ? _e : null);
2669
+ (0, import_react9.useEffect)(() => {
2670
+ let mounted = true;
2671
+ void (() => __async(null, null, function* () {
2672
+ var _a2;
2673
+ const expo = yield loadExpo();
2674
+ if (!mounted) {
2675
+ return;
2676
+ }
2677
+ expoRef.current = expo;
2678
+ if (expo) {
2679
+ try {
2680
+ const perm = yield expo.getPermissionsAsync();
2681
+ if (!mounted) {
2682
+ return;
2683
+ }
2684
+ setPermissionStatus(mapStatus(perm == null ? void 0 : perm.status));
2685
+ } catch (e) {
2686
+ setPermissionStatus("unknown");
2687
+ }
2688
+ return;
2689
+ }
2690
+ if (isWebPushSupported()) {
647
2691
  isWebRef.current = true;
648
2692
  if (!vapidKeyRef.current) {
649
2693
  try {
650
2694
  const res = yield fetch(
651
- `${options.apiBaseUrl}/api/notifications/vapid-key`,
2695
+ `${apiBaseUrl}/api/notifications/vapid-key`,
652
2696
  {
653
2697
  headers: {
654
2698
  Accept: "application/json",
655
- "X-API-Key": options.apiKey
2699
+ "X-API-Key": apiKey
656
2700
  }
657
2701
  }
658
2702
  );
@@ -677,9 +2721,9 @@ function useNotifications(options) {
677
2721
  return () => {
678
2722
  mounted = false;
679
2723
  };
680
- }, [options.apiBaseUrl, options.apiKey]);
681
- const register = (0, import_react7.useCallback)(() => __async(null, null, function* () {
682
- var _a2, _b;
2724
+ }, [apiBaseUrl, apiKey]);
2725
+ const register = (0, import_react9.useCallback)(() => __async(null, null, function* () {
2726
+ var _a2, _b2;
683
2727
  if (!options.userId) {
684
2728
  return { ok: false, reason: "missing_userId" };
685
2729
  }
@@ -703,12 +2747,12 @@ function useNotifications(options) {
703
2747
  const subJSON = subscriptionToJSON(subscription);
704
2748
  setDevicePushToken(subscription.endpoint);
705
2749
  const res2 = yield fetch(
706
- `${options.apiBaseUrl}/api/notifications/devices/register`,
2750
+ `${apiBaseUrl}/api/notifications/devices/register`,
707
2751
  {
708
2752
  method: "POST",
709
2753
  headers: {
710
2754
  "Content-Type": "application/json",
711
- "X-API-Key": options.apiKey
2755
+ "X-API-Key": apiKey
712
2756
  },
713
2757
  body: JSON.stringify({
714
2758
  userId: options.userId,
@@ -735,7 +2779,7 @@ function useNotifications(options) {
735
2779
  };
736
2780
  }
737
2781
  }
738
- const expo = (_b = expoRef.current) != null ? _b : yield loadExpo();
2782
+ const expo = (_b2 = expoRef.current) != null ? _b2 : yield loadExpo();
739
2783
  expoRef.current = expo;
740
2784
  if (!expo) {
741
2785
  return { ok: false, reason: "expo_notifications_missing" };
@@ -756,12 +2800,12 @@ function useNotifications(options) {
756
2800
  }
757
2801
  setDevicePushToken(nativeToken);
758
2802
  const res = yield fetch(
759
- `${options.apiBaseUrl}/api/notifications/devices/register`,
2803
+ `${apiBaseUrl}/api/notifications/devices/register`,
760
2804
  {
761
2805
  method: "POST",
762
2806
  headers: {
763
2807
  "Content-Type": "application/json",
764
- "X-API-Key": options.apiKey
2808
+ "X-API-Key": apiKey
765
2809
  },
766
2810
  body: JSON.stringify(__spreadValues({
767
2811
  userId: options.userId,
@@ -782,28 +2826,28 @@ function useNotifications(options) {
782
2826
  }
783
2827
  return { ok: true };
784
2828
  }), [
785
- options.apiBaseUrl,
786
- options.apiKey,
2829
+ apiBaseUrl,
2830
+ apiKey,
787
2831
  options.userId,
788
2832
  options.appVersion,
789
2833
  options.serviceWorkerPath
790
2834
  ]);
791
- const unregister = (0, import_react7.useCallback)(() => __async(null, null, function* () {
2835
+ const unregister = (0, import_react9.useCallback)(() => __async(null, null, function* () {
792
2836
  if (!devicePushToken) {
793
2837
  return;
794
2838
  }
795
- yield fetch(`${options.apiBaseUrl}/api/notifications/devices/unregister`, {
2839
+ yield fetch(`${apiBaseUrl}/api/notifications/devices/unregister`, {
796
2840
  method: "POST",
797
2841
  headers: {
798
2842
  "Content-Type": "application/json",
799
- "X-API-Key": options.apiKey
2843
+ "X-API-Key": apiKey
800
2844
  },
801
2845
  body: JSON.stringify({ nativeToken: devicePushToken })
802
2846
  }).catch(() => {
803
2847
  });
804
2848
  setDevicePushToken(null);
805
- }), [options.apiBaseUrl, options.apiKey, devicePushToken]);
806
- const addReceivedListener = (0, import_react7.useCallback)(
2849
+ }), [apiBaseUrl, apiKey, devicePushToken]);
2850
+ const addReceivedListener = (0, import_react9.useCallback)(
807
2851
  (listener) => {
808
2852
  if (isWebRef.current) {
809
2853
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
@@ -832,7 +2876,7 @@ function useNotifications(options) {
832
2876
  },
833
2877
  []
834
2878
  );
835
- const addResponseListener = (0, import_react7.useCallback)(
2879
+ const addResponseListener = (0, import_react9.useCallback)(
836
2880
  (listener) => {
837
2881
  if (isWebRef.current) {
838
2882
  if (typeof navigator === "undefined" || !("serviceWorker" in navigator)) {
@@ -861,7 +2905,7 @@ function useNotifications(options) {
861
2905
  },
862
2906
  []
863
2907
  );
864
- const getBadgeCount = (0, import_react7.useCallback)(() => __async(null, null, function* () {
2908
+ const getBadgeCount = (0, import_react9.useCallback)(() => __async(null, null, function* () {
865
2909
  var _a2;
866
2910
  const expo = expoRef.current;
867
2911
  if (!(expo == null ? void 0 : expo.getBadgeCountAsync)) {
@@ -869,7 +2913,7 @@ function useNotifications(options) {
869
2913
  }
870
2914
  return (_a2 = yield expo.getBadgeCountAsync()) != null ? _a2 : 0;
871
2915
  }), []);
872
- const setBadgeCount = (0, import_react7.useCallback)((count) => __async(null, null, function* () {
2916
+ const setBadgeCount = (0, import_react9.useCallback)((count) => __async(null, null, function* () {
873
2917
  const expo = expoRef.current;
874
2918
  if (!(expo == null ? void 0 : expo.setBadgeCountAsync)) {
875
2919
  return;
@@ -877,7 +2921,7 @@ function useNotifications(options) {
877
2921
  yield expo.setBadgeCountAsync(count);
878
2922
  }), []);
879
2923
  const autoRegister = options.autoRegister !== false;
880
- (0, import_react7.useEffect)(() => {
2924
+ (0, import_react9.useEffect)(() => {
881
2925
  if (!autoRegister) {
882
2926
  return;
883
2927
  }
@@ -909,6 +2953,71 @@ function useNotifications(options) {
909
2953
  setBadgeCount
910
2954
  };
911
2955
  }
2956
+ function useNotificationsActions() {
2957
+ var _a, _b;
2958
+ const sdkContext = useTruthSdkContext();
2959
+ const apiBaseUrl = (_a = sdkContext == null ? void 0 : sdkContext.apiBaseUrl) != null ? _a : "";
2960
+ const apiKey = (_b = sdkContext == null ? void 0 : sdkContext.apiKey) != null ? _b : "";
2961
+ const post = (0, import_react9.useCallback)(
2962
+ (path, body) => __async(null, null, function* () {
2963
+ const res = yield fetch(`${apiBaseUrl}/api${path}`, {
2964
+ method: "POST",
2965
+ headers: {
2966
+ "Content-Type": "application/json",
2967
+ Accept: "application/json",
2968
+ "X-API-Key": apiKey
2969
+ },
2970
+ body: JSON.stringify(body)
2971
+ });
2972
+ if (!res.ok) {
2973
+ const text = yield res.text().catch(() => "");
2974
+ throw new Error(
2975
+ `Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`
2976
+ );
2977
+ }
2978
+ return yield res.json();
2979
+ }),
2980
+ [apiBaseUrl, apiKey]
2981
+ );
2982
+ const get = (0, import_react9.useCallback)(
2983
+ (path) => __async(null, null, function* () {
2984
+ const res = yield fetch(`${apiBaseUrl}/api${path}`, {
2985
+ method: "GET",
2986
+ headers: { Accept: "application/json", "X-API-Key": apiKey }
2987
+ });
2988
+ if (!res.ok) {
2989
+ const text = yield res.text().catch(() => "");
2990
+ throw new Error(
2991
+ `Truth API ${path} failed (${res.status}): ${text.slice(0, 200)}`
2992
+ );
2993
+ }
2994
+ return yield res.json();
2995
+ }),
2996
+ [apiBaseUrl, apiKey]
2997
+ );
2998
+ const send = (0, import_react9.useCallback)(
2999
+ (input) => post("/notifications/send", input),
3000
+ [post]
3001
+ );
3002
+ const schedule = (0, import_react9.useCallback)(
3003
+ (input) => post("/notifications/schedule", input),
3004
+ [post]
3005
+ );
3006
+ const getPreferences = (0, import_react9.useCallback)(
3007
+ (userId) => get(
3008
+ `/notifications/preferences/${encodeURIComponent(userId)}`
3009
+ ),
3010
+ [get]
3011
+ );
3012
+ const updatePreferences = (0, import_react9.useCallback)(
3013
+ (userId, prefs) => post(
3014
+ `/notifications/preferences/${encodeURIComponent(userId)}`,
3015
+ prefs
3016
+ ),
3017
+ [post]
3018
+ );
3019
+ return { send, schedule, getPreferences, updatePreferences };
3020
+ }
912
3021
  function mapStatus(status) {
913
3022
  if (status === "granted") {
914
3023
  return "granted";
@@ -935,16 +3044,16 @@ function detectPlatform(tokenType) {
935
3044
  }
936
3045
 
937
3046
  // src/react/patient-family.ts
938
- var import_react8 = require("convex/react");
939
- var import_server5 = require("convex/server");
940
- var patientsFamilyMembersRef = (0, import_server5.makeFunctionReference)("patients:listFamilyMembers");
3047
+ var import_react10 = require("convex/react");
3048
+ var import_server6 = require("convex/server");
3049
+ var patientsFamilyMembersRef = (0, import_server6.makeFunctionReference)("patients:listFamilyMembers");
941
3050
  var SKIP3 = "skip";
942
3051
  function usePatientFamilyMembers(input) {
943
3052
  const hasFamilyId = !!(input == null ? void 0 : input.familyId);
944
3053
  const hasPhoneNumbers = !!((input == null ? void 0 : input.phoneNumbers) && input.phoneNumbers.length > 0);
945
3054
  const shouldQuery = hasFamilyId || hasPhoneNumbers;
946
3055
  const args = shouldQuery ? __spreadValues(__spreadValues(__spreadValues({}, (input == null ? void 0 : input.familyId) ? { familyId: input.familyId } : {}), (input == null ? void 0 : input.phoneNumbers) && input.phoneNumbers.length > 0 ? { phoneNumbers: input.phoneNumbers } : {}), (input == null ? void 0 : input.excludeHintId) ? { excludeHintId: input.excludeHintId } : {}) : SKIP3;
947
- const result = (0, import_react8.useQuery)(
3056
+ const result = (0, import_react10.useQuery)(
948
3057
  patientsFamilyMembersRef,
949
3058
  args
950
3059
  );
@@ -959,15 +3068,15 @@ function usePatientFamilyMembers(input) {
959
3068
  }
960
3069
 
961
3070
  // src/react/patient-search.ts
962
- var import_react9 = require("convex/react");
963
- var import_server6 = require("convex/server");
964
- var patientsSearchRef = (0, import_server6.makeFunctionReference)("patients:search");
3071
+ var import_react11 = require("convex/react");
3072
+ var import_server7 = require("convex/server");
3073
+ var patientsSearchRef = (0, import_server7.makeFunctionReference)("patients:search");
965
3074
  var SKIP4 = "skip";
966
3075
  function usePatientSearch(options) {
967
3076
  var _a;
968
3077
  const trimmedQuery = ((_a = options.query) != null ? _a : "").trim();
969
3078
  const skipped = trimmedQuery.length === 0;
970
- const result = (0, import_react9.useQuery)(
3079
+ const result = (0, import_react11.useQuery)(
971
3080
  patientsSearchRef,
972
3081
  skipped ? SKIP4 : __spreadValues(__spreadValues(__spreadValues({
973
3082
  query: trimmedQuery
@@ -984,24 +3093,26 @@ function usePatientSearch(options) {
984
3093
  }
985
3094
 
986
3095
  // src/react/patients-bulk.ts
987
- var import_react10 = require("convex/react");
988
- var import_server7 = require("convex/server");
989
- var import_react11 = require("react");
990
- var patientsGetByIdsRef = (0, import_server7.makeFunctionReference)("patients:getByIds");
991
- var patientsGetByPhonesRef = (0, import_server7.makeFunctionReference)("patients:getByPhones");
3096
+ var import_react12 = require("convex/react");
3097
+ var import_server8 = require("convex/server");
3098
+ var import_react13 = require("react");
3099
+ var patientsGetByIdsRef = (0, import_server8.makeFunctionReference)("patients:getByIds");
3100
+ var patientsGetByPhonesRef = (0, import_server8.makeFunctionReference)("patients:getByPhones");
992
3101
  var SKIP5 = "skip";
993
3102
  function usePatientsByIds(ids) {
994
- const stableIds = (0, import_react11.useMemo)(() => {
3103
+ const stableIds = (0, import_react13.useMemo)(() => {
995
3104
  const arr = ids != null ? ids : [];
996
3105
  return [...new Set(arr)].sort();
997
3106
  }, [ids]);
998
3107
  const skipped = stableIds.length === 0;
999
- const result = (0, import_react10.useQuery)(
3108
+ const result = (0, import_react12.useQuery)(
1000
3109
  patientsGetByIdsRef,
1001
3110
  skipped ? SKIP5 : { ids: stableIds }
1002
3111
  );
1003
- const mapped = (0, import_react11.useMemo)(() => {
1004
- if (result === void 0) return void 0;
3112
+ const mapped = (0, import_react13.useMemo)(() => {
3113
+ if (result === void 0) {
3114
+ return void 0;
3115
+ }
1005
3116
  return Object.fromEntries(
1006
3117
  result.map((p) => {
1007
3118
  var _a, _b;
@@ -1020,18 +3131,20 @@ function usePatientsByIds(ids) {
1020
3131
  };
1021
3132
  }
1022
3133
  function usePatientsByPhones(phones) {
1023
- const stableDigits = (0, import_react11.useMemo)(() => {
3134
+ const stableDigits = (0, import_react13.useMemo)(() => {
1024
3135
  const arr = phones != null ? phones : [];
1025
3136
  const digits = arr.map((p) => p.replace(/\D+/g, "")).filter((s) => s.length > 0);
1026
3137
  return [...new Set(digits)].sort();
1027
3138
  }, [phones]);
1028
3139
  const skipped = stableDigits.length === 0;
1029
- const result = (0, import_react10.useQuery)(
3140
+ const result = (0, import_react12.useQuery)(
1030
3141
  patientsGetByPhonesRef,
1031
3142
  skipped ? SKIP5 : { phoneDigits: stableDigits }
1032
3143
  );
1033
- const mapped = (0, import_react11.useMemo)(() => {
1034
- if (result === void 0) return void 0;
3144
+ const mapped = (0, import_react13.useMemo)(() => {
3145
+ if (result === void 0) {
3146
+ return void 0;
3147
+ }
1035
3148
  return Object.fromEntries(result.map((r) => [r.phoneDigits, r.patient]));
1036
3149
  }, [result]);
1037
3150
  if (skipped) {
@@ -1044,39 +3157,10 @@ function usePatientsByPhones(phones) {
1044
3157
  };
1045
3158
  }
1046
3159
 
1047
- // src/react/provider.ts
1048
- var import_react12 = require("convex/react");
1049
- var import_react13 = require("react");
1050
- var CONVEX_URLS = {
1051
- local: "https://courteous-duck-623.convex.cloud",
1052
- staging: "https://courteous-duck-623.convex.cloud",
1053
- stg: "https://courteous-duck-623.convex.cloud",
1054
- sandbox: "https://courteous-duck-623.convex.cloud",
1055
- uat: "https://gallant-gecko-217.convex.cloud",
1056
- production: "https://gallant-gecko-217.convex.cloud"
1057
- };
1058
- function resolveConvexUrl(environment, override) {
1059
- var _a;
1060
- if (override) {
1061
- return override;
1062
- }
1063
- const env = environment != null ? environment : "sandbox";
1064
- return (_a = CONVEX_URLS[env]) != null ? _a : CONVEX_URLS.sandbox;
1065
- }
1066
- function TruthProvider({
1067
- environment = "sandbox",
1068
- convexUrl,
1069
- children
1070
- }) {
1071
- const url = resolveConvexUrl(environment, convexUrl);
1072
- const client = (0, import_react13.useMemo)(() => new import_react12.ConvexReactClient(url), [url]);
1073
- return (0, import_react13.createElement)(import_react12.ConvexProvider, { client }, children);
1074
- }
1075
-
1076
3160
  // src/react/reminders.ts
1077
3161
  var import_react14 = require("convex/react");
1078
- var import_server8 = require("convex/server");
1079
- var remindersListPendingByConversationIdsRef = (0, import_server8.makeFunctionReference)("reminders:listPendingByConversationIds");
3162
+ var import_server9 = require("convex/server");
3163
+ var remindersListPendingByConversationIdsRef = (0, import_server9.makeFunctionReference)("reminders:listPendingByConversationIds");
1080
3164
  var SKIP6 = "skip";
1081
3165
  function useRemindersForConversations(conversationIds) {
1082
3166
  const ids = conversationIds != null ? conversationIds : [];
@@ -1098,9 +3182,9 @@ function useRemindersForConversations(conversationIds) {
1098
3182
 
1099
3183
  // src/react/tasks.ts
1100
3184
  var import_react15 = require("convex/react");
1101
- var import_server9 = require("convex/server");
3185
+ var import_server10 = require("convex/server");
1102
3186
  var import_react16 = require("react");
1103
- var conversationTasksMarkSeenRef = (0, import_server9.makeFunctionReference)("conversationTasks:markSeen");
3187
+ var conversationTasksMarkSeenRef = (0, import_server10.makeFunctionReference)("conversationTasks:markSeen");
1104
3188
  function useConversationTaskMarkSeen() {
1105
3189
  const mutate = (0, import_react15.useMutation)(
1106
3190
  conversationTasksMarkSeenRef
@@ -1113,200 +3197,6 @@ function useConversationTaskMarkSeen() {
1113
3197
 
1114
3198
  // src/react/tracking.ts
1115
3199
  var import_react17 = require("react");
1116
-
1117
- // src/tracking/tracker.ts
1118
- function generateUuidV7() {
1119
- const now = Date.now();
1120
- const timeBytes = new Uint8Array(6);
1121
- let ts = now;
1122
- for (let i = 5; i >= 0; i--) {
1123
- timeBytes[i] = ts & 255;
1124
- ts = Math.floor(ts / 256);
1125
- }
1126
- const randomBytes = new Uint8Array(10);
1127
- if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.getRandomValues) {
1128
- globalThis.crypto.getRandomValues(randomBytes);
1129
- } else {
1130
- for (let i = 0; i < 10; i++) {
1131
- randomBytes[i] = Math.floor(Math.random() * 256);
1132
- }
1133
- }
1134
- const bytes = new Uint8Array(16);
1135
- bytes.set(timeBytes, 0);
1136
- bytes.set(randomBytes, 6);
1137
- bytes[6] = bytes[6] & 15 | 112;
1138
- bytes[8] = bytes[8] & 63 | 128;
1139
- const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1140
- return [
1141
- hex.slice(0, 8),
1142
- hex.slice(8, 12),
1143
- hex.slice(12, 16),
1144
- hex.slice(16, 20),
1145
- hex.slice(20, 32)
1146
- ].join("-");
1147
- }
1148
- var API_URLS = {
1149
- local: "http://localhost:3000",
1150
- staging: "https://app.sandbox.communication-hub.com",
1151
- stg: "https://app.sandbox.communication-hub.com",
1152
- sandbox: "https://app.sandbox.communication-hub.com",
1153
- uat: "https://app.truth.communication-hub.com",
1154
- production: "https://app.truth.communication-hub.com"
1155
- };
1156
- var MAX_RETRIES = 3;
1157
- var BASE_RETRY_DELAY_MS = 500;
1158
- var Tracker = class {
1159
- constructor(config) {
1160
- this.queue = [];
1161
- this.flushTimer = null;
1162
- this.isFlushing = false;
1163
- this.isShutdown = false;
1164
- var _a, _b;
1165
- this.config = config;
1166
- this.apiUrl = (_b = (_a = config.apiBaseUrl) != null ? _a : API_URLS[config.environment]) != null ? _b : API_URLS.local;
1167
- this.startFlushInterval();
1168
- this.registerShutdownHooks();
1169
- }
1170
- /**
1171
- * Set the default actor context for subsequent events.
1172
- */
1173
- setActor(actor) {
1174
- this.defaultActor = actor;
1175
- }
1176
- /**
1177
- * Enqueue a typed event for delivery. This is fire-and-forget from
1178
- * the caller's perspective -- events are buffered and flushed in batches.
1179
- */
1180
- track(eventType, payload, options) {
1181
- var _a, _b, _c;
1182
- if (this.isShutdown) {
1183
- return;
1184
- }
1185
- const now = (/* @__PURE__ */ new Date()).toISOString();
1186
- const envelope = {
1187
- event_id: generateUuidV7(),
1188
- event_type: eventType,
1189
- schema_version: 1,
1190
- occurred_at: (_a = options == null ? void 0 : options.occurredAt) != null ? _a : now,
1191
- received_at: now,
1192
- source: this.config.source,
1193
- source_version: this.config.sourceVersion,
1194
- tenant_id: (_b = options == null ? void 0 : options.tenantId) != null ? _b : this.config.tenantId,
1195
- actor: (_c = options == null ? void 0 : options.actor) != null ? _c : this.defaultActor ? {
1196
- actor_id: this.defaultActor.actorId,
1197
- actor_type: this.defaultActor.actorType
1198
- } : void 0,
1199
- subject: options == null ? void 0 : options.subject,
1200
- compliance: options == null ? void 0 : options.compliance,
1201
- payload
1202
- };
1203
- this.queue.push(envelope);
1204
- if (this.queue.length >= this.config.batchSize) {
1205
- void this.flush();
1206
- }
1207
- }
1208
- /**
1209
- * Force an immediate flush of all buffered events.
1210
- * Returns a promise that resolves when the flush completes.
1211
- */
1212
- flush() {
1213
- return __async(this, null, function* () {
1214
- if (this.queue.length === 0 || this.isFlushing) {
1215
- return;
1216
- }
1217
- this.isFlushing = true;
1218
- const batch = this.queue.splice(0, this.config.batchSize);
1219
- try {
1220
- yield this.sendBatch(batch);
1221
- } catch (e) {
1222
- this.queue.unshift(...batch);
1223
- } finally {
1224
- this.isFlushing = false;
1225
- }
1226
- if (this.queue.length >= this.config.batchSize) {
1227
- yield this.flush();
1228
- }
1229
- });
1230
- }
1231
- /**
1232
- * Gracefully shut down the tracker. Flushes remaining events and
1233
- * clears the flush interval.
1234
- */
1235
- shutdown() {
1236
- return __async(this, null, function* () {
1237
- this.isShutdown = true;
1238
- this.stopFlushInterval();
1239
- yield this.flush();
1240
- });
1241
- }
1242
- /**
1243
- * Send a batch of events to the Truth API with exponential backoff retry.
1244
- */
1245
- sendBatch(batch) {
1246
- return __async(this, null, function* () {
1247
- let lastError;
1248
- for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
1249
- try {
1250
- const response = yield fetch(`${this.apiUrl}/api/events/ingest`, {
1251
- method: "POST",
1252
- headers: {
1253
- "Content-Type": "application/json",
1254
- "X-API-Key": this.config.apiKey
1255
- },
1256
- body: JSON.stringify({ events: batch })
1257
- });
1258
- if (response.ok) {
1259
- return;
1260
- }
1261
- if (response.status >= 400 && response.status < 500 && response.status !== 429) {
1262
- return;
1263
- }
1264
- lastError = new Error(
1265
- `HTTP ${response.status}: ${response.statusText}`
1266
- );
1267
- } catch (error) {
1268
- lastError = error;
1269
- }
1270
- if (attempt < MAX_RETRIES) {
1271
- const delay = BASE_RETRY_DELAY_MS * __pow(2, attempt);
1272
- const jitter = Math.random() * delay * 0.5;
1273
- yield sleep(delay + jitter);
1274
- }
1275
- }
1276
- throw lastError;
1277
- });
1278
- }
1279
- startFlushInterval() {
1280
- if (this.config.flushIntervalMs > 0) {
1281
- this.flushTimer = setInterval(() => {
1282
- void this.flush();
1283
- }, this.config.flushIntervalMs);
1284
- if (typeof this.flushTimer === "object" && "unref" in this.flushTimer) {
1285
- this.flushTimer.unref();
1286
- }
1287
- }
1288
- }
1289
- stopFlushInterval() {
1290
- if (this.flushTimer !== null) {
1291
- clearInterval(this.flushTimer);
1292
- this.flushTimer = null;
1293
- }
1294
- }
1295
- registerShutdownHooks() {
1296
- if (typeof globalThis.process !== "undefined" && globalThis.process.on) {
1297
- const shutdownHandler = () => {
1298
- void this.shutdown();
1299
- };
1300
- globalThis.process.on("beforeExit", shutdownHandler);
1301
- globalThis.process.on("SIGTERM", shutdownHandler);
1302
- }
1303
- }
1304
- };
1305
- function sleep(ms) {
1306
- return new Promise((resolve) => setTimeout(resolve, ms));
1307
- }
1308
-
1309
- // src/react/tracking.ts
1310
3200
  var TruthTrackingContext = (0, import_react17.createContext)(
1311
3201
  null
1312
3202
  );
@@ -1315,12 +3205,14 @@ function TruthTrackingProvider({
1315
3205
  source = "communication-hub.frontend",
1316
3206
  sourceVersion = "unknown",
1317
3207
  tenantId = "hipnation",
1318
- apiKey = "",
3208
+ apiKey,
1319
3209
  children
1320
3210
  }) {
3211
+ var _a, _b;
3212
+ const resolvedApiKey = (_b = apiKey != null ? apiKey : typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a.EXPO_PUBLIC_TRUTH_API_KEY : void 0) != null ? _b : "";
1321
3213
  const value = (0, import_react17.useMemo)(() => {
1322
3214
  const tracker = new Tracker({
1323
- apiKey,
3215
+ apiKey: resolvedApiKey,
1324
3216
  environment,
1325
3217
  source,
1326
3218
  sourceVersion,
@@ -1336,7 +3228,7 @@ function TruthTrackingProvider({
1336
3228
  tracker.setActor({ actorId, actorType });
1337
3229
  }
1338
3230
  };
1339
- }, [apiKey, environment, source, sourceVersion, tenantId]);
3231
+ }, [resolvedApiKey, environment, source, sourceVersion, tenantId]);
1340
3232
  return (0, import_react17.createElement)(TruthTrackingContext.Provider, { value }, children);
1341
3233
  }
1342
3234
  function useTruth() {
@@ -1349,8 +3241,8 @@ function useTruth() {
1349
3241
 
1350
3242
  // src/react/user-settings.ts
1351
3243
  var import_react18 = require("convex/react");
1352
- var import_server10 = require("convex/server");
1353
- var userSettingsGetByUserIdRef = (0, import_server10.makeFunctionReference)("userSettings:getByUserId");
3244
+ var import_server11 = require("convex/server");
3245
+ var userSettingsGetByUserIdRef = (0, import_server11.makeFunctionReference)("userSettings:getByUserId");
1354
3246
  var SKIP7 = "skip";
1355
3247
  function useUserSettings(userId) {
1356
3248
  const skip = !userId;
@@ -1368,16 +3260,103 @@ function useUserSettings(userId) {
1368
3260
  };
1369
3261
  }
1370
3262
 
1371
- // src/react/voicemail.ts
3263
+ // src/react/users.ts
1372
3264
  var import_react19 = require("react");
1373
- function useVoicemailUrl(client) {
1374
- const [url, setUrl] = (0, import_react19.useState)(null);
1375
- const [isLoading, setIsLoading] = (0, import_react19.useState)(false);
3265
+ function useUserSync(input) {
3266
+ var _a, _b, _c, _d;
3267
+ const sdkContext = useTruthSdkContext();
3268
+ const apiBaseUrl = (_b = (_a = input.apiBaseUrl) != null ? _a : sdkContext == null ? void 0 : sdkContext.apiBaseUrl) != null ? _b : "";
3269
+ const apiKey = (_d = (_c = input.apiKey) != null ? _c : sdkContext == null ? void 0 : sdkContext.apiKey) != null ? _d : "";
3270
+ const [status, setStatus] = (0, import_react19.useState)("idle");
1376
3271
  const [error, setError] = (0, import_react19.useState)(null);
1377
- const inFlightRef = (0, import_react19.useRef)(false);
1378
- const fetchUrl = (0, import_react19.useCallback)(
3272
+ const lastKeyRef = (0, import_react19.useRef)(null);
3273
+ const sync = () => __async(null, null, function* () {
3274
+ if (!input.userId) {
3275
+ return { ok: false, reason: "missing_userId" };
3276
+ }
3277
+ if (!apiBaseUrl || !apiKey) {
3278
+ return { ok: false, reason: "missing_truth_config" };
3279
+ }
3280
+ setStatus("syncing");
3281
+ setError(null);
3282
+ try {
3283
+ const res = yield fetch(`${apiBaseUrl}/api/users/sync`, {
3284
+ method: "POST",
3285
+ headers: {
3286
+ "Content-Type": "application/json",
3287
+ "X-API-Key": apiKey
3288
+ },
3289
+ body: JSON.stringify({
3290
+ userId: input.userId,
3291
+ email: input.email,
3292
+ name: input.name,
3293
+ firstName: input.firstName,
3294
+ lastName: input.lastName,
3295
+ imageUrl: input.imageUrl,
3296
+ notificationsEnabled: input.notificationsEnabled
3297
+ })
3298
+ });
3299
+ if (!res.ok) {
3300
+ const text = yield res.text().catch(() => "");
3301
+ const reason = `sync_failed_${res.status}: ${text.slice(0, 120)}`;
3302
+ setStatus("error");
3303
+ setError(reason);
3304
+ return { ok: false, reason };
3305
+ }
3306
+ setStatus("synced");
3307
+ return { ok: true };
3308
+ } catch (err) {
3309
+ const message = err instanceof Error ? err.message : String(err);
3310
+ setStatus("error");
3311
+ setError(message);
3312
+ return { ok: false, reason: message };
3313
+ }
3314
+ });
3315
+ (0, import_react19.useEffect)(() => {
3316
+ var _a2, _b2, _c2, _d2, _e;
3317
+ if (!input.userId) {
3318
+ return;
3319
+ }
3320
+ const key = [
3321
+ input.userId,
3322
+ (_a2 = input.email) != null ? _a2 : "",
3323
+ (_b2 = input.name) != null ? _b2 : "",
3324
+ (_c2 = input.firstName) != null ? _c2 : "",
3325
+ (_d2 = input.lastName) != null ? _d2 : "",
3326
+ (_e = input.imageUrl) != null ? _e : "",
3327
+ input.notificationsEnabled === void 0 ? "" : String(input.notificationsEnabled)
3328
+ ].join("|");
3329
+ if (key === lastKeyRef.current) {
3330
+ return;
3331
+ }
3332
+ lastKeyRef.current = key;
3333
+ void sync();
3334
+ }, [
3335
+ apiBaseUrl,
3336
+ apiKey,
3337
+ input.userId,
3338
+ input.email,
3339
+ input.name,
3340
+ input.firstName,
3341
+ input.lastName,
3342
+ input.imageUrl,
3343
+ input.notificationsEnabled
3344
+ ]);
3345
+ return { status, error, sync };
3346
+ }
3347
+
3348
+ // src/react/voicemail.ts
3349
+ var import_react20 = require("react");
3350
+ function useVoicemailUrl(client) {
3351
+ const [url, setUrl] = (0, import_react20.useState)(null);
3352
+ const [isLoading, setIsLoading] = (0, import_react20.useState)(false);
3353
+ const [error, setError] = (0, import_react20.useState)(null);
3354
+ const inFlightRef = (0, import_react20.useRef)(false);
3355
+ const fetchUrl = (0, import_react20.useCallback)(
1379
3356
  (voicemailLink) => __async(null, null, function* () {
1380
- if (inFlightRef.current) return null;
3357
+ if (inFlightRef.current) {
3358
+ return null;
3359
+ }
1381
3360
  inFlightRef.current = true;
1382
3361
  setIsLoading(true);
1383
3362
  setError(null);
@@ -1401,12 +3380,17 @@ function useVoicemailUrl(client) {
1401
3380
  // Annotate the CommonJS export names for ESM import in node:
1402
3381
  0 && (module.exports = {
1403
3382
  ACTIVE_CALL_STATES,
3383
+ API_BASE_URLS,
1404
3384
  CONNECTED_CALL_STATES,
3385
+ CONVEX_URLS,
1405
3386
  DialpadCallState,
1406
3387
  RINGING_CALL_STATES,
1407
3388
  TERMINAL_CALL_STATES,
1408
3389
  TruthProvider,
1409
3390
  TruthTrackingProvider,
3391
+ getTruthClient,
3392
+ resolveApiBaseUrl,
3393
+ resolveConvexUrl,
1410
3394
  useActiveCalls,
1411
3395
  useAppointment,
1412
3396
  useAppointmentByElationId,
@@ -1426,6 +3410,7 @@ function useVoicemailUrl(client) {
1426
3410
  useDialpadCallsForConversation,
1427
3411
  useMessages,
1428
3412
  useNotifications,
3413
+ useNotificationsActions,
1429
3414
  usePatient,
1430
3415
  usePatientBasic,
1431
3416
  usePatientByElationId,
@@ -1442,9 +3427,12 @@ function useVoicemailUrl(client) {
1442
3427
  usePhysiciansByElationIds,
1443
3428
  useRemindersForConversations,
1444
3429
  useTruth,
3430
+ useTruthClient,
3431
+ useTruthSdkContext,
1445
3432
  useUnreadAggregate,
1446
3433
  useUnreadCount,
1447
3434
  useUserSettings,
3435
+ useUserSync,
1448
3436
  useVoicemailUrl
1449
3437
  });
1450
3438
  //# sourceMappingURL=react.js.map