vintasend-medplum 0.4.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,8 +11,61 @@ This implementation uses FHIR (Fast Healthcare Interoperability Resources) stand
11
11
  - **MedplumNotificationBackend**: Stores notifications as FHIR `Communication` resources
12
12
  - **MedplumNotificationAdapter**: Sends email notifications via Medplum's email API
13
13
  - **MedplumAttachmentManager**: Manages file attachments using FHIR `Binary` and `Media` resources
14
+ - **PugInlineEmailTemplateRenderer**: Renders Pug email templates from pre-compiled JSON (ideal for production)
14
15
  - **MedplumLogger**: Simple console-based logger
15
16
 
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ # Install the package
21
+ npm install vintasend-medplum @medplum/core
22
+
23
+ # Compile your Pug templates
24
+ npx compile-pug-templates ./templates ./src/compiled-templates.json
25
+ ```
26
+
27
+ ```typescript
28
+ import { MedplumClient } from '@medplum/core';
29
+ import {
30
+ MedplumNotificationBackend,
31
+ MedplumNotificationAdapter,
32
+ PugInlineEmailTemplateRenderer,
33
+ MedplumLogger
34
+ } from 'vintasend-medplum';
35
+ import { NotificationService } from 'vintasend';
36
+ import compiledTemplates from './compiled-templates.json';
37
+
38
+ // Initialize Medplum client
39
+ const medplum = new MedplumClient({
40
+ baseUrl: 'https://api.medplum.com',
41
+ clientId: 'your-client-id',
42
+ clientSecret: 'your-client-secret',
43
+ });
44
+
45
+ // Create services
46
+ const renderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
47
+ const adapter = new MedplumNotificationAdapter(medplum, renderer);
48
+ const backend = new MedplumNotificationBackend(medplum);
49
+ const logger = new MedplumLogger();
50
+
51
+ // Initialize notification service
52
+ const notificationService = new NotificationService(backend, [adapter], logger);
53
+
54
+ // Send a notification
55
+ await notificationService.createNotification({
56
+ userId: 'Patient/123',
57
+ notificationType: 'EMAIL',
58
+ contextName: 'welcome',
59
+ contextParameters: { firstName: 'John' },
60
+ title: 'Welcome!',
61
+ bodyTemplate: 'welcome.pug',
62
+ subjectTemplate: 'subjects/welcome.pug',
63
+ sendAfter: new Date(),
64
+ });
65
+
66
+ await notificationService.processPendingNotifications();
67
+ ```
68
+
16
69
  ## How It Works
17
70
 
18
71
  ### FHIR Resource Mapping
@@ -94,6 +147,76 @@ npm install vintasend-medplum @medplum/core
94
147
 
95
148
  ## Setup
96
149
 
150
+ ### Template Compilation
151
+
152
+ VintaSend Medplum uses pre-compiled Pug email templates that are embedded in your application as JSON. This approach ensures templates are bundled with your code and don't require file system access at runtime.
153
+
154
+ **Step 1: Organize Your Templates**
155
+
156
+ Create a directory structure for your templates:
157
+ ```
158
+ templates/
159
+ welcome.pug
160
+ password-reset.pug
161
+ notifications/
162
+ order-confirmation.pug
163
+ shipment-update.pug
164
+ ```
165
+
166
+ **Step 2: Compile Templates**
167
+
168
+ Run the compilation script using npx:
169
+
170
+ ```bash
171
+ npx compile-pug-templates [input-directory] [output-file]
172
+ ```
173
+
174
+ Both arguments are optional:
175
+ - `input-directory`: Directory containing .pug templates (default: `./templates`)
176
+ - `output-file`: Output JSON file path (default: `compiled-templates.json`)
177
+
178
+ Examples:
179
+ ```bash
180
+ # Use default values (./templates → compiled-templates.json)
181
+ npx compile-pug-templates
182
+
183
+ # Specify only input directory (output to compiled-templates.json)
184
+ npx compile-pug-templates ./email-templates
185
+
186
+ # Specify both arguments
187
+ npx compile-pug-templates ./templates ./src/compiled-templates.json
188
+ ```
189
+
190
+ Or add it to your package.json scripts:
191
+ ```json
192
+ {
193
+ "scripts": {
194
+ "compile-templates": "compile-pug-templates ./templates ./src/compiled-templates.json"
195
+ }
196
+ }
197
+ ```
198
+
199
+ This generates a JSON file where keys are relative paths and values are template contents:
200
+ ```json
201
+ {
202
+ "welcome.pug": "doctype html\nhtml\n body\n h1 Welcome {{firstName}}!",
203
+ "notifications/order-confirmation.pug": "doctype html\n..."
204
+ }
205
+ ```
206
+
207
+ **Step 3: Import and Use Compiled Templates**
208
+
209
+ ```typescript
210
+ import { PugInlineEmailTemplateRenderer } from 'vintasend-medplum';
211
+ import compiledTemplates from './compiled-templates.json';
212
+
213
+ // Create the template renderer with compiled templates
214
+ const templateRenderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
215
+
216
+ // Use with notification adapter
217
+ const adapter = new MedplumNotificationAdapter(medplum, templateRenderer);
218
+ ```
219
+
97
220
  ### Basic Configuration
98
221
 
99
222
  ```typescript
@@ -151,6 +274,56 @@ const backend = new MedplumNotificationBackend(medplum, {
151
274
 
152
275
  ## Usage Examples
153
276
 
277
+ ### Configuring Templates
278
+
279
+ When creating notifications, reference your templates using the same paths used during compilation:
280
+
281
+ ```typescript
282
+ // If you compiled templates/welcome.pug
283
+ await notificationService.createNotification({
284
+ userId: 'Patient/123',
285
+ notificationType: 'EMAIL',
286
+ contextName: 'user-welcome',
287
+ contextParameters: {
288
+ firstName: 'John',
289
+ lastName: 'Doe',
290
+ },
291
+ title: 'Welcome to our platform!',
292
+ bodyTemplate: 'welcome.pug', // Path from compiled templates
293
+ subjectTemplate: 'subjects/welcome.pug', // Can be in subdirectories
294
+ sendAfter: new Date(),
295
+ });
296
+
297
+ // If you compiled templates/notifications/order-confirmation.pug
298
+ await notificationService.createNotification({
299
+ userId: 'Patient/456',
300
+ notificationType: 'EMAIL',
301
+ contextName: 'order-confirmation',
302
+ contextParameters: {
303
+ orderNumber: '12345',
304
+ totalAmount: '$99.99',
305
+ },
306
+ title: 'Order Confirmation',
307
+ bodyTemplate: 'notifications/order-confirmation.pug',
308
+ subjectTemplate: 'notifications/subjects/order-confirmation.pug',
309
+ sendAfter: new Date(),
310
+ });
311
+ ```
312
+
313
+ **Template Example (welcome.pug):**
314
+ ```pug
315
+ doctype html
316
+ html
317
+ head
318
+ title Welcome
319
+ body
320
+ h1 Welcome #{firstName} #{lastName}!
321
+ p Thank you for joining our platform.
322
+ p
323
+ | If you have any questions, feel free to
324
+ a(href="mailto:support@example.com") contact us
325
+ ```
326
+
154
327
  ### Sending a Simple Notification
155
328
 
156
329
  ```typescript
@@ -395,6 +568,34 @@ constructor(
395
568
  **Key Methods:**
396
569
  - `send(notification, context)` - Send an email notification with attachments
397
570
 
571
+ ### PugInlineEmailTemplateRenderer
572
+
573
+ ```typescript
574
+ class PugInlineEmailTemplateRenderer<Config extends BaseNotificationTypeConfig>
575
+ implements BaseEmailTemplateRenderer<Config>
576
+ ```
577
+
578
+ Template renderer that compiles Pug templates from pre-compiled JSON strings instead of reading from file paths. This is ideal for production deployments where templates are embedded in the application.
579
+
580
+ **Constructor:**
581
+ ```typescript
582
+ constructor(generatedTemplates: Record<string, string>)
583
+ ```
584
+
585
+ **Parameters:**
586
+ - `generatedTemplates` - Object mapping template paths to template content strings (generated by `compile-pug-templates` script)
587
+
588
+ **Key Methods:**
589
+ - `render(notification, context)` - Compile and render both subject and body templates using the notification's template paths
590
+
591
+ **Example:**
592
+ ```typescript
593
+ import compiledTemplates from './compiled-templates.json';
594
+
595
+ const renderer = new PugInlineEmailTemplateRenderer(compiledTemplates);
596
+ const adapter = new MedplumNotificationAdapter(medplum, renderer);
597
+ ```
598
+
398
599
  ### MedplumAttachmentManager
399
600
 
400
601
  ```typescript
@@ -430,7 +631,70 @@ Provides access to files stored in FHIR Binary resources.
430
631
 
431
632
  ## Best Practices
432
633
 
433
- ### 1. Use FHIR References Consistently
634
+ ### 1. Template Organization
635
+
636
+ Organize your templates in a clear directory structure:
637
+
638
+ ```
639
+ templates/
640
+ subjects/ # Email subject templates
641
+ welcome.pug
642
+ order-confirmation.pug
643
+ bodies/ # Or organize by feature
644
+ welcome.pug
645
+ order-confirmation.pug
646
+ notifications/ # Group related templates
647
+ orders/
648
+ confirmation.pug
649
+ shipped.pug
650
+ users/
651
+ welcome.pug
652
+ password-reset.pug
653
+ ```
654
+
655
+ ### 2. Template Naming Conventions
656
+
657
+ Use consistent, descriptive names:
658
+ ```typescript
659
+ // ✅ Good - clear and descriptive
660
+ bodyTemplate: 'notifications/orders/confirmation.pug'
661
+ subjectTemplate: 'subjects/order-confirmation.pug'
662
+
663
+ // ❌ Bad - unclear purpose
664
+ bodyTemplate: 'template1.pug'
665
+ subjectTemplate: 'subj.pug'
666
+ ```
667
+
668
+ ### 3. Template Variables
669
+
670
+ Document the expected context variables in each template:
671
+
672
+ ```pug
673
+ //- templates/welcome.pug
674
+ //- Expected variables: firstName, lastName, loginUrl
675
+ doctype html
676
+ html
677
+ body
678
+ h1 Welcome #{firstName} #{lastName}!
679
+ a(href=loginUrl) Login to your account
680
+ ```
681
+
682
+ ### 4. Compilation in Build Process
683
+
684
+ Add template compilation to your build pipeline:
685
+
686
+ ```json
687
+ {
688
+ "scripts": {
689
+ "compile-templates": "compile-pug-templates ./templates ./src/compiled-templates.json",
690
+ "build": "npm run compile-templates && tsc"
691
+ }
692
+ }
693
+ ```
694
+
695
+ This ensures templates are always compiled before building your application.
696
+
697
+ ### 5. Use FHIR References Consistently
434
698
 
435
699
  Always use proper FHIR reference format for user IDs:
436
700
 
@@ -444,7 +708,7 @@ userId: '123'
444
708
  userId: 'user-456'
445
709
  ```
446
710
 
447
- ### 2. Configure Custom Extension URLs
711
+ ### 6. Configure Custom Extension URLs
448
712
 
449
713
  Use your own domain for extension URLs in production:
450
714
 
@@ -454,7 +718,7 @@ const backend = new MedplumNotificationBackend(medplum, {
454
718
  });
455
719
  ```
456
720
 
457
- ### 3. Handle Large Attachments Carefully
721
+ ### 7. Handle Large Attachments Carefully
458
722
 
459
723
  For large files, consider:
460
724
  - Using file size limits
@@ -469,7 +733,7 @@ if (buffer.length > maxSize) {
469
733
  }
470
734
  ```
471
735
 
472
- ### 4. Clean Up Orphaned Files
736
+ ### 8. Clean Up Orphaned Files
473
737
 
474
738
  Regularly run cleanup to remove orphaned attachment files:
475
739
 
@@ -483,7 +747,7 @@ for (const file of orphaned) {
483
747
  }
484
748
  ```
485
749
 
486
- ### 5. Use Pagination
750
+ ### 9. Use Pagination
487
751
 
488
752
  Always paginate when fetching large result sets:
489
753
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { MedplumAttachmentManager, MedplumAttachmentFile } from './medplum-attachment-manager';
2
2
  export { MedplumNotificationBackend, MedplumNotificationBackendFactory } from './medplum-backend';
3
3
  export { MedplumNotificationAdapter } from './medplum-adapter';
4
+ export { PugInlineEmailTemplateRenderer, PugInlineEmailTemplateRendererFactory } from './pug-inline-email-template-renderer';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,OAAO,EAAE,0BAA0B,EAAE,iCAAiC,EAAE,MAAM,mBAAmB,CAAC;AAClG,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,8BAA8B,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAC"}
package/dist/index.js CHANGED
@@ -1,11 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MedplumNotificationAdapter = exports.MedplumNotificationBackendFactory = exports.MedplumNotificationBackend = exports.MedplumAttachmentFile = exports.MedplumAttachmentManager = void 0;
4
- var medplum_attachment_manager_1 = require("./medplum-attachment-manager");
5
- Object.defineProperty(exports, "MedplumAttachmentManager", { enumerable: true, get: function () { return medplum_attachment_manager_1.MedplumAttachmentManager; } });
6
- Object.defineProperty(exports, "MedplumAttachmentFile", { enumerable: true, get: function () { return medplum_attachment_manager_1.MedplumAttachmentFile; } });
7
- var medplum_backend_1 = require("./medplum-backend");
8
- Object.defineProperty(exports, "MedplumNotificationBackend", { enumerable: true, get: function () { return medplum_backend_1.MedplumNotificationBackend; } });
9
- Object.defineProperty(exports, "MedplumNotificationBackendFactory", { enumerable: true, get: function () { return medplum_backend_1.MedplumNotificationBackendFactory; } });
10
- var medplum_adapter_1 = require("./medplum-adapter");
11
- Object.defineProperty(exports, "MedplumNotificationAdapter", { enumerable: true, get: function () { return medplum_adapter_1.MedplumNotificationAdapter; } });
1
+ export { MedplumAttachmentManager, MedplumAttachmentFile } from './medplum-attachment-manager';
2
+ export { MedplumNotificationBackend, MedplumNotificationBackendFactory } from './medplum-backend';
3
+ export { MedplumNotificationAdapter } from './medplum-adapter';
4
+ export { PugInlineEmailTemplateRenderer, PugInlineEmailTemplateRendererFactory } from './pug-inline-email-template-renderer';
@@ -17,3 +17,4 @@ export declare class MedplumNotificationAdapter<TemplateRenderer extends BaseEma
17
17
  contentType: string;
18
18
  }>>;
19
19
  }
20
+ //# sourceMappingURL=medplum-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"medplum-adapter.d.ts","sourceRoot":"","sources":["../src/medplum-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,sFAAsF,CAAC;AACtI,OAAO,EAAE,uBAAuB,EAAE,MAAM,yEAAyE,CAAC;AAClH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AACjF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AAExE,qBAAa,0BAA0B,CACrC,gBAAgB,SAAS,yBAAyB,CAAC,MAAM,CAAC,EAC1D,MAAM,SAAS,0BAA0B,CACzC,SAAQ,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAIvD,OAAO,CAAC,OAAO;IAHV,GAAG,EAAE,MAAM,GAAG,IAAI,CAAmB;gBAGlC,OAAO,EAAE,aAAa,EAC9B,gBAAgB,EAAE,gBAAgB;IAMpC,IAAI,mBAAmB,IAAI,OAAO,CAEjC;IAEK,IAAI,CAAC,YAAY,EAAE,uBAAuB,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;cA0C7E,kBAAkB,CAChC,WAAW,EAAE,gBAAgB,EAAE,GAC9B,OAAO,CAAC,KAAK,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CAYJ"}
@@ -1,8 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MedplumNotificationAdapter = void 0;
4
- const base_notification_adapter_1 = require("vintasend/dist/services/notification-adapters/base-notification-adapter");
5
- class MedplumNotificationAdapter extends base_notification_adapter_1.BaseNotificationAdapter {
1
+ import { BaseNotificationAdapter } from 'vintasend/dist/services/notification-adapters/base-notification-adapter';
2
+ export class MedplumNotificationAdapter extends BaseNotificationAdapter {
6
3
  constructor(medplum, templateRenderer) {
7
4
  const notificationType = 'EMAIL';
8
5
  super(templateRenderer, notificationType, false);
@@ -46,4 +43,3 @@ class MedplumNotificationAdapter extends base_notification_adapter_1.BaseNotific
46
43
  }));
47
44
  }
48
45
  }
49
- exports.MedplumNotificationAdapter = MedplumNotificationAdapter;
@@ -80,3 +80,4 @@ export declare class MedplumAttachmentFile implements AttachmentFile {
80
80
  */
81
81
  delete(): Promise<void>;
82
82
  }
83
+ //# sourceMappingURL=medplum-attachment-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"medplum-attachment-manager.d.ts","sourceRoot":"","sources":["../src/medplum-attachment-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,oEAAoE,CAAC;AAC3G,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,cAAc,EACf,MAAM,iCAAiC,CAAC;AAEzC;;;;;;;;GAQG;AACH,qBAAa,wBAAyB,SAAQ,qBAAqB;IACrD,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,aAAa;IAI1C;;;;;;;OAOG;IACG,UAAU,CACd,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,CAAC;IA6DhC;;;;;OAKG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IA0BnE;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB/C;;;;;OAKG;IACH,yBAAyB,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc;CAgBpF;AAED;;;;GAIG;AACH,qBAAa,qBAAsB,YAAW,cAAc;IAIxD,OAAO,CAAC,OAAO;IAHjB,OAAO,CAAC,QAAQ,CAAS;gBAGf,OAAO,EAAE,aAAa,EAC9B,SAAS,EAAE,MAAM;IAUnB;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;IAQ7B;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC;IAUvC;;;;;;;;OAQG;IACG,GAAG,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5C;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9B"}
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MedplumAttachmentFile = exports.MedplumAttachmentManager = void 0;
4
- const base_attachment_manager_1 = require("vintasend/dist/services/attachment-manager/base-attachment-manager");
1
+ import { BaseAttachmentManager } from 'vintasend/dist/services/attachment-manager/base-attachment-manager';
5
2
  /**
6
3
  * Medplum AttachmentManager implementation.
7
4
  *
@@ -11,7 +8,7 @@ const base_attachment_manager_1 = require("vintasend/dist/services/attachment-ma
11
8
  *
12
9
  * Files are uploaded to Medplum's storage and can be accessed via URLs.
13
10
  */
14
- class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentManager {
11
+ export class MedplumAttachmentManager extends BaseAttachmentManager {
15
12
  constructor(medplum) {
16
13
  super();
17
14
  this.medplum = medplum;
@@ -25,7 +22,6 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
25
22
  * @returns AttachmentFileRecord with metadata about the uploaded file
26
23
  */
27
24
  async uploadFile(file, filename, contentType) {
28
- var _a, _b;
29
25
  // Convert file to Buffer
30
26
  const buffer = await this.fileToBuffer(file);
31
27
  // Calculate checksum for deduplication
@@ -74,8 +70,8 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
74
70
  url: binaryUrl,
75
71
  creation: createdMedia.content.creation,
76
72
  },
77
- createdAt: ((_a = createdMedia.meta) === null || _a === void 0 ? void 0 : _a.lastUpdated) ? new Date(createdMedia.meta.lastUpdated) : new Date(),
78
- updatedAt: ((_b = createdMedia.meta) === null || _b === void 0 ? void 0 : _b.lastUpdated) ? new Date(createdMedia.meta.lastUpdated) : new Date(),
73
+ createdAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
74
+ updatedAt: createdMedia.meta?.lastUpdated ? new Date(createdMedia.meta.lastUpdated) : new Date(),
79
75
  };
80
76
  }
81
77
  /**
@@ -85,7 +81,6 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
85
81
  * @returns The file record or null if not found
86
82
  */
87
83
  async getFile(fileId) {
88
- var _a, _b, _c, _d;
89
84
  try {
90
85
  const media = await this.medplum.readResource('Media', fileId);
91
86
  if (!media.content || !media.id) {
@@ -96,16 +91,16 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
96
91
  filename: media.content.title || 'untitled',
97
92
  contentType: media.content.contentType || 'application/octet-stream',
98
93
  size: media.content.size || 0,
99
- checksum: ((_b = (_a = media.identifier) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) || '',
94
+ checksum: media.identifier?.[0]?.value || '',
100
95
  storageMetadata: {
101
96
  url: media.content.url,
102
97
  creation: media.content.creation,
103
98
  },
104
- createdAt: ((_c = media.meta) === null || _c === void 0 ? void 0 : _c.lastUpdated) ? new Date(media.meta.lastUpdated) : new Date(),
105
- updatedAt: ((_d = media.meta) === null || _d === void 0 ? void 0 : _d.lastUpdated) ? new Date(media.meta.lastUpdated) : new Date(),
99
+ createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
100
+ updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
106
101
  };
107
102
  }
108
- catch (_e) {
103
+ catch {
109
104
  return null;
110
105
  }
111
106
  }
@@ -117,11 +112,10 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
117
112
  * @param fileId - The Media resource ID
118
113
  */
119
114
  async deleteFile(fileId) {
120
- var _a;
121
115
  // Get Media resource to find the Binary URL
122
116
  const media = await this.medplum.readResource('Media', fileId);
123
117
  // Extract Binary ID from URL
124
- const binaryUrl = (_a = media.content) === null || _a === void 0 ? void 0 : _a.url;
118
+ const binaryUrl = media.content?.url;
125
119
  if (binaryUrl) {
126
120
  const match = binaryUrl.match(/Binary\/([^/]+)/);
127
121
  if (match) {
@@ -162,13 +156,12 @@ class MedplumAttachmentManager extends base_attachment_manager_1.BaseAttachmentM
162
156
  return new MedplumAttachmentFile(this.medplum, binaryUrl);
163
157
  }
164
158
  }
165
- exports.MedplumAttachmentManager = MedplumAttachmentManager;
166
159
  /**
167
160
  * Medplum AttachmentFile implementation.
168
161
  *
169
162
  * Provides access to files stored in Medplum Binary resources.
170
163
  */
171
- class MedplumAttachmentFile {
164
+ export class MedplumAttachmentFile {
172
165
  constructor(medplum, binaryUrl) {
173
166
  this.medplum = medplum;
174
167
  // Extract Binary ID from URL (format: "Binary/{id}")
@@ -225,4 +218,3 @@ class MedplumAttachmentFile {
225
218
  await this.medplum.deleteResource('Binary', this.binaryId);
226
219
  }
227
220
  }
228
- exports.MedplumAttachmentFile = MedplumAttachmentFile;
@@ -84,3 +84,4 @@ export declare class MedplumNotificationBackendFactory<Config extends BaseNotifi
84
84
  create(medplum: MedplumClient): MedplumNotificationBackend<Config>;
85
85
  }
86
86
  export {};
87
+ //# sourceMappingURL=medplum-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"medplum-backend.d.ts","sourceRoot":"","sources":["../src/medplum-backend.ts"],"names":[],"mappings":"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"}
@@ -1,40 +1,4 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.MedplumNotificationBackendFactory = exports.MedplumNotificationBackend = void 0;
37
- class MedplumNotificationBackend {
1
+ export class MedplumNotificationBackend {
38
2
  constructor(medplum, options = {
39
3
  emailNotificationSubjectExtensionUrl: 'http://vintasend.com/fhir/StructureDefinition/email-notification-subject',
40
4
  }) {
@@ -57,27 +21,26 @@ class MedplumNotificationBackend {
57
21
  return this.attachmentManager;
58
22
  }
59
23
  mapToDatabaseNotification(communication) {
60
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
61
24
  const notificationId = communication.id;
62
- const referenceString = (_b = (_a = communication.recipient) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.reference;
25
+ const referenceString = communication.recipient?.[0]?.reference;
63
26
  // Extract subjectTemplate from payload extension
64
- const subjectTemplateExtension = (_e = (_d = (_c = communication.payload) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.extension) === null || _e === void 0 ? void 0 : _e.find((ext) => ext.url === this.options.emailNotificationSubjectExtensionUrl);
65
- const subjectTemplate = (subjectTemplateExtension === null || subjectTemplateExtension === void 0 ? void 0 : subjectTemplateExtension.valueString) || null;
27
+ const subjectTemplateExtension = communication.payload?.[0]?.extension?.find((ext) => ext.url === this.options.emailNotificationSubjectExtensionUrl);
28
+ const subjectTemplate = subjectTemplateExtension?.valueString || null;
66
29
  // Extract one-off notification fields from extensions
67
- const emailOrPhoneExtension = (_f = communication.extension) === null || _f === void 0 ? void 0 : _f.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/emailOrPhone');
68
- const firstNameExtension = (_g = communication.extension) === null || _g === void 0 ? void 0 : _g.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/firstName');
69
- const lastNameExtension = (_h = communication.extension) === null || _h === void 0 ? void 0 : _h.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/lastName');
70
- const emailOrPhone = emailOrPhoneExtension === null || emailOrPhoneExtension === void 0 ? void 0 : emailOrPhoneExtension.valueString;
71
- const firstName = firstNameExtension === null || firstNameExtension === void 0 ? void 0 : firstNameExtension.valueString;
72
- const lastName = lastNameExtension === null || lastNameExtension === void 0 ? void 0 : lastNameExtension.valueString;
30
+ const emailOrPhoneExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/emailOrPhone');
31
+ const firstNameExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/firstName');
32
+ const lastNameExtension = communication.extension?.find((ext) => ext.url === 'http://vintasend.com/fhir/StructureDefinition/lastName');
33
+ const emailOrPhone = emailOrPhoneExtension?.valueString;
34
+ const firstName = firstNameExtension?.valueString;
35
+ const lastName = lastNameExtension?.valueString;
73
36
  const baseNotification = {
74
37
  id: notificationId,
75
- notificationType: (_l = (_k = (_j = communication.meta) === null || _j === void 0 ? void 0 : _j.tag) === null || _k === void 0 ? void 0 : _k[2]) === null || _l === void 0 ? void 0 : _l.code,
76
- title: ((_m = communication.topic) === null || _m === void 0 ? void 0 : _m.text) || null,
77
- contextName: (_q = (_p = (_o = communication.meta) === null || _o === void 0 ? void 0 : _o.tag) === null || _p === void 0 ? void 0 : _p[1]) === null || _q === void 0 ? void 0 : _q.code,
78
- contextParameters: JSON.parse(((_s = (_r = communication.note) === null || _r === void 0 ? void 0 : _r[0]) === null || _s === void 0 ? void 0 : _s.text) || '{}'),
38
+ notificationType: communication.meta?.tag?.[2]?.code,
39
+ title: communication.topic?.text || null,
40
+ contextName: communication.meta?.tag?.[1]?.code,
41
+ contextParameters: JSON.parse(communication.note?.[0]?.text || '{}'),
79
42
  sendAfter: communication.sent ? new Date(communication.sent) : null,
80
- bodyTemplate: ((_u = (_t = communication.payload) === null || _t === void 0 ? void 0 : _t[0]) === null || _u === void 0 ? void 0 : _u.contentString) || '',
43
+ bodyTemplate: communication.payload?.[0]?.contentString || '',
81
44
  subjectTemplate: subjectTemplate,
82
45
  extraParams: {},
83
46
  status: communication.status === 'completed' ? 'SENT' : communication.status === 'stopped' ? 'FAILED' : 'PENDING_SEND',
@@ -85,8 +48,8 @@ class MedplumNotificationBackend {
85
48
  adapterUsed: null,
86
49
  sentAt: communication.status === 'completed' ? new Date(communication.sent || new Date()) : null,
87
50
  readAt: null,
88
- createdAt: new Date(((_v = communication.meta) === null || _v === void 0 ? void 0 : _v.lastUpdated) || new Date()),
89
- updatedAt: new Date(((_w = communication.meta) === null || _w === void 0 ? void 0 : _w.lastUpdated) || new Date()),
51
+ createdAt: new Date(communication.meta?.lastUpdated || new Date()),
52
+ updatedAt: new Date(communication.meta?.lastUpdated || new Date()),
90
53
  };
91
54
  // Check if this is a one-off notification
92
55
  if (emailOrPhone && firstName && lastName) {
@@ -168,7 +131,6 @@ class MedplumNotificationBackend {
168
131
  .filter((n) => 'userId' in n);
169
132
  }
170
133
  async persistNotification(notification) {
171
- var _a;
172
134
  // Build base payload with body template
173
135
  const payload = notification.bodyTemplate
174
136
  ? [
@@ -190,7 +152,7 @@ class MedplumNotificationBackend {
190
152
  const communication = {
191
153
  resourceType: 'Communication',
192
154
  status: 'in-progress',
193
- sent: (_a = notification.sendAfter) === null || _a === void 0 ? void 0 : _a.toISOString(),
155
+ sent: notification.sendAfter?.toISOString(),
194
156
  topic: { text: notification.title || undefined },
195
157
  payload,
196
158
  recipient: [{ reference: notification.userId }],
@@ -235,13 +197,12 @@ class MedplumNotificationBackend {
235
197
  return communications.map((comm) => this.mapToDatabaseNotification(comm));
236
198
  }
237
199
  async bulkPersistNotifications(notifications) {
238
- var _a;
239
200
  const ids = [];
240
201
  for (const notification of notifications) {
241
202
  const communication = {
242
203
  resourceType: 'Communication',
243
204
  status: 'in-progress',
244
- sent: (_a = notification.sendAfter) === null || _a === void 0 ? void 0 : _a.toISOString(),
205
+ sent: notification.sendAfter?.toISOString(),
245
206
  topic: { text: notification.title || undefined },
246
207
  payload: notification.bodyTemplate
247
208
  ? [
@@ -270,7 +231,7 @@ class MedplumNotificationBackend {
270
231
  }
271
232
  async markAsSent(notificationId, checkIsPending = true) {
272
233
  const notification = await this.getNotification(notificationId, false);
273
- if (checkIsPending && (notification === null || notification === void 0 ? void 0 : notification.status) !== 'PENDING_SEND') {
234
+ if (checkIsPending && notification?.status !== 'PENDING_SEND') {
274
235
  throw new Error('Notification is not pending');
275
236
  }
276
237
  // Use internal update that returns AnyDatabaseNotification
@@ -288,7 +249,7 @@ class MedplumNotificationBackend {
288
249
  }
289
250
  async markAsFailed(notificationId, checkIsPending = true) {
290
251
  const notification = await this.getNotification(notificationId, false);
291
- if (checkIsPending && (notification === null || notification === void 0 ? void 0 : notification.status) !== 'PENDING_SEND') {
252
+ if (checkIsPending && notification?.status !== 'PENDING_SEND') {
292
253
  throw new Error('Notification is not pending');
293
254
  }
294
255
  // Use internal update that returns AnyDatabaseNotification
@@ -306,7 +267,7 @@ class MedplumNotificationBackend {
306
267
  }
307
268
  async markAsRead(notificationId, checkIsSent = true) {
308
269
  const notification = await this.getNotification(notificationId, false);
309
- if (checkIsSent && (notification === null || notification === void 0 ? void 0 : notification.status) !== 'SENT') {
270
+ if (checkIsSent && notification?.status !== 'SENT') {
310
271
  throw new Error('Notification is not sent');
311
272
  }
312
273
  // Ensure it's a regular notification (not one-off)
@@ -325,7 +286,7 @@ class MedplumNotificationBackend {
325
286
  const communication = await this.medplum.readResource('Communication', notificationId);
326
287
  return this.mapToDatabaseNotification(communication);
327
288
  }
328
- catch (_a) {
289
+ catch {
329
290
  return null;
330
291
  }
331
292
  }
@@ -352,10 +313,9 @@ class MedplumNotificationBackend {
352
313
  .filter((notif) => 'userId' in notif && !notif.readAt);
353
314
  }
354
315
  async getUserEmailFromNotification(notificationId) {
355
- var _a, _b, _c, _d;
356
316
  try {
357
317
  const communication = await this.medplum.readResource('Communication', notificationId);
358
- const practitionerRef = (_b = (_a = communication.recipient) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.reference;
318
+ const practitionerRef = communication.recipient?.[0]?.reference;
359
319
  if (!practitionerRef) {
360
320
  return undefined;
361
321
  }
@@ -366,7 +326,7 @@ class MedplumNotificationBackend {
366
326
  return undefined;
367
327
  }
368
328
  const resource = await this.medplum.readResource(resourceType, id);
369
- return (_d = (_c = resource.telecom) === null || _c === void 0 ? void 0 : _c.find((t) => t.system === 'email')) === null || _d === void 0 ? void 0 : _d.value;
329
+ return resource.telecom?.find((t) => t.system === 'email')?.value;
370
330
  }
371
331
  catch (error) {
372
332
  // eslint-disable-next-line no-console
@@ -384,7 +344,6 @@ class MedplumNotificationBackend {
384
344
  }
385
345
  /* One-off notification methods */
386
346
  async persistOneOffNotification(notification) {
387
- var _a;
388
347
  // Build base payload with body template
389
348
  const payload = notification.bodyTemplate
390
349
  ? [
@@ -406,7 +365,7 @@ class MedplumNotificationBackend {
406
365
  const communication = {
407
366
  resourceType: 'Communication',
408
367
  status: 'in-progress',
409
- sent: (_a = notification.sendAfter) === null || _a === void 0 ? void 0 : _a.toISOString(),
368
+ sent: notification.sendAfter?.toISOString(),
410
369
  topic: { text: notification.title || undefined },
411
370
  payload,
412
371
  note: [{ text: JSON.stringify(notification.contextParameters) }],
@@ -475,7 +434,7 @@ class MedplumNotificationBackend {
475
434
  }
476
435
  if (typeof file === 'string') {
477
436
  // File path - read from filesystem
478
- const fs = await Promise.resolve().then(() => __importStar(require('node:fs/promises')));
437
+ const fs = await import('node:fs/promises');
479
438
  return await fs.readFile(file);
480
439
  }
481
440
  // ReadableStream or Readable
@@ -507,7 +466,6 @@ class MedplumNotificationBackend {
507
466
  * Helper: Convert FHIR Media resource to AttachmentFileRecord
508
467
  */
509
468
  mediaToAttachmentFileRecord(media) {
510
- var _a, _b, _c, _d;
511
469
  const content = media.content;
512
470
  if (!content || !media.id) {
513
471
  throw new Error('Invalid Media resource: missing content or id');
@@ -517,21 +475,20 @@ class MedplumNotificationBackend {
517
475
  filename: content.title || 'untitled',
518
476
  contentType: content.contentType || 'application/octet-stream',
519
477
  size: content.size || 0,
520
- checksum: ((_b = (_a = media.identifier) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value) || '',
478
+ checksum: media.identifier?.[0]?.value || '',
521
479
  storageMetadata: {
522
480
  url: content.url,
523
481
  creation: content.creation,
524
482
  },
525
- createdAt: ((_c = media.meta) === null || _c === void 0 ? void 0 : _c.lastUpdated) ? new Date(media.meta.lastUpdated) : new Date(),
526
- updatedAt: ((_d = media.meta) === null || _d === void 0 ? void 0 : _d.lastUpdated) ? new Date(media.meta.lastUpdated) : new Date(),
483
+ createdAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
484
+ updatedAt: media.meta?.lastUpdated ? new Date(media.meta.lastUpdated) : new Date(),
527
485
  };
528
486
  }
529
487
  /**
530
488
  * Helper: Create FHIR Attachment from file record
531
489
  */
532
490
  async createFhirAttachment(fileRecord) {
533
- var _a;
534
- const url = (_a = fileRecord.storageMetadata) === null || _a === void 0 ? void 0 : _a.url;
491
+ const url = fileRecord.storageMetadata?.url;
535
492
  return {
536
493
  contentType: fileRecord.contentType,
537
494
  url: url,
@@ -552,7 +509,7 @@ class MedplumNotificationBackend {
552
509
  const media = await this.medplum.readResource('Media', fileId);
553
510
  return this.mediaToAttachmentFileRecord(media);
554
511
  }
555
- catch (_a) {
512
+ catch {
556
513
  return null;
557
514
  }
558
515
  }
@@ -564,7 +521,7 @@ class MedplumNotificationBackend {
564
521
  }
565
522
  return this.mediaToAttachmentFileRecord(results[0]);
566
523
  }
567
- catch (_a) {
524
+ catch {
568
525
  return null;
569
526
  }
570
527
  }
@@ -572,7 +529,6 @@ class MedplumNotificationBackend {
572
529
  * Find multiple attachment files by their checksums in a single query
573
530
  */
574
531
  async findAttachmentFilesByChecksums(checksums) {
575
- var _a, _b;
576
532
  if (checksums.length === 0) {
577
533
  return new Map();
578
534
  }
@@ -583,14 +539,14 @@ class MedplumNotificationBackend {
583
539
  // Map results by checksum
584
540
  const filesByChecksum = new Map();
585
541
  for (const media of results) {
586
- const checksum = (_b = (_a = media.identifier) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.value;
542
+ const checksum = media.identifier?.[0]?.value;
587
543
  if (checksum) {
588
544
  filesByChecksum.set(checksum, this.mediaToAttachmentFileRecord(media));
589
545
  }
590
546
  }
591
547
  return filesByChecksum;
592
548
  }
593
- catch (_c) {
549
+ catch {
594
550
  return new Map();
595
551
  }
596
552
  }
@@ -684,7 +640,6 @@ class MedplumNotificationBackend {
684
640
  return payload;
685
641
  }
686
642
  async deleteAttachmentFile(fileId) {
687
- var _a;
688
643
  // Check if file exists first
689
644
  const fileRecord = await this.getAttachmentFile(fileId);
690
645
  if (!fileRecord) {
@@ -695,7 +650,7 @@ class MedplumNotificationBackend {
695
650
  const communications = await this.medplum.searchResources('Communication', {
696
651
  _tag: 'notification',
697
652
  });
698
- const isReferenced = communications.some((comm) => { var _a; return (_a = comm.payload) === null || _a === void 0 ? void 0 : _a.some((p) => { var _a, _b; return (_b = (_a = p.contentAttachment) === null || _a === void 0 ? void 0 : _a.url) === null || _b === void 0 ? void 0 : _b.includes(fileId); }); });
653
+ const isReferenced = communications.some((comm) => comm.payload?.some((p) => p.contentAttachment?.url?.includes(fileId)));
699
654
  if (isReferenced) {
700
655
  throw new Error('Cannot delete attachment file: still referenced by notifications');
701
656
  }
@@ -704,12 +659,12 @@ class MedplumNotificationBackend {
704
659
  await this.attachmentManager.deleteFile(fileId);
705
660
  }
706
661
  // Delete Binary resource if it exists
707
- if ((_a = fileRecord.storageMetadata) === null || _a === void 0 ? void 0 : _a.url) {
662
+ if (fileRecord.storageMetadata?.url) {
708
663
  const binaryId = fileRecord.storageMetadata.url.replace('Binary/', '');
709
664
  try {
710
665
  await this.medplum.deleteResource('Binary', binaryId);
711
666
  }
712
- catch (_b) {
667
+ catch {
713
668
  // Binary may not exist or already deleted
714
669
  }
715
670
  }
@@ -717,7 +672,6 @@ class MedplumNotificationBackend {
717
672
  await this.medplum.deleteResource('Media', fileId);
718
673
  }
719
674
  async getOrphanedAttachmentFiles() {
720
- var _a;
721
675
  // Get all Media resources tagged as attachment-files
722
676
  const allMedia = await this.medplum.searchResources('Media', {
723
677
  _tag: 'attachment-file',
@@ -730,7 +684,7 @@ class MedplumNotificationBackend {
730
684
  const referencedIds = new Set();
731
685
  for (const comm of communications) {
732
686
  for (const payload of comm.payload || []) {
733
- const url = (_a = payload.contentAttachment) === null || _a === void 0 ? void 0 : _a.url;
687
+ const url = payload.contentAttachment?.url;
734
688
  if (url) {
735
689
  // Extract Media ID from URL (assuming format like Media/{id})
736
690
  const match = url.match(/Media\/([^/]+)/);
@@ -786,13 +740,13 @@ class MedplumNotificationBackend {
786
740
  checksum: fileRecord.checksum,
787
741
  createdAt: fileRecord.createdAt,
788
742
  file: attachmentFile,
789
- description: payloadData === null || payloadData === void 0 ? void 0 : payloadData.description,
743
+ description: payloadData?.description,
790
744
  storageMetadata: fileRecord.storageMetadata,
791
745
  });
792
746
  }
793
747
  return attachments;
794
748
  }
795
- catch (_a) {
749
+ catch {
796
750
  return [];
797
751
  }
798
752
  }
@@ -800,8 +754,7 @@ class MedplumNotificationBackend {
800
754
  const communication = await this.medplum.readResource('Communication', notificationId);
801
755
  // Check if attachment exists
802
756
  const hasAttachment = (communication.payload || []).some((p) => {
803
- var _a;
804
- const url = (_a = p.contentAttachment) === null || _a === void 0 ? void 0 : _a.url;
757
+ const url = p.contentAttachment?.url;
805
758
  return url && url.includes(attachmentId);
806
759
  });
807
760
  if (!hasAttachment) {
@@ -809,8 +762,7 @@ class MedplumNotificationBackend {
809
762
  }
810
763
  // Filter out the attachment from payload
811
764
  const updatedPayload = (communication.payload || []).filter((p) => {
812
- var _a;
813
- const url = (_a = p.contentAttachment) === null || _a === void 0 ? void 0 : _a.url;
765
+ const url = p.contentAttachment?.url;
814
766
  if (!url)
815
767
  return true;
816
768
  return !url.includes(attachmentId);
@@ -822,10 +774,8 @@ class MedplumNotificationBackend {
822
774
  await this.medplum.updateResource(updated);
823
775
  }
824
776
  }
825
- exports.MedplumNotificationBackend = MedplumNotificationBackend;
826
- class MedplumNotificationBackendFactory {
777
+ export class MedplumNotificationBackendFactory {
827
778
  create(medplum) {
828
779
  return new MedplumNotificationBackend(medplum);
829
780
  }
830
781
  }
831
- exports.MedplumNotificationBackendFactory = MedplumNotificationBackendFactory;
@@ -6,3 +6,4 @@ export declare class MedplumLogger implements BaseLogger {
6
6
  error(message: string): void;
7
7
  warn(message: string): void;
8
8
  }
9
+ //# sourceMappingURL=medplum-logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"medplum-logger.d.ts","sourceRoot":"","sources":["../src/medplum-logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAE9E,qBAAa,aAAc,YAAW,UAAU;IAC9C,OAAO,CAAC,MAAM,CAAiB;;IAM/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CAG5B"}
@@ -1,7 +1,4 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MedplumLogger = void 0;
4
- class MedplumLogger {
1
+ export class MedplumLogger {
5
2
  constructor() {
6
3
  this.logger = console;
7
4
  }
@@ -15,4 +12,3 @@ class MedplumLogger {
15
12
  this.logger.warn(message);
16
13
  }
17
14
  }
18
- exports.MedplumLogger = MedplumLogger;
@@ -0,0 +1,29 @@
1
+ import { BaseEmailTemplateRenderer } from 'vintasend';
2
+ import type { JsonObject } from 'vintasend/dist/types/json-values';
3
+ import type { DatabaseNotification } from 'vintasend/dist/types/notification';
4
+ import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
5
+ import type { BaseLogger } from 'vintasend/dist/services/loggers/base-logger';
6
+ /**
7
+ * Custom email template renderer that compiles Pug templates from strings
8
+ * instead of reading from file paths.
9
+ *
10
+ * This is necessary for bot deployments where templates are embedded as constants
11
+ * rather than separate files.
12
+ */
13
+ export declare class PugInlineEmailTemplateRenderer<Config extends BaseNotificationTypeConfig> implements BaseEmailTemplateRenderer<Config> {
14
+ private templates;
15
+ private logger;
16
+ constructor(generatedTemplates: Record<string, string>);
17
+ /**
18
+ * Inject logger (called by VintaSend when logger exists)
19
+ */
20
+ injectLogger(logger: BaseLogger): void;
21
+ render(notification: DatabaseNotification<Config>, context: JsonObject): Promise<{
22
+ subject: string;
23
+ body: string;
24
+ }>;
25
+ }
26
+ export declare class PugInlineEmailTemplateRendererFactory<Config extends BaseNotificationTypeConfig> {
27
+ create(generatedTemplates: Record<string, string>): PugInlineEmailTemplateRenderer<Config>;
28
+ }
29
+ //# sourceMappingURL=pug-inline-email-template-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pug-inline-email-template-renderer.d.ts","sourceRoot":"","sources":["../src/pug-inline-email-template-renderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,+CAA+C,CAAC;AAChG,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6CAA6C,CAAC;AAG9E;;;;;;GAMG;AACH,qBAAa,8BAA8B,CAAC,MAAM,SAAS,0BAA0B,CACnF,YAAW,yBAAyB,CAAC,MAAM,CAAC;IAE5C,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,MAAM,CAA2B;gBAE7B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAItD;;OAEG;IACH,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIhC,MAAM,CACV,YAAY,EAAE,oBAAoB,CAAC,MAAM,CAAC,EAC1C,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CA6C9C;AAED,qBAAa,qCAAqC,CAAC,MAAM,SAAS,0BAA0B;IAC1F,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,8BAA8B,CAAC,MAAM,CAAC;CAG3F"}
@@ -0,0 +1,68 @@
1
+ import * as pug from 'pug';
2
+ /**
3
+ * Custom email template renderer that compiles Pug templates from strings
4
+ * instead of reading from file paths.
5
+ *
6
+ * This is necessary for bot deployments where templates are embedded as constants
7
+ * rather than separate files.
8
+ */
9
+ export class PugInlineEmailTemplateRenderer {
10
+ constructor(generatedTemplates) {
11
+ this.logger = null;
12
+ this.templates = generatedTemplates;
13
+ }
14
+ /**
15
+ * Inject logger (called by VintaSend when logger exists)
16
+ */
17
+ injectLogger(logger) {
18
+ this.logger = logger;
19
+ }
20
+ async render(notification, context) {
21
+ // Check if body template is provided
22
+ const bodyTemplateKey = notification.bodyTemplate;
23
+ if (!bodyTemplateKey) {
24
+ throw new Error('Body template is required');
25
+ }
26
+ if (!(bodyTemplateKey in this.templates)) {
27
+ throw new Error(`Body template "${bodyTemplateKey}" not found in templates`);
28
+ }
29
+ // Check if subject template is provided
30
+ const subjectTemplateKey = notification.subjectTemplate;
31
+ if (!subjectTemplateKey) {
32
+ throw new Error('Subject template is required');
33
+ }
34
+ if (!(subjectTemplateKey in this.templates)) {
35
+ throw new Error(`Subject template "${subjectTemplateKey}" not found in templates`);
36
+ }
37
+ let body;
38
+ try {
39
+ // Compile and render the body template from string
40
+ const bodyTemplate = pug.compile(this.templates[bodyTemplateKey]);
41
+ body = bodyTemplate(context);
42
+ }
43
+ catch (error) {
44
+ if (this.logger) {
45
+ this.logger.error('[PugInlineEmailTemplateRenderer] Error rendering body template');
46
+ }
47
+ throw error;
48
+ }
49
+ let subject;
50
+ try {
51
+ // Compile and render the subject template from string
52
+ const subjectTemplate = pug.compile(this.templates[subjectTemplateKey]);
53
+ subject = subjectTemplate(context);
54
+ }
55
+ catch (error) {
56
+ if (this.logger) {
57
+ this.logger.error('[PugInlineEmailTemplateRenderer] Error rendering subject template');
58
+ }
59
+ throw error;
60
+ }
61
+ return { subject, body };
62
+ }
63
+ }
64
+ export class PugInlineEmailTemplateRendererFactory {
65
+ create(generatedTemplates) {
66
+ return new PugInlineEmailTemplateRenderer(generatedTemplates);
67
+ }
68
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=compile-pug-templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile-pug-templates.d.ts","sourceRoot":"","sources":["../../src/scripts/compile-pug-templates.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ /**
5
+ * Recursively finds all .pug files in a directory
6
+ */
7
+ function findPugFiles(dir, baseDir, files = new Map()) {
8
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
9
+ for (const entry of entries) {
10
+ const fullPath = path.join(dir, entry.name);
11
+ if (entry.isDirectory()) {
12
+ // Recursively search subdirectories
13
+ findPugFiles(fullPath, baseDir, files);
14
+ }
15
+ else if (entry.isFile() && entry.name.endsWith('.pug')) {
16
+ // Read the pug file content
17
+ const content = fs.readFileSync(fullPath, 'utf-8');
18
+ // Use relative path from base directory as the key
19
+ // Normalize to POSIX-style path (forward slashes) for cross-platform consistency
20
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
21
+ files.set(relativePath, content);
22
+ }
23
+ }
24
+ return files;
25
+ }
26
+ /**
27
+ * Main function to compile pug templates to JSON
28
+ */
29
+ function compilePugTemplates(inputDir = './templates', outputFile = 'compiled-templates.json') {
30
+ // Validate input directory exists
31
+ if (!fs.existsSync(inputDir)) {
32
+ console.error(`Error: Directory "${inputDir}" does not exist.`);
33
+ process.exit(1);
34
+ }
35
+ if (!fs.statSync(inputDir).isDirectory()) {
36
+ console.error(`Error: "${inputDir}" is not a directory.`);
37
+ process.exit(1);
38
+ }
39
+ console.log(`Searching for .pug files in: ${inputDir}`);
40
+ // Find all pug files
41
+ const pugFiles = findPugFiles(inputDir, inputDir);
42
+ console.log(`Found ${pugFiles.size} .pug file(s)`);
43
+ // Convert Map to plain object
44
+ const result = {};
45
+ for (const [filePath, content] of pugFiles.entries()) {
46
+ result[filePath] = content;
47
+ console.log(` - ${filePath}`);
48
+ }
49
+ // Write to output file
50
+ const outputContent = JSON.stringify(result, null, 2);
51
+ fs.writeFileSync(outputFile, outputContent, 'utf-8');
52
+ console.log(`\nSuccessfully compiled templates to: ${outputFile}`);
53
+ }
54
+ // Parse command line arguments
55
+ const args = process.argv.slice(2);
56
+ if (args.includes('--help') || args.includes('-h')) {
57
+ console.log('Usage: compile-pug-templates [input-directory] [output-file]');
58
+ console.log('');
59
+ console.log('Arguments:');
60
+ console.log(' input-directory Directory containing .pug templates (default: ./templates)');
61
+ console.log(' output-file Output JSON file path (default: compiled-templates.json)');
62
+ console.log('');
63
+ console.log('Examples:');
64
+ console.log(' compile-pug-templates');
65
+ console.log(' compile-pug-templates ./templates');
66
+ console.log(' compile-pug-templates ./templates ./compiled-templates.json');
67
+ process.exit(0);
68
+ }
69
+ const [inputDir, outputFile] = args;
70
+ // Run the compilation
71
+ compilePugTemplates(inputDir, outputFile);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vintasend-medplum",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -8,26 +8,35 @@
8
8
  "prepublishOnly": "npm run build",
9
9
  "test": "jest",
10
10
  "test:watch": "jest --watch",
11
- "test:coverage": "jest --coverage"
11
+ "test:coverage": "jest --coverage",
12
+ "compile-templates": "ts-node src/scripts/compile-pug-templates.ts"
12
13
  },
13
14
  "files": [
14
- "dist"
15
+ "dist",
16
+ "src/scripts/compile-pug-templates.ts"
15
17
  ],
18
+ "bin": {
19
+ "compile-pug-templates": "dist/scripts/compile-pug-templates.js"
20
+ },
16
21
  "author": "Hugo Bessa",
17
22
  "license": "MIT",
18
23
  "peerDependencies": {
19
- "@medplum/core": "^4.5.2",
20
- "@medplum/fhirtypes": "^4.5.2",
24
+ "@medplum/core": "^5.0.10",
25
+ "@medplum/fhirtypes": "^5.0.10",
21
26
  "pdfmake": "^0.2.23",
22
- "vintasend": "^0.4.0"
27
+ "pug": "^3.0.3",
28
+ "vintasend": "^0.4.3"
23
29
  },
24
30
  "devDependencies": {
25
- "@medplum/definitions": "^4.5.2",
26
- "@medplum/mock": "^4.5.2",
31
+ "@medplum/definitions": "^5.0.10",
32
+ "@medplum/mock": "^5.0.10",
27
33
  "@types/jest": "^30.0.0",
34
+ "@types/node": "^22.0.0",
28
35
  "@types/pdfmake": "^0.2.12",
36
+ "@types/pug": "^2.0.10",
29
37
  "jest": "^30.2.0",
30
38
  "ts-jest": "^29.4.6",
39
+ "ts-node": "^10.9.2",
31
40
  "typescript": "^5.9.3"
32
41
  }
33
42
  }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ interface PugTemplatesMap {
6
+ [key: string]: string;
7
+ }
8
+
9
+ /**
10
+ * Recursively finds all .pug files in a directory
11
+ */
12
+ function findPugFiles(dir: string, baseDir: string, files: Map<string, string> = new Map()): Map<string, string> {
13
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
14
+
15
+ for (const entry of entries) {
16
+ const fullPath = path.join(dir, entry.name);
17
+
18
+ if (entry.isDirectory()) {
19
+ // Recursively search subdirectories
20
+ findPugFiles(fullPath, baseDir, files);
21
+ } else if (entry.isFile() && entry.name.endsWith('.pug')) {
22
+ // Read the pug file content
23
+ const content = fs.readFileSync(fullPath, 'utf-8');
24
+ // Use relative path from base directory as the key
25
+ // Normalize to POSIX-style path (forward slashes) for cross-platform consistency
26
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
27
+ files.set(relativePath, content);
28
+ }
29
+ }
30
+
31
+ return files;
32
+ }
33
+
34
+ /**
35
+ * Main function to compile pug templates to JSON
36
+ */
37
+ function compilePugTemplates(inputDir: string = './templates', outputFile: string = 'compiled-templates.json'): void {
38
+ // Validate input directory exists
39
+ if (!fs.existsSync(inputDir)) {
40
+ console.error(`Error: Directory "${inputDir}" does not exist.`);
41
+ process.exit(1);
42
+ }
43
+
44
+ if (!fs.statSync(inputDir).isDirectory()) {
45
+ console.error(`Error: "${inputDir}" is not a directory.`);
46
+ process.exit(1);
47
+ }
48
+
49
+ console.log(`Searching for .pug files in: ${inputDir}`);
50
+
51
+ // Find all pug files
52
+ const pugFiles = findPugFiles(inputDir, inputDir);
53
+
54
+ console.log(`Found ${pugFiles.size} .pug file(s)`);
55
+
56
+ // Convert Map to plain object
57
+ const result: PugTemplatesMap = {};
58
+ for (const [filePath, content] of pugFiles.entries()) {
59
+ result[filePath] = content;
60
+ console.log(` - ${filePath}`);
61
+ }
62
+
63
+ // Write to output file
64
+ const outputContent = JSON.stringify(result, null, 2);
65
+ fs.writeFileSync(outputFile, outputContent, 'utf-8');
66
+
67
+ console.log(`\nSuccessfully compiled templates to: ${outputFile}`);
68
+ }
69
+
70
+ // Parse command line arguments
71
+ const args = process.argv.slice(2);
72
+
73
+ if (args.includes('--help') || args.includes('-h')) {
74
+ console.log('Usage: compile-pug-templates [input-directory] [output-file]');
75
+ console.log('');
76
+ console.log('Arguments:');
77
+ console.log(' input-directory Directory containing .pug templates (default: ./templates)');
78
+ console.log(' output-file Output JSON file path (default: compiled-templates.json)');
79
+ console.log('');
80
+ console.log('Examples:');
81
+ console.log(' compile-pug-templates');
82
+ console.log(' compile-pug-templates ./templates');
83
+ console.log(' compile-pug-templates ./templates ./compiled-templates.json');
84
+ process.exit(0);
85
+ }
86
+
87
+ const [inputDir, outputFile] = args;
88
+
89
+ // Run the compilation
90
+ compilePugTemplates(inputDir, outputFile);