vintasend-medplum 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,506 @@
1
+ # VintaSend Medplum Implementation
2
+
3
+ A complete VintaSend implementation using [Medplum](https://www.medplum.com/) as the backend, leveraging FHIR resources for notification management, file storage, and healthcare integration.
4
+
5
+ ## Overview
6
+
7
+ This implementation uses FHIR (Fast Healthcare Interoperability Resources) standards to store and manage notifications, making it ideal for healthcare applications that need to integrate notifications with patient care workflows.
8
+
9
+ ### Key Components
10
+
11
+ - **MedplumNotificationBackend**: Stores notifications as FHIR `Communication` resources
12
+ - **MedplumNotificationAdapter**: Sends email notifications via Medplum's email API
13
+ - **MedplumAttachmentManager**: Manages file attachments using FHIR `Binary` and `Media` resources
14
+ - **MedplumLogger**: Simple console-based logger
15
+
16
+ ## How It Works
17
+
18
+ ### FHIR Resource Mapping
19
+
20
+ The implementation maps VintaSend concepts to FHIR resources:
21
+
22
+ #### Notifications → Communication Resources
23
+
24
+ ```typescript
25
+ {
26
+ resourceType: "Communication",
27
+ status: "in-progress", // PENDING_SEND
28
+ sent: "2024-01-15T10:00:00Z", // sendAfter
29
+ topic: { text: "Welcome!" }, // title
30
+ recipient: [{ reference: "Patient/123" }], // userId
31
+ payload: [{
32
+ contentString: "Hello {{name}}", // bodyTemplate
33
+ extension: [{
34
+ url: "http://vintasend.com/fhir/StructureDefinition/email-notification-subject",
35
+ valueString: "Welcome {{name}}" // subjectTemplate
36
+ }]
37
+ }],
38
+ note: [{ text: '{"userId": "123"}' }], // contextParameters
39
+ meta: {
40
+ tag: [
41
+ { code: "notification" },
42
+ { code: "user-welcome" }, // contextName
43
+ { code: "EMAIL" } // notificationType
44
+ ]
45
+ }
46
+ }
47
+ ```
48
+
49
+ #### File Attachments → Binary + Media Resources
50
+
51
+ Files are stored using two FHIR resources:
52
+
53
+ 1. **Binary Resource**: Stores the actual file data (base64 encoded)
54
+ 2. **Media Resource**: Stores metadata and links to the Binary
55
+
56
+ ```typescript
57
+ // Binary resource
58
+ {
59
+ resourceType: "Binary",
60
+ contentType: "application/pdf",
61
+ data: "base64EncodedData..."
62
+ }
63
+
64
+ // Media resource
65
+ {
66
+ resourceType: "Media",
67
+ status: "completed",
68
+ content: {
69
+ contentType: "application/pdf",
70
+ url: "Binary/binary-id",
71
+ size: 12345,
72
+ title: "invoice.pdf"
73
+ },
74
+ identifier: [{
75
+ system: "http://vintasend.com/fhir/attachment-checksum",
76
+ value: "sha256-checksum"
77
+ }]
78
+ }
79
+ ```
80
+
81
+ ### Status Mapping
82
+
83
+ | VintaSend Status | FHIR Communication Status |
84
+ |-----------------|---------------------------|
85
+ | PENDING_SEND | in-progress |
86
+ | SENT | completed |
87
+ | FAILED | stopped |
88
+
89
+ ## Installation
90
+
91
+ ```bash
92
+ npm install vintasend-medplum @medplum/core
93
+ ```
94
+
95
+ ## Setup
96
+
97
+ ### Basic Configuration
98
+
99
+ ```typescript
100
+ import { MedplumClient } from '@medplum/core';
101
+ import { MedplumNotificationBackend } from 'vintasend-medplum';
102
+ import { MedplumNotificationAdapter } from 'vintasend-medplum';
103
+ import { MedplumAttachmentManager } from 'vintasend-medplum';
104
+ import { MedplumLogger } from 'vintasend-medplum';
105
+ import { PugTemplateRenderer } from 'vintasend-pug';
106
+ import { NotificationService } from 'vintasend';
107
+
108
+ // Initialize Medplum client
109
+ const medplum = new MedplumClient({
110
+ baseUrl: 'https://api.medplum.com',
111
+ clientId: 'your-client-id',
112
+ clientSecret: 'your-client-secret',
113
+ });
114
+
115
+ // Create template renderer
116
+ const templateRenderer = new PugTemplateRenderer({
117
+ templatesDir: './templates',
118
+ });
119
+
120
+ // Create notification adapter
121
+ const adapter = new MedplumNotificationAdapter(medplum, templateRenderer);
122
+
123
+ // Create backend
124
+ const backend = new MedplumNotificationBackend(medplum, {
125
+ emailNotificationSubjectExtensionUrl: 'http://your-domain.com/fhir/StructureDefinition/email-notification-subject',
126
+ });
127
+
128
+ // Create attachment manager (optional)
129
+ const attachmentManager = new MedplumAttachmentManager(medplum);
130
+
131
+ // Create logger
132
+ const logger = new MedplumLogger();
133
+
134
+ // Initialize notification service
135
+ const notificationService = new NotificationService(
136
+ backend,
137
+ [adapter],
138
+ logger,
139
+ attachmentManager,
140
+ );
141
+ ```
142
+
143
+ ### With Custom Configuration
144
+
145
+ ```typescript
146
+ // Custom extension URL for email subjects
147
+ const backend = new MedplumNotificationBackend(medplum, {
148
+ emailNotificationSubjectExtensionUrl: 'http://example.com/fhir/email-subject',
149
+ });
150
+ ```
151
+
152
+ ## Usage Examples
153
+
154
+ ### Sending a Simple Notification
155
+
156
+ ```typescript
157
+ // Create a notification
158
+ await notificationService.createNotification({
159
+ userId: 'Patient/123',
160
+ notificationType: 'EMAIL',
161
+ contextName: 'user-welcome',
162
+ contextParameters: {
163
+ firstName: 'John',
164
+ lastName: 'Doe',
165
+ },
166
+ title: 'Welcome to our platform!',
167
+ bodyTemplate: 'Hello {{firstName}} {{lastName}}!',
168
+ subjectTemplate: 'Welcome {{firstName}}!',
169
+ sendAfter: new Date(),
170
+ });
171
+
172
+ // Process pending notifications
173
+ await notificationService.processPendingNotifications();
174
+ ```
175
+
176
+ ### Sending Notifications with Attachments
177
+
178
+ ```typescript
179
+ import { readFile } from 'fs/promises';
180
+
181
+ // Create notification with file attachments
182
+ await notificationService.createNotification({
183
+ userId: 'Patient/123',
184
+ notificationType: 'EMAIL',
185
+ contextName: 'lab-results',
186
+ contextParameters: {
187
+ patientName: 'John Doe',
188
+ },
189
+ title: 'Your lab results are ready',
190
+ bodyTemplate: 'Dear {{patientName}}, your lab results are attached.',
191
+ subjectTemplate: 'Lab Results - {{patientName}}',
192
+ sendAfter: new Date(),
193
+ attachments: [
194
+ {
195
+ file: await readFile('./lab-results.pdf'),
196
+ filename: 'lab-results.pdf',
197
+ contentType: 'application/pdf',
198
+ },
199
+ ],
200
+ });
201
+ ```
202
+
203
+ ### One-Off Notifications
204
+
205
+ Send notifications to users without storing them in the system:
206
+
207
+ ```typescript
208
+ await notificationService.createOneOffNotification({
209
+ emailOrPhone: 'patient@example.com',
210
+ firstName: 'Jane',
211
+ lastName: 'Smith',
212
+ notificationType: 'EMAIL',
213
+ contextName: 'appointment-reminder',
214
+ contextParameters: {
215
+ appointmentDate: '2024-02-01',
216
+ doctorName: 'Dr. Johnson',
217
+ },
218
+ title: 'Appointment Reminder',
219
+ bodyTemplate: 'Hi {{firstName}}, reminder for your appointment on {{appointmentDate}}.',
220
+ subjectTemplate: 'Appointment on {{appointmentDate}}',
221
+ sendAfter: new Date(),
222
+ });
223
+ ```
224
+
225
+ ### Managing Attachments
226
+
227
+ ```typescript
228
+ // Upload a file
229
+ const fileRecord = await attachmentManager.uploadFile(
230
+ buffer,
231
+ 'document.pdf',
232
+ 'application/pdf',
233
+ );
234
+
235
+ // Retrieve file metadata
236
+ const file = await attachmentManager.getFile(fileRecord.id);
237
+
238
+ // Get file data
239
+ const attachmentFile = attachmentManager.reconstructAttachmentFile(
240
+ file.storageMetadata,
241
+ );
242
+ const fileBuffer = await attachmentFile.read();
243
+
244
+ // Generate temporary URL
245
+ const url = await attachmentFile.url(3600); // 1 hour expiry
246
+
247
+ // Delete file
248
+ await attachmentManager.deleteFile(fileRecord.id);
249
+ ```
250
+
251
+ ### Querying Notifications
252
+
253
+ ```typescript
254
+ // Get pending notifications
255
+ const pending = await notificationService.getPendingNotifications(0, 10);
256
+
257
+ // Get future notifications for a user
258
+ const future = await notificationService.getFutureNotificationsFromUser(
259
+ 'Patient/123',
260
+ 0,
261
+ 10,
262
+ );
263
+
264
+ // Get unread in-app notifications
265
+ const unread = await notificationService.filterInAppUnreadNotifications(
266
+ 'Patient/123',
267
+ 0,
268
+ 10,
269
+ );
270
+
271
+ // Mark as read
272
+ await notificationService.markAsRead(notificationId);
273
+ ```
274
+
275
+ ### Canceling Notifications
276
+
277
+ ```typescript
278
+ // Cancel a scheduled notification
279
+ await notificationService.cancelNotification(notificationId);
280
+ ```
281
+
282
+ ## Healthcare Integration
283
+
284
+ This implementation is designed for healthcare applications and integrates naturally with Medplum's FHIR-based infrastructure.
285
+
286
+ ### Linking Notifications to Patients
287
+
288
+ ```typescript
289
+ // Create notification linked to a patient
290
+ await notificationService.createNotification({
291
+ userId: 'Patient/123', // FHIR Patient reference
292
+ notificationType: 'EMAIL',
293
+ contextName: 'medication-reminder',
294
+ contextParameters: {
295
+ medicationName: 'Aspirin',
296
+ dosage: '100mg',
297
+ },
298
+ // ...
299
+ });
300
+ ```
301
+
302
+ ### Linking Notifications to Practitioners
303
+
304
+ ```typescript
305
+ // Notify a healthcare provider
306
+ await notificationService.createNotification({
307
+ userId: 'Practitioner/456', // FHIR Practitioner reference
308
+ notificationType: 'EMAIL',
309
+ contextName: 'new-patient-alert',
310
+ // ...
311
+ });
312
+ ```
313
+
314
+ ### Searching Notifications by Resource
315
+
316
+ ```typescript
317
+ // Get all notifications for a patient
318
+ const communications = await medplum.searchResources('Communication', {
319
+ _tag: 'notification',
320
+ recipient: 'Patient/123',
321
+ });
322
+ ```
323
+
324
+ ## Features
325
+
326
+ ### ✅ Supported Features
327
+
328
+ - Email notifications via Medplum's email API
329
+ - File attachments using FHIR Binary and Media resources
330
+ - One-off notifications (no user account required)
331
+ - Scheduled notifications (send later)
332
+ - Notification templates with context parameters
333
+ - File deduplication via checksum
334
+ - Attachment cleanup for orphaned files
335
+ - FHIR-compliant data storage
336
+
337
+ ### ❌ Not Yet Supported
338
+
339
+ - SMS notifications (Medplum limitation)
340
+ - Push notifications (Medplum limitation)
341
+ - In-app notification UI
342
+ - Real-time notification delivery (requires polling or webhooks)
343
+
344
+ ## API Reference
345
+
346
+ ### MedplumNotificationBackend
347
+
348
+ ```typescript
349
+ class MedplumNotificationBackend<Config extends BaseNotificationTypeConfig>
350
+ ```
351
+
352
+ Main backend for storing notifications as FHIR Communication resources.
353
+
354
+ **Constructor:**
355
+ ```typescript
356
+ constructor(
357
+ medplum: MedplumClient,
358
+ options?: {
359
+ emailNotificationSubjectExtensionUrl?: string;
360
+ }
361
+ )
362
+ ```
363
+
364
+ **Key Methods:**
365
+ - `persistNotification(notification)` - Create a new notification
366
+ - `persistOneOffNotification(notification)` - Create a one-off notification
367
+ - `getNotification(id)` - Retrieve a notification by ID
368
+ - `getPendingNotifications(page, pageSize)` - Get notifications ready to send
369
+ - `markAsSent(id)` - Mark notification as successfully sent
370
+ - `markAsFailed(id)` - Mark notification as failed
371
+ - `cancelNotification(id)` - Cancel a scheduled notification
372
+
373
+ ### MedplumNotificationAdapter
374
+
375
+ ```typescript
376
+ class MedplumNotificationAdapter<
377
+ TemplateRenderer extends BaseEmailTemplateRenderer<Config>,
378
+ Config extends BaseNotificationTypeConfig
379
+ >
380
+ ```
381
+
382
+ Adapter for sending email notifications via Medplum.
383
+
384
+ **Constructor:**
385
+ ```typescript
386
+ constructor(
387
+ medplum: MedplumClient,
388
+ templateRenderer: TemplateRenderer
389
+ )
390
+ ```
391
+
392
+ **Properties:**
393
+ - `supportsAttachments: boolean` - Returns `true`
394
+
395
+ **Key Methods:**
396
+ - `send(notification, context)` - Send an email notification with attachments
397
+
398
+ ### MedplumAttachmentManager
399
+
400
+ ```typescript
401
+ class MedplumAttachmentManager extends BaseAttachmentManager
402
+ ```
403
+
404
+ Manages file attachments using FHIR Binary and Media resources.
405
+
406
+ **Constructor:**
407
+ ```typescript
408
+ constructor(medplum: MedplumClient)
409
+ ```
410
+
411
+ **Key Methods:**
412
+ - `uploadFile(file, filename, contentType?)` - Upload a file to Medplum storage
413
+ - `getFile(fileId)` - Retrieve file metadata by Media resource ID
414
+ - `deleteFile(fileId)` - Delete file and its Binary resource
415
+ - `reconstructAttachmentFile(storageMetadata)` - Create AttachmentFile from metadata
416
+
417
+ ### MedplumAttachmentFile
418
+
419
+ ```typescript
420
+ class MedplumAttachmentFile implements AttachmentFile
421
+ ```
422
+
423
+ Provides access to files stored in FHIR Binary resources.
424
+
425
+ **Methods:**
426
+ - `read()` - Read entire file into a Buffer
427
+ - `stream()` - Get a ReadableStream for the file
428
+ - `url(expiresIn?)` - Generate URL for file access
429
+ - `delete()` - Delete the file from storage
430
+
431
+ ## Best Practices
432
+
433
+ ### 1. Use FHIR References Consistently
434
+
435
+ Always use proper FHIR reference format for user IDs:
436
+
437
+ ```typescript
438
+ // ✅ Good
439
+ userId: 'Patient/123'
440
+ userId: 'Practitioner/456'
441
+
442
+ // ❌ Bad
443
+ userId: '123'
444
+ userId: 'user-456'
445
+ ```
446
+
447
+ ### 2. Configure Custom Extension URLs
448
+
449
+ Use your own domain for extension URLs in production:
450
+
451
+ ```typescript
452
+ const backend = new MedplumNotificationBackend(medplum, {
453
+ emailNotificationSubjectExtensionUrl: 'http://your-domain.com/fhir/email-subject',
454
+ });
455
+ ```
456
+
457
+ ### 3. Handle Large Attachments Carefully
458
+
459
+ For large files, consider:
460
+ - Using file size limits
461
+ - Implementing file compression
462
+ - Using streaming for file operations
463
+
464
+ ```typescript
465
+ // Check file size before upload
466
+ const maxSize = 10 * 1024 * 1024; // 10MB
467
+ if (buffer.length > maxSize) {
468
+ throw new Error('File too large');
469
+ }
470
+ ```
471
+
472
+ ### 4. Clean Up Orphaned Files
473
+
474
+ Regularly run cleanup to remove orphaned attachment files:
475
+
476
+ ```typescript
477
+ // Get orphaned files
478
+ const orphaned = await backend.getOrphanedAttachmentFiles();
479
+
480
+ // Delete them
481
+ for (const file of orphaned) {
482
+ await backend.deleteAttachmentFile(file.id);
483
+ }
484
+ ```
485
+
486
+ ### 5. Use Pagination
487
+
488
+ Always paginate when fetching large result sets:
489
+
490
+ ```typescript
491
+ // ✅ Good
492
+ const notifications = await notificationService.getPendingNotifications(0, 50);
493
+
494
+ // ❌ Bad - could return thousands of records
495
+ const notifications = await notificationService.getAllPendingNotifications();
496
+ ```
497
+
498
+ ## License
499
+
500
+ MIT
501
+
502
+ ## Support
503
+
504
+ For issues and questions:
505
+ - VintaSend: [GitHub Issues](https://github.com/vintasoftware/vintasend-ts/issues)
506
+ - Medplum: [Documentation](https://www.medplum.com/docs)
@@ -0,0 +1,3 @@
1
+ export { MedplumAttachmentManager, MedplumAttachmentFile } from './medplum-attachment-manager';
2
+ export { MedplumNotificationBackend, MedplumNotificationBackendFactory } from './medplum-backend';
3
+ export { MedplumNotificationAdapter } from './medplum-adapter';
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
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; } });
@@ -0,0 +1,19 @@
1
+ import type { MedplumClient } from '@medplum/core';
2
+ import type { BaseEmailTemplateRenderer } from 'vintasend/dist/services/notification-template-renderers/base-email-template-renderer';
3
+ import { BaseNotificationAdapter } from 'vintasend/dist/services/notification-adapters/base-notification-adapter';
4
+ import type { JsonObject } from 'vintasend/dist/types/json-values';
5
+ import type { AnyDatabaseNotification } from 'vintasend/dist/types/notification';
6
+ import type { BaseNotificationTypeConfig } from 'vintasend/dist/types/notification-type-config';
7
+ import type { StoredAttachment } from 'vintasend/dist/types/attachment';
8
+ export declare class MedplumNotificationAdapter<TemplateRenderer extends BaseEmailTemplateRenderer<Config>, Config extends BaseNotificationTypeConfig> extends BaseNotificationAdapter<TemplateRenderer, Config> {
9
+ private medplum;
10
+ key: string | null;
11
+ constructor(medplum: MedplumClient, templateRenderer: TemplateRenderer);
12
+ get supportsAttachments(): boolean;
13
+ send(notification: AnyDatabaseNotification<Config>, context: JsonObject): Promise<void>;
14
+ protected prepareAttachments(attachments: StoredAttachment[]): Promise<Array<{
15
+ filename: string;
16
+ content: string;
17
+ contentType: string;
18
+ }>>;
19
+ }
@@ -0,0 +1,49 @@
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 {
6
+ constructor(medplum, templateRenderer) {
7
+ const notificationType = 'EMAIL';
8
+ super(templateRenderer, notificationType, false);
9
+ this.medplum = medplum;
10
+ this.key = 'medplum-email';
11
+ }
12
+ get supportsAttachments() {
13
+ return true;
14
+ }
15
+ async send(notification, context) {
16
+ if (!this.backend) {
17
+ throw new Error('Backend not injected');
18
+ }
19
+ if (!notification.id) {
20
+ throw new Error('Notification ID is required');
21
+ }
22
+ // Use the helper method to get recipient email (handles both regular and one-off notifications)
23
+ const recipientEmail = await this.getRecipientEmail(notification);
24
+ const template = await this.templateRenderer.render(notification, context);
25
+ const emailOptions = {
26
+ to: recipientEmail,
27
+ subject: template.subject,
28
+ text: template.body,
29
+ };
30
+ // Add attachments if present
31
+ // Type assertion needed because npm version may not have attachments field yet
32
+ const notificationWithAttachments = notification;
33
+ if (notificationWithAttachments.attachments && notificationWithAttachments.attachments.length > 0) {
34
+ emailOptions.attachments = await this.prepareAttachments(notificationWithAttachments.attachments);
35
+ }
36
+ await this.medplum.sendEmail(emailOptions);
37
+ }
38
+ async prepareAttachments(attachments) {
39
+ return Promise.all(attachments.map(async (att) => {
40
+ const buffer = await att.file.read();
41
+ return {
42
+ filename: att.filename,
43
+ content: buffer.toString('base64'),
44
+ contentType: att.contentType,
45
+ };
46
+ }));
47
+ }
48
+ }
49
+ exports.MedplumNotificationAdapter = MedplumNotificationAdapter;
@@ -0,0 +1,82 @@
1
+ import { MedplumClient } from '@medplum/core';
2
+ import { BaseAttachmentManager } from 'vintasend/dist/services/attachment-manager/base-attachment-manager';
3
+ import type { AttachmentFileRecord, AttachmentFile, FileAttachment } from 'vintasend/dist/types/attachment';
4
+ /**
5
+ * Medplum AttachmentManager implementation.
6
+ *
7
+ * This implementation uses FHIR resources for file storage:
8
+ * - Binary resources store the actual file data
9
+ * - Media resources store file metadata and link to Binary resources
10
+ *
11
+ * Files are uploaded to Medplum's storage and can be accessed via URLs.
12
+ */
13
+ export declare class MedplumAttachmentManager extends BaseAttachmentManager {
14
+ private medplum;
15
+ constructor(medplum: MedplumClient);
16
+ /**
17
+ * Upload a file to Medplum storage using Binary and Media resources.
18
+ *
19
+ * @param file - The file data (Buffer, ReadableStream, or file path)
20
+ * @param filename - The filename to use for storage
21
+ * @param contentType - Optional MIME type (auto-detected if not provided)
22
+ * @returns AttachmentFileRecord with metadata about the uploaded file
23
+ */
24
+ uploadFile(file: FileAttachment, filename: string, contentType?: string): Promise<AttachmentFileRecord>;
25
+ /**
26
+ * Retrieve a file record by its Media resource ID.
27
+ *
28
+ * @param fileId - The Media resource ID
29
+ * @returns The file record or null if not found
30
+ */
31
+ getFile(fileId: string): Promise<AttachmentFileRecord | null>;
32
+ /**
33
+ * Delete a file from Medplum storage.
34
+ *
35
+ * Deletes both the Media resource (metadata) and Binary resource (file data).
36
+ *
37
+ * @param fileId - The Media resource ID
38
+ */
39
+ deleteFile(fileId: string): Promise<void>;
40
+ /**
41
+ * Reconstruct an AttachmentFile from storage metadata.
42
+ *
43
+ * @param storageMetadata - Metadata containing either 'url' (Binary URL) or 'binaryId'
44
+ * @returns AttachmentFile instance for accessing the file
45
+ */
46
+ reconstructAttachmentFile(storageMetadata: Record<string, unknown>): AttachmentFile;
47
+ }
48
+ /**
49
+ * Medplum AttachmentFile implementation.
50
+ *
51
+ * Provides access to files stored in Medplum Binary resources.
52
+ */
53
+ export declare class MedplumAttachmentFile implements AttachmentFile {
54
+ private medplum;
55
+ private binaryId;
56
+ constructor(medplum: MedplumClient, binaryUrl: string);
57
+ /**
58
+ * Read the entire file into memory as a Buffer.
59
+ */
60
+ read(): Promise<Buffer>;
61
+ /**
62
+ * Get a readable stream for the file.
63
+ *
64
+ * Note: For Medplum, we read the entire file and convert to stream.
65
+ * For true streaming, you'd need to use Medplum's binary download API.
66
+ */
67
+ stream(): Promise<ReadableStream>;
68
+ /**
69
+ * Generate a URL for accessing the file.
70
+ *
71
+ * For Medplum, this returns the Binary resource URL.
72
+ * In production, you might want to generate a presigned URL.
73
+ *
74
+ * @param expiresIn - Seconds until the URL expires (not used for Medplum)
75
+ * @returns URL for file access
76
+ */
77
+ url(expiresIn?: number): Promise<string>;
78
+ /**
79
+ * Delete this file from storage.
80
+ */
81
+ delete(): Promise<void>;
82
+ }