@fhirust/sdk 0.3.0 โ†’ 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +430 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,430 @@
1
+ # @fhirust/sdk
2
+
3
+ > TypeScript SDK for building FHIRust WASM plugins
4
+
5
+ [![npm version](https://badge.fury.io/js/@fhirust%2Fsdk.svg)](https://www.npmjs.com/package/@fhirust/sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ Build powerful FHIR resource validation, transformation, and enrichment plugins for [FHIRust](https://github.com/wei6bin/fhirust) using TypeScript and WebAssembly.
9
+
10
+ ---
11
+
12
+ ## ๐Ÿ“ฆ Installation
13
+
14
+ ```bash
15
+ npm install @fhirust/sdk @bytecodealliance/componentize-js
16
+ ```
17
+
18
+ **Requirements:**
19
+ - Node.js 18+ or Bun
20
+ - TypeScript 5.0+
21
+
22
+ ---
23
+
24
+ ## ๐Ÿš€ Quick Start
25
+
26
+ ### 1. Create a new plugin
27
+
28
+ ```typescript
29
+ import { FhirPlugin, reject } from "@fhirust/sdk";
30
+ import type { Patient } from "@fhirust/sdk/r4";
31
+
32
+ const plugin = new FhirPlugin("my-validator", "1.0.0");
33
+
34
+ // Validate Patient before creation
35
+ plugin.beforeCreate<Patient>("Patient", (patient, ctx) => {
36
+ if (!patient.name || patient.name.length === 0) {
37
+ return reject("Patient must have at least one name");
38
+ }
39
+ return patient;
40
+ });
41
+
42
+ // Export plugin hooks
43
+ export const { getMetadata, executeHook } = plugin.exports();
44
+ ```
45
+
46
+ ### 2. Build the plugin
47
+
48
+ ```bash
49
+ # Using the SDK CLI
50
+ npx fhirust-sdk build
51
+
52
+ # Or manually with componentize-js
53
+ npx jco componentize src/index.ts -o dist/plugin.wasm \
54
+ --wit wit/plugin.wit \
55
+ --world-name fhirust-plugin
56
+ ```
57
+
58
+ ### 3. Deploy to FHIRust server
59
+
60
+ ```toml
61
+ # config/server.toml
62
+ [[plugins.plugins]]
63
+ name = "my-validator"
64
+ path = "./plugins/my-validator.wasm"
65
+ enabled = true
66
+ ```
67
+
68
+ ---
69
+
70
+ ## โœจ Features
71
+
72
+ - **Type-safe FHIR resources** โ€” Full TypeScript types for FHIR R4 resources
73
+ - **Hook-based architecture** โ€” Intercept and modify resources before/after CRUD operations
74
+ - **Built-in FHIR client** โ€” Make authenticated API calls to the FHIR server
75
+ - **HTTP client** โ€” Fetch data from external APIs with allowlist control
76
+ - **Event emission** โ€” Send async events for logging, webhooks, and integrations
77
+ - **WebAssembly runtime** โ€” Sandboxed execution with fine-grained permissions
78
+ - **Testing utilities** โ€” Built-in test harness for unit testing plugins
79
+
80
+ ---
81
+
82
+ ## ๐Ÿ“š API Reference
83
+
84
+ ### Core Classes
85
+
86
+ #### `FhirPlugin`
87
+
88
+ Main plugin class for registering hooks.
89
+
90
+ ```typescript
91
+ import { FhirPlugin } from "@fhirust/sdk";
92
+
93
+ const plugin = new FhirPlugin(name: string, version: string);
94
+ ```
95
+
96
+ **Methods:**
97
+
98
+ - `beforeCreate<T>(resourceType, handler)` โ€” Hook before resource creation
99
+ - `afterCreate<T>(resourceType, handler)` โ€” Hook after resource creation
100
+ - `beforeRead(resourceType, handler)` โ€” Hook before resource read
101
+ - `afterRead<T>(resourceType, handler)` โ€” Hook after resource read
102
+ - `beforeUpdate<T>(resourceType, handler)` โ€” Hook before resource update
103
+ - `afterUpdate<T>(resourceType, handler)` โ€” Hook after resource update
104
+ - `beforeDelete(resourceType, handler)` โ€” Hook before resource deletion
105
+ - `afterDelete(resourceType, handler)` โ€” Hook after resource deletion
106
+ - `exports()` โ€” Export plugin metadata and hook executor
107
+
108
+ ### Helper Functions
109
+
110
+ #### `reject(message: string): never`
111
+
112
+ Reject a hook operation with an error message.
113
+
114
+ ```typescript
115
+ if (!patient.birthDate) {
116
+ return reject("Patient birth date is required");
117
+ }
118
+ ```
119
+
120
+ #### `outcome(issues: OperationOutcomeIssue[]): OperationOutcome`
121
+
122
+ Create a FHIR OperationOutcome for complex validation errors.
123
+
124
+ ```typescript
125
+ return outcome([
126
+ { severity: "error", code: "required", diagnostics: "Missing name" },
127
+ { severity: "warning", code: "business-rule", diagnostics: "Unusual age" }
128
+ ]);
129
+ ```
130
+
131
+ ### FHIR Client
132
+
133
+ Access the FHIR server from within your plugin.
134
+
135
+ ```typescript
136
+ plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
137
+ // Search for duplicates
138
+ const bundle = await ctx.fhir.search("Patient", {
139
+ identifier: patient.identifier?.[0]?.value
140
+ });
141
+
142
+ if (bundle.total > 0) {
143
+ return reject("Duplicate patient identifier");
144
+ }
145
+
146
+ return patient;
147
+ });
148
+ ```
149
+
150
+ **Methods:**
151
+ - `fhir.search(resourceType, params)` โ€” Search for resources
152
+ - `fhir.read(resourceType, id)` โ€” Read a resource by ID
153
+ - `fhir.create(resourceType, resource)` โ€” Create a new resource
154
+ - `fhir.update(resourceType, id, resource)` โ€” Update a resource
155
+ - `fhir.delete(resourceType, id)` โ€” Delete a resource
156
+
157
+ ### HTTP Client
158
+
159
+ Make HTTP requests to external APIs (requires `http.fetch` permission).
160
+
161
+ ```typescript
162
+ plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
163
+ // Verify insurance eligibility via external API
164
+ const response = await ctx.http.post("https://api.insurance.com/verify", {
165
+ body: { memberId: patient.identifier?.[0]?.value }
166
+ });
167
+
168
+ if (!response.ok) {
169
+ return reject("Insurance verification failed");
170
+ }
171
+
172
+ return patient;
173
+ });
174
+ ```
175
+
176
+ ### Event Emitter
177
+
178
+ Emit events for async processing.
179
+
180
+ ```typescript
181
+ plugin.afterCreate<Patient>("Patient", (patient, ctx) => {
182
+ ctx.events.emit("patient.created", {
183
+ id: patient.id,
184
+ name: patient.name?.[0]?.family
185
+ });
186
+
187
+ return patient;
188
+ });
189
+ ```
190
+
191
+ ### Logger
192
+
193
+ Structured logging with severity levels.
194
+
195
+ ```typescript
196
+ plugin.beforeCreate<Patient>("Patient", (patient, ctx) => {
197
+ ctx.logger.info("Validating patient", { id: patient.id });
198
+ ctx.logger.warn("Missing phone number", { id: patient.id });
199
+ ctx.logger.error("Validation failed", { id: patient.id });
200
+
201
+ return patient;
202
+ });
203
+ ```
204
+
205
+ ---
206
+
207
+ ## ๐Ÿ’ก Examples
208
+
209
+ ### Example 1: Patient Name Validator
210
+
211
+ ```typescript
212
+ import { FhirPlugin, reject } from "@fhirust/sdk";
213
+ import type { Patient } from "@fhirust/sdk/r4";
214
+
215
+ const plugin = new FhirPlugin("name-validator", "1.0.0");
216
+
217
+ plugin.beforeCreate<Patient>("Patient", (patient) => {
218
+ if (!patient.name || patient.name.length === 0) {
219
+ return reject("Patient must have at least one name");
220
+ }
221
+
222
+ const hasFamily = patient.name.some(n => n.family);
223
+ if (!hasFamily) {
224
+ return reject("At least one name must include a family name");
225
+ }
226
+
227
+ return patient;
228
+ });
229
+
230
+ export const { getMetadata, executeHook } = plugin.exports();
231
+ ```
232
+
233
+ ### Example 2: Auto-Tag Resources
234
+
235
+ ```typescript
236
+ import { FhirPlugin } from "@fhirust/sdk";
237
+ import type { Patient } from "@fhirust/sdk/r4";
238
+
239
+ const plugin = new FhirPlugin("auto-tagger", "1.0.0");
240
+
241
+ plugin.beforeCreate<Patient>("Patient", (patient) => {
242
+ // Add validation tag
243
+ patient.meta = patient.meta || {};
244
+ patient.meta.tag = patient.meta.tag || [];
245
+ patient.meta.tag.push({
246
+ system: "http://example.org/tags",
247
+ code: "validated",
248
+ display: "Validated by Plugin"
249
+ });
250
+
251
+ return patient;
252
+ });
253
+
254
+ export const { getMetadata, executeHook } = plugin.exports();
255
+ ```
256
+
257
+ ### Example 3: Duplicate Detection
258
+
259
+ ```typescript
260
+ import { FhirPlugin, reject } from "@fhirust/sdk";
261
+ import type { Patient } from "@fhirust/sdk/r4";
262
+
263
+ const plugin = new FhirPlugin("duplicate-detector", "1.0.0");
264
+
265
+ plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
266
+ const identifier = patient.identifier?.[0]?.value;
267
+ if (!identifier) return patient;
268
+
269
+ // Search for existing patients with same identifier
270
+ const bundle = await ctx.fhir.search("Patient", {
271
+ identifier: identifier
272
+ });
273
+
274
+ if (bundle.total && bundle.total > 0) {
275
+ return reject(`Duplicate patient found with identifier: ${identifier}`);
276
+ }
277
+
278
+ return patient;
279
+ });
280
+
281
+ export const { getMetadata, executeHook } = plugin.exports();
282
+ ```
283
+
284
+ ### Example 4: External API Integration
285
+
286
+ ```typescript
287
+ import { FhirPlugin, reject } from "@fhirust/sdk";
288
+ import type { Patient } from "@fhirust/sdk/r4";
289
+
290
+ const plugin = new FhirPlugin("insurance-verifier", "1.0.0");
291
+
292
+ plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
293
+ const memberId = patient.identifier?.find(
294
+ i => i.system === "http://insurance.org"
295
+ )?.value;
296
+
297
+ if (memberId) {
298
+ // Verify with external insurance API
299
+ const response = await ctx.http.post(
300
+ "https://api.insurance.com/verify",
301
+ { body: { memberId } }
302
+ );
303
+
304
+ if (!response.ok) {
305
+ return reject("Insurance verification failed");
306
+ }
307
+
308
+ ctx.logger.info("Insurance verified", { memberId });
309
+ }
310
+
311
+ return patient;
312
+ });
313
+
314
+ export const { getMetadata, executeHook } = plugin.exports();
315
+ ```
316
+
317
+ ---
318
+
319
+ ## ๐Ÿงช Testing
320
+
321
+ Use the built-in testing utilities:
322
+
323
+ ```typescript
324
+ import { FhirPlugin } from "@fhirust/sdk";
325
+ import { createTestContext } from "@fhirust/sdk/testing";
326
+ import type { Patient } from "@fhirust/sdk/r4";
327
+ import { test } from "node:test";
328
+ import assert from "node:assert";
329
+
330
+ test("validates patient name", async () => {
331
+ const plugin = new FhirPlugin("test", "1.0.0");
332
+
333
+ plugin.beforeCreate<Patient>("Patient", (patient) => {
334
+ if (!patient.name?.length) {
335
+ return reject("Name required");
336
+ }
337
+ return patient;
338
+ });
339
+
340
+ const ctx = createTestContext();
341
+ const patient: Patient = {
342
+ resourceType: "Patient",
343
+ name: [{ family: "Doe", given: ["John"] }]
344
+ };
345
+
346
+ const handler = plugin.getHandler("beforeCreate", "Patient");
347
+ const result = await handler(patient, ctx);
348
+
349
+ assert.equal(result.name[0].family, "Doe");
350
+ });
351
+ ```
352
+
353
+ Run tests:
354
+
355
+ ```bash
356
+ npm test
357
+ ```
358
+
359
+ ---
360
+
361
+ ## ๐Ÿ”ง CLI Commands
362
+
363
+ The SDK includes a CLI for common tasks:
364
+
365
+ ```bash
366
+ # Create a new plugin from template
367
+ npx fhirust-sdk init my-plugin
368
+
369
+ # Build plugin to WASM
370
+ npx fhirust-sdk build
371
+
372
+ # Run tests
373
+ npx fhirust-sdk test
374
+
375
+ # Validate plugin manifest
376
+ npx fhirust-sdk validate
377
+ ```
378
+
379
+ ---
380
+
381
+ ## ๐Ÿ“ Configuration
382
+
383
+ ### Plugin Permissions
384
+
385
+ Configure plugin permissions in `config/server.toml`:
386
+
387
+ ```toml
388
+ [[plugins.plugins]]
389
+ name = "my-plugin"
390
+ path = "./plugins/my-plugin.wasm"
391
+ enabled = true
392
+ permissions = [
393
+ "fhir.read", # Read FHIR resources
394
+ "fhir.write", # Create/update FHIR resources
395
+ "utils.log", # Write to server logs
396
+ "utils.emit-event", # Emit async events
397
+ "http.fetch" # Make HTTP requests
398
+ ]
399
+ http_allowlist = [
400
+ "https://api.insurance.com/*",
401
+ "https://api.eligibility.org/*"
402
+ ]
403
+ ```
404
+
405
+ ---
406
+
407
+ ## ๐Ÿค Contributing
408
+
409
+ Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
410
+
411
+ ---
412
+
413
+ ## ๐Ÿ“„ License
414
+
415
+ MIT ยฉ FHIRust Contributors
416
+
417
+ ---
418
+
419
+ ## ๐Ÿ”— Resources
420
+
421
+ - **Documentation**: https://fhirust.dev/docs/plugins
422
+ - **GitHub**: https://github.com/wei6bin/fhirust
423
+ - **npm**: https://www.npmjs.com/package/@fhirust/sdk
424
+ - **Issues**: https://github.com/wei6bin/fhirust/issues
425
+
426
+ ---
427
+
428
+ ## ๐Ÿ“Œ Version History
429
+
430
+ See [CHANGELOG.md](./CHANGELOG.md) for release notes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fhirust/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "TypeScript SDK for building FHIRust WASM plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",