@fhirfly-io/shl 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/chunk-7WIM2QP5.js +133 -0
- package/dist/chunk-7WIM2QP5.js.map +1 -0
- package/dist/chunk-CNVYKA4D.cjs +135 -0
- package/dist/chunk-CNVYKA4D.cjs.map +1 -0
- package/dist/chunk-KI44MYPE.cjs +170 -0
- package/dist/chunk-KI44MYPE.cjs.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +11 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/chunk-XTLU6O32.js +163 -0
- package/dist/chunk-XTLU6O32.js.map +1 -0
- package/dist/express.cjs +37 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +39 -0
- package/dist/express.d.ts +39 -0
- package/dist/express.js +35 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.cjs +49 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +43 -0
- package/dist/fastify.d.ts +43 -0
- package/dist/fastify.js +47 -0
- package/dist/fastify.js.map +1 -0
- package/dist/index.cjs +1615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +765 -0
- package/dist/index.d.ts +765 -0
- package/dist/index.js +1593 -0
- package/dist/index.js.map +1 -0
- package/dist/lambda.cjs +64 -0
- package/dist/lambda.cjs.map +1 -0
- package/dist/lambda.d.cts +53 -0
- package/dist/lambda.d.ts +53 -0
- package/dist/lambda.js +62 -0
- package/dist/lambda.js.map +1 -0
- package/dist/server.cjs +166 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +85 -0
- package/dist/server.d.ts +85 -0
- package/dist/server.js +159 -0
- package/dist/server.js.map +1 -0
- package/dist/storage-CHi9vLD_.d.cts +82 -0
- package/dist/storage-iI_tyHcX.d.ts +82 -0
- package/dist/types--f4ITgu9.d.cts +73 -0
- package/dist/types-DKtPO4DP.d.ts +73 -0
- package/dist/types-yk0mDByJ.d.cts +96 -0
- package/dist/types-yk0mDByJ.d.ts +96 -0
- package/package.json +137 -0
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkKI44MYPE_cjs = require('./chunk-KI44MYPE.cjs');
|
|
4
|
+
var chunkCNVYKA4D_cjs = require('./chunk-CNVYKA4D.cjs');
|
|
5
|
+
require('./chunk-Q7SFCCGT.cjs');
|
|
6
|
+
var fs = require('fs');
|
|
7
|
+
var path = require('path');
|
|
8
|
+
|
|
9
|
+
var ServerLocalStorage = class extends chunkKI44MYPE_cjs.LocalStorage {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
super(config);
|
|
12
|
+
}
|
|
13
|
+
async read(key) {
|
|
14
|
+
const filePath = path.join(this.config.directory, key);
|
|
15
|
+
if (!fs.existsSync(filePath)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return fs.readFileSync(filePath, "utf8");
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async updateMetadata(shlId, updater) {
|
|
25
|
+
const key = `${shlId}/metadata.json`;
|
|
26
|
+
const raw = await this.read(key);
|
|
27
|
+
if (raw === null) return null;
|
|
28
|
+
const current = JSON.parse(raw);
|
|
29
|
+
const updated = updater(current);
|
|
30
|
+
if (updated === null) return null;
|
|
31
|
+
await this.store(key, JSON.stringify(updated));
|
|
32
|
+
return updated;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var _s3Module;
|
|
36
|
+
async function getS3Module() {
|
|
37
|
+
if (_s3Module) return _s3Module;
|
|
38
|
+
try {
|
|
39
|
+
_s3Module = await import('@aws-sdk/client-s3');
|
|
40
|
+
return _s3Module;
|
|
41
|
+
} catch {
|
|
42
|
+
throw new chunkKI44MYPE_cjs.StorageError(
|
|
43
|
+
"@aws-sdk/client-s3 is required for ServerS3Storage. Install it: npm install @aws-sdk/client-s3",
|
|
44
|
+
"import"
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
var ServerS3Storage = class {
|
|
49
|
+
_config;
|
|
50
|
+
_client;
|
|
51
|
+
constructor(config) {
|
|
52
|
+
this._config = config;
|
|
53
|
+
}
|
|
54
|
+
get baseUrl() {
|
|
55
|
+
return this._config.baseUrl.replace(/\/+$/, "");
|
|
56
|
+
}
|
|
57
|
+
async store(key, content) {
|
|
58
|
+
try {
|
|
59
|
+
const s3 = await getS3Module();
|
|
60
|
+
const client = this._getClient(s3);
|
|
61
|
+
const body = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
62
|
+
const command = new s3.PutObjectCommand({
|
|
63
|
+
Bucket: this._config.bucket,
|
|
64
|
+
Key: this._s3Key(key),
|
|
65
|
+
Body: body,
|
|
66
|
+
ContentType: this._contentType(key)
|
|
67
|
+
});
|
|
68
|
+
await client.send(command);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof chunkKI44MYPE_cjs.StorageError) throw err;
|
|
71
|
+
throw new chunkKI44MYPE_cjs.StorageError(
|
|
72
|
+
`Failed to store ${key}: ${err instanceof Error ? err.message : String(err)}`,
|
|
73
|
+
"store"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async delete(prefix) {
|
|
78
|
+
try {
|
|
79
|
+
const s3 = await getS3Module();
|
|
80
|
+
const client = this._getClient(s3);
|
|
81
|
+
const s3Prefix = this._s3Key(prefix);
|
|
82
|
+
let continuationToken;
|
|
83
|
+
do {
|
|
84
|
+
const listInput = {
|
|
85
|
+
Bucket: this._config.bucket,
|
|
86
|
+
Prefix: s3Prefix
|
|
87
|
+
};
|
|
88
|
+
if (continuationToken) listInput["ContinuationToken"] = continuationToken;
|
|
89
|
+
const listCommand = new s3.ListObjectsV2Command(listInput);
|
|
90
|
+
const response = await client.send(listCommand);
|
|
91
|
+
const objects = response.Contents;
|
|
92
|
+
if (!objects || objects.length === 0) break;
|
|
93
|
+
const deleteCommand = new s3.DeleteObjectsCommand({
|
|
94
|
+
Bucket: this._config.bucket,
|
|
95
|
+
Delete: {
|
|
96
|
+
Objects: objects.map((obj) => ({ Key: obj.Key })),
|
|
97
|
+
Quiet: true
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
await client.send(deleteCommand);
|
|
101
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
102
|
+
} while (continuationToken);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
if (err instanceof chunkKI44MYPE_cjs.StorageError) throw err;
|
|
105
|
+
throw new chunkKI44MYPE_cjs.StorageError(
|
|
106
|
+
`Failed to delete ${prefix}: ${err instanceof Error ? err.message : String(err)}`,
|
|
107
|
+
"delete"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async read(key) {
|
|
112
|
+
try {
|
|
113
|
+
const s3 = await getS3Module();
|
|
114
|
+
const client = this._getClient(s3);
|
|
115
|
+
const command = new s3.GetObjectCommand({
|
|
116
|
+
Bucket: this._config.bucket,
|
|
117
|
+
Key: this._s3Key(key)
|
|
118
|
+
});
|
|
119
|
+
const response = await client.send(command);
|
|
120
|
+
if (!response.Body) return null;
|
|
121
|
+
return response.Body.transformToString();
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const code = err.name;
|
|
124
|
+
if (code === "NoSuchKey") return null;
|
|
125
|
+
if (err instanceof chunkKI44MYPE_cjs.StorageError) throw err;
|
|
126
|
+
throw new chunkKI44MYPE_cjs.StorageError(
|
|
127
|
+
`Failed to read ${key}: ${err instanceof Error ? err.message : String(err)}`,
|
|
128
|
+
"read"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async updateMetadata(shlId, updater) {
|
|
133
|
+
const key = `${shlId}/metadata.json`;
|
|
134
|
+
const raw = await this.read(key);
|
|
135
|
+
if (raw === null) return null;
|
|
136
|
+
const current = JSON.parse(raw);
|
|
137
|
+
const updated = updater(current);
|
|
138
|
+
if (updated === null) return null;
|
|
139
|
+
await this.store(key, JSON.stringify(updated));
|
|
140
|
+
return updated;
|
|
141
|
+
}
|
|
142
|
+
_getClient(s3) {
|
|
143
|
+
if (!this._client) {
|
|
144
|
+
this._client = new s3.S3Client({ region: this._config.region });
|
|
145
|
+
}
|
|
146
|
+
return this._client;
|
|
147
|
+
}
|
|
148
|
+
_s3Key(key) {
|
|
149
|
+
const prefix = this._config.prefix?.replace(/\/+$/, "");
|
|
150
|
+
return prefix ? `${prefix}/${key}` : key;
|
|
151
|
+
}
|
|
152
|
+
_contentType(key) {
|
|
153
|
+
if (key.endsWith(".jwe")) return "application/jose";
|
|
154
|
+
if (key.endsWith(".json")) return "application/json";
|
|
155
|
+
return "application/octet-stream";
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
Object.defineProperty(exports, "createHandler", {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
get: function () { return chunkCNVYKA4D_cjs.createHandler; }
|
|
162
|
+
});
|
|
163
|
+
exports.ServerLocalStorage = ServerLocalStorage;
|
|
164
|
+
exports.ServerS3Storage = ServerS3Storage;
|
|
165
|
+
//# sourceMappingURL=server.cjs.map
|
|
166
|
+
//# sourceMappingURL=server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/storage.ts"],"names":["LocalStorage","join","existsSync","readFileSync","StorageError"],"mappings":";;;;;;;;AA0BO,IAAM,kBAAA,GAAN,cAAiCA,8BAAA,CAAyC;AAAA,EAC/E,YAAY,MAAA,EAA4B;AACtC,IAAA,KAAA,CAAM,MAAM,CAAA;AAAA,EACd;AAAA,EAEA,MAAM,KAAK,GAAA,EAAkD;AAC3D,IAAA,MAAM,QAAA,GAAWC,SAAA,CAAK,IAAA,CAAK,MAAA,CAAO,WAAW,GAAG,CAAA;AAChD,IAAA,IAAI,CAACC,aAAA,CAAW,QAAQ,CAAA,EAAG;AACzB,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI;AAEF,MAAA,OAAOC,eAAA,CAAa,UAAU,MAAM,CAAA;AAAA,IACtC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,cAAA,CACJ,KAAA,EACA,OAAA,EAC6B;AAC7B,IAAA,MAAM,GAAA,GAAM,GAAG,KAAK,CAAA,cAAA,CAAA;AACpB,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAC/B,IAAA,IAAI,GAAA,KAAQ,MAAM,OAAO,IAAA;AAEzB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,GAAa,CAAA;AACxC,IAAA,MAAM,OAAA,GAAU,QAAQ,OAAO,CAAA;AAC/B,IAAA,IAAI,OAAA,KAAY,MAAM,OAAO,IAAA;AAE7B,IAAA,MAAM,KAAK,KAAA,CAAM,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAC7C,IAAA,OAAO,OAAA;AAAA,EACT;AACF;AAcA,IAAI,SAAA;AACJ,eAAe,WAAA,GAAiC;AAC9C,EAAA,IAAI,WAAW,OAAO,SAAA;AACtB,EAAA,IAAI;AACF,IAAA,SAAA,GAAa,MAAM,OAAO,oBAAoB,CAAA;AAC9C,IAAA,OAAO,SAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAIC,8BAAA;AAAA,MACR,gGAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;AAqBO,IAAM,kBAAN,MAAkD;AAAA,EACtC,OAAA;AAAA,EACT,OAAA;AAAA,EAER,YAAY,MAAA,EAAyB;AACnC,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AAAA,EACjB;AAAA,EAEA,IAAI,OAAA,GAAkB;AACpB,IAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAAA,EAChD;AAAA,EAEA,MAAM,KAAA,CAAM,GAAA,EAAa,OAAA,EAA6C;AACpE,IAAA,IAAI;AACF,MAAA,MAAM,EAAA,GAAK,MAAM,WAAA,EAAY;AAC7B,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,UAAA,CAAW,EAAE,CAAA;AACjC,MAAA,MAAM,IAAA,GAAO,OAAO,OAAA,KAAY,QAAA,GAAW,OAAO,IAAA,CAAK,OAAA,EAAS,MAAM,CAAA,GAAI,OAAA;AAE1E,MAAA,MAAM,OAAA,GAAU,IAAI,EAAA,CAAG,gBAAA,CAAiB;AAAA,QACtC,MAAA,EAAQ,KAAK,OAAA,CAAQ,MAAA;AAAA,QACrB,GAAA,EAAK,IAAA,CAAK,MAAA,CAAO,GAAG,CAAA;AAAA,QACpB,IAAA,EAAM,IAAA;AAAA,QACN,WAAA,EAAa,IAAA,CAAK,YAAA,CAAa,GAAG;AAAA,OACnC,CAAA;AAED,MAAA,MAAM,MAAA,CAAO,KAAK,OAAO,CAAA;AAAA,IAC3B,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,GAAA,YAAeA,gCAAc,MAAM,GAAA;AACvC,MAAA,MAAM,IAAIA,8BAAA;AAAA,QACR,CAAA,gBAAA,EAAmB,GAAG,CAAA,EAAA,EAAK,GAAA,YAAe,QAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA;AAAA,QAC3E;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,MAAA,EAA+B;AAC1C,IAAA,IAAI;AACF,MAAA,MAAM,EAAA,GAAK,MAAM,WAAA,EAAY;AAC7B,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,UAAA,CAAW,EAAE,CAAA;AACjC,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA;AAEnC,MAAA,IAAI,iBAAA;AACJ,MAAA,GAAG;AACD,QAAA,MAAM,SAAA,GAAqC;AAAA,UACzC,MAAA,EAAQ,KAAK,OAAA,CAAQ,MAAA;AAAA,UACrB,MAAA,EAAQ;AAAA,SACV;AACA,QAAA,IAAI,iBAAA,EAAmB,SAAA,CAAU,mBAAmB,CAAA,GAAI,iBAAA;AAExD,QAAA,MAAM,WAAA,GAAc,IAAI,EAAA,CAAG,oBAAA,CAAqB,SAAS,CAAA;AACzD,QAAA,MAAM,QAAA,GAAY,MAAM,MAAA,CAAO,IAAA,CAAK,WAAW,CAAA;AAM/C,QAAA,MAAM,UAAU,QAAA,CAAS,QAAA;AACzB,QAAA,IAAI,CAAC,OAAA,IAAW,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG;AAEtC,QAAA,MAAM,aAAA,GAAgB,IAAI,EAAA,CAAG,oBAAA,CAAqB;AAAA,UAChD,MAAA,EAAQ,KAAK,OAAA,CAAQ,MAAA;AAAA,UACrB,MAAA,EAAQ;AAAA,YACN,OAAA,EAAS,QAAQ,GAAA,CAAI,CAAC,SAAS,EAAE,GAAA,EAAK,GAAA,CAAI,GAAA,EAAI,CAAE,CAAA;AAAA,YAChD,KAAA,EAAO;AAAA;AACT,SACD,CAAA;AACD,QAAA,MAAM,MAAA,CAAO,KAAK,aAAa,CAAA;AAE/B,QAAA,iBAAA,GAAoB,QAAA,CAAS,WAAA,GACzB,QAAA,CAAS,qBAAA,GACT,KAAA,CAAA;AAAA,MACN,CAAA,QAAS,iBAAA;AAAA,IACX,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,GAAA,YAAeA,gCAAc,MAAM,GAAA;AACvC,MAAA,MAAM,IAAIA,8BAAA;AAAA,QACR,CAAA,iBAAA,EAAoB,MAAM,CAAA,EAAA,EAAK,GAAA,YAAe,QAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA;AAAA,QAC/E;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,GAAA,EAAkD;AAC3D,IAAA,IAAI;AACF,MAAA,MAAM,EAAA,GAAK,MAAM,WAAA,EAAY;AAC7B,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,UAAA,CAAW,EAAE,CAAA;AAEjC,MAAA,MAAM,OAAA,GAAU,IAAI,EAAA,CAAG,gBAAA,CAAiB;AAAA,QACtC,MAAA,EAAQ,KAAK,OAAA,CAAQ,MAAA;AAAA,QACrB,GAAA,EAAK,IAAA,CAAK,MAAA,CAAO,GAAG;AAAA,OACrB,CAAA;AAED,MAAA,MAAM,QAAA,GAAY,MAAM,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAI3C,MAAA,IAAI,CAAC,QAAA,CAAS,IAAA,EAAM,OAAO,IAAA;AAC3B,MAAA,OAAO,QAAA,CAAS,KAAK,iBAAA,EAAkB;AAAA,IACzC,SAAS,GAAA,EAAK;AAEZ,MAAA,MAAM,OAAQ,GAAA,CAA0B,IAAA;AACxC,MAAA,IAAI,IAAA,KAAS,aAAa,OAAO,IAAA;AACjC,MAAA,IAAI,GAAA,YAAeA,gCAAc,MAAM,GAAA;AACvC,MAAA,MAAM,IAAIA,8BAAA;AAAA,QACR,CAAA,eAAA,EAAkB,GAAG,CAAA,EAAA,EAAK,GAAA,YAAe,QAAQ,GAAA,CAAI,OAAA,GAAU,MAAA,CAAO,GAAG,CAAC,CAAA,CAAA;AAAA,QAC1E;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cAAA,CACJ,KAAA,EACA,OAAA,EAC6B;AAC7B,IAAA,MAAM,GAAA,GAAM,GAAG,KAAK,CAAA,cAAA,CAAA;AACpB,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAC/B,IAAA,IAAI,GAAA,KAAQ,MAAM,OAAO,IAAA;AAEzB,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,GAAa,CAAA;AACxC,IAAA,MAAM,OAAA,GAAU,QAAQ,OAAO,CAAA;AAC/B,IAAA,IAAI,OAAA,KAAY,MAAM,OAAO,IAAA;AAE7B,IAAA,MAAM,KAAK,KAAA,CAAM,GAAA,EAAK,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,CAAA;AAC7C,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEQ,WAAW,EAAA,EAAgC;AACjD,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,IAAA,CAAK,OAAA,GAAU,IAAI,EAAA,CAAG,QAAA,CAAS,EAAE,MAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,CAAA;AAAA,IAChE;AACA,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA,EAEQ,OAAO,GAAA,EAAqB;AAClC,IAAA,MAAM,SAAS,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,OAAA,CAAQ,QAAQ,EAAE,CAAA;AACtD,IAAA,OAAO,MAAA,GAAS,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA,GAAK,GAAA;AAAA,EACvC;AAAA,EAEQ,aAAa,GAAA,EAAqB;AACxC,IAAA,IAAI,GAAA,CAAI,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,kBAAA;AACjC,IAAA,IAAI,GAAA,CAAI,QAAA,CAAS,OAAO,CAAA,EAAG,OAAO,kBAAA;AAClC,IAAA,OAAO,0BAAA;AAAA,EACT;AACF","file":"server.cjs","sourcesContent":["// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { LocalStorage } from \"../shl/storage.js\";\nimport type { LocalStorageConfig, S3StorageConfig } from \"../shl/storage.js\";\nimport type { SHLServerStorage } from \"./types.js\";\nimport type { SHLMetadata } from \"../shl/types.js\";\nimport { StorageError } from \"../errors.js\";\n\n/**\n * Local filesystem server storage for SMART Health Links.\n *\n * Extends the base `LocalStorage` (write-only) with `read` and\n * `updateMetadata` methods needed for serving SHLs.\n *\n * @example\n * ```ts\n * import { ServerLocalStorage } from \"@fhirfly-io/shl/server\";\n *\n * const storage = new ServerLocalStorage({\n * directory: \"./shl-data\",\n * baseUrl: \"https://shl.example.com\",\n * });\n * ```\n */\nexport class ServerLocalStorage extends LocalStorage implements SHLServerStorage {\n constructor(config: LocalStorageConfig) {\n super(config);\n }\n\n async read(key: string): Promise<string | Uint8Array | null> {\n const filePath = join(this.config.directory, key);\n if (!existsSync(filePath)) {\n return null;\n }\n try {\n // Read as UTF-8 for JSON/JWE files, binary for others\n return readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n }\n\n async updateMetadata(\n shlId: string,\n updater: (current: SHLMetadata) => SHLMetadata | null,\n ): Promise<SHLMetadata | null> {\n const key = `${shlId}/metadata.json`;\n const raw = await this.read(key);\n if (raw === null) return null;\n\n const current = JSON.parse(raw as string) as SHLMetadata;\n const updated = updater(current);\n if (updated === null) return null;\n\n await this.store(key, JSON.stringify(updated));\n return updated;\n }\n}\n\n// Minimal S3 interfaces (same pattern as shl/storage.ts)\ninterface S3ClientInstance {\n send(command: unknown): Promise<unknown>;\n}\ninterface S3Module {\n S3Client: new (config: { region: string }) => S3ClientInstance;\n PutObjectCommand: new (input: Record<string, unknown>) => unknown;\n GetObjectCommand: new (input: Record<string, unknown>) => unknown;\n ListObjectsV2Command: new (input: Record<string, unknown>) => unknown;\n DeleteObjectsCommand: new (input: Record<string, unknown>) => unknown;\n}\n\nlet _s3Module: S3Module | undefined;\nasync function getS3Module(): Promise<S3Module> {\n if (_s3Module) return _s3Module;\n try {\n _s3Module = (await import(\"@aws-sdk/client-s3\")) as unknown as S3Module;\n return _s3Module;\n } catch {\n throw new StorageError(\n \"@aws-sdk/client-s3 is required for ServerS3Storage. Install it: npm install @aws-sdk/client-s3\",\n \"import\",\n );\n }\n}\n\n/**\n * S3-backed server storage for SMART Health Links.\n *\n * Implements the full `SHLServerStorage` interface with `read` and\n * `updateMetadata` on top of S3.\n *\n * Uses conditional PutObject for optimistic concurrency on metadata updates.\n *\n * @example\n * ```ts\n * import { ServerS3Storage } from \"@fhirfly-io/shl/server\";\n *\n * const storage = new ServerS3Storage({\n * bucket: \"my-shl-bucket\",\n * region: \"us-east-1\",\n * baseUrl: \"https://shl.example.com\",\n * });\n * ```\n */\nexport class ServerS3Storage implements SHLServerStorage {\n private readonly _config: S3StorageConfig;\n private _client?: S3ClientInstance;\n\n constructor(config: S3StorageConfig) {\n this._config = config;\n }\n\n get baseUrl(): string {\n return this._config.baseUrl.replace(/\\/+$/, \"\");\n }\n\n async store(key: string, content: string | Uint8Array): Promise<void> {\n try {\n const s3 = await getS3Module();\n const client = this._getClient(s3);\n const body = typeof content === \"string\" ? Buffer.from(content, \"utf8\") : content;\n\n const command = new s3.PutObjectCommand({\n Bucket: this._config.bucket,\n Key: this._s3Key(key),\n Body: body,\n ContentType: this._contentType(key),\n });\n\n await client.send(command);\n } catch (err) {\n if (err instanceof StorageError) throw err;\n throw new StorageError(\n `Failed to store ${key}: ${err instanceof Error ? err.message : String(err)}`,\n \"store\",\n );\n }\n }\n\n async delete(prefix: string): Promise<void> {\n try {\n const s3 = await getS3Module();\n const client = this._getClient(s3);\n const s3Prefix = this._s3Key(prefix);\n\n let continuationToken: string | undefined;\n do {\n const listInput: Record<string, unknown> = {\n Bucket: this._config.bucket,\n Prefix: s3Prefix,\n };\n if (continuationToken) listInput[\"ContinuationToken\"] = continuationToken;\n\n const listCommand = new s3.ListObjectsV2Command(listInput);\n const response = (await client.send(listCommand)) as {\n Contents?: Array<{ Key?: string }>;\n IsTruncated?: boolean;\n NextContinuationToken?: string;\n };\n\n const objects = response.Contents;\n if (!objects || objects.length === 0) break;\n\n const deleteCommand = new s3.DeleteObjectsCommand({\n Bucket: this._config.bucket,\n Delete: {\n Objects: objects.map((obj) => ({ Key: obj.Key })),\n Quiet: true,\n },\n });\n await client.send(deleteCommand);\n\n continuationToken = response.IsTruncated\n ? response.NextContinuationToken\n : undefined;\n } while (continuationToken);\n } catch (err) {\n if (err instanceof StorageError) throw err;\n throw new StorageError(\n `Failed to delete ${prefix}: ${err instanceof Error ? err.message : String(err)}`,\n \"delete\",\n );\n }\n }\n\n async read(key: string): Promise<string | Uint8Array | null> {\n try {\n const s3 = await getS3Module();\n const client = this._getClient(s3);\n\n const command = new s3.GetObjectCommand({\n Bucket: this._config.bucket,\n Key: this._s3Key(key),\n });\n\n const response = (await client.send(command)) as {\n Body?: { transformToString(): Promise<string> };\n };\n\n if (!response.Body) return null;\n return response.Body.transformToString();\n } catch (err) {\n // S3 returns NoSuchKey for missing objects\n const code = (err as { name?: string }).name;\n if (code === \"NoSuchKey\") return null;\n if (err instanceof StorageError) throw err;\n throw new StorageError(\n `Failed to read ${key}: ${err instanceof Error ? err.message : String(err)}`,\n \"read\",\n );\n }\n }\n\n async updateMetadata(\n shlId: string,\n updater: (current: SHLMetadata) => SHLMetadata | null,\n ): Promise<SHLMetadata | null> {\n const key = `${shlId}/metadata.json`;\n const raw = await this.read(key);\n if (raw === null) return null;\n\n const current = JSON.parse(raw as string) as SHLMetadata;\n const updated = updater(current);\n if (updated === null) return null;\n\n await this.store(key, JSON.stringify(updated));\n return updated;\n }\n\n private _getClient(s3: S3Module): S3ClientInstance {\n if (!this._client) {\n this._client = new s3.S3Client({ region: this._config.region });\n }\n return this._client;\n }\n\n private _s3Key(key: string): string {\n const prefix = this._config.prefix?.replace(/\\/+$/, \"\");\n return prefix ? `${prefix}/${key}` : key;\n }\n\n private _contentType(key: string): string {\n if (key.endsWith(\".jwe\")) return \"application/jose\";\n if (key.endsWith(\".json\")) return \"application/json\";\n return \"application/octet-stream\";\n }\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types--f4ITgu9.cjs';
|
|
2
|
+
export { A as AccessEvent } from './types--f4ITgu9.cjs';
|
|
3
|
+
import { L as LocalStorage, a as LocalStorageConfig, b as S3StorageConfig } from './storage-CHi9vLD_.cjs';
|
|
4
|
+
import { e as SHLMetadata } from './types-yk0mDByJ.cjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a framework-agnostic SHL request handler.
|
|
8
|
+
*
|
|
9
|
+
* Returns an async function that processes incoming requests and returns
|
|
10
|
+
* responses. This handler implements two routes:
|
|
11
|
+
*
|
|
12
|
+
* - `POST /{shlId}` — Manifest endpoint (validates passcode, checks access limits)
|
|
13
|
+
* - `GET /{shlId}/content` — Content endpoint (serves encrypted JWE)
|
|
14
|
+
*
|
|
15
|
+
* Framework adapters (Express, Fastify, Lambda) translate their native
|
|
16
|
+
* request/response types to/from `HandlerRequest`/`HandlerResponse`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const handle = createHandler({ storage });
|
|
21
|
+
* const response = await handle({
|
|
22
|
+
* method: "POST",
|
|
23
|
+
* path: "/abc123",
|
|
24
|
+
* body: { passcode: "1234" },
|
|
25
|
+
* headers: { "content-type": "application/json" },
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function createHandler(config: SHLHandlerConfig): (req: HandlerRequest) => Promise<HandlerResponse>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Local filesystem server storage for SMART Health Links.
|
|
33
|
+
*
|
|
34
|
+
* Extends the base `LocalStorage` (write-only) with `read` and
|
|
35
|
+
* `updateMetadata` methods needed for serving SHLs.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { ServerLocalStorage } from "@fhirfly-io/shl/server";
|
|
40
|
+
*
|
|
41
|
+
* const storage = new ServerLocalStorage({
|
|
42
|
+
* directory: "./shl-data",
|
|
43
|
+
* baseUrl: "https://shl.example.com",
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare class ServerLocalStorage extends LocalStorage implements SHLServerStorage {
|
|
48
|
+
constructor(config: LocalStorageConfig);
|
|
49
|
+
read(key: string): Promise<string | Uint8Array | null>;
|
|
50
|
+
updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* S3-backed server storage for SMART Health Links.
|
|
54
|
+
*
|
|
55
|
+
* Implements the full `SHLServerStorage` interface with `read` and
|
|
56
|
+
* `updateMetadata` on top of S3.
|
|
57
|
+
*
|
|
58
|
+
* Uses conditional PutObject for optimistic concurrency on metadata updates.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { ServerS3Storage } from "@fhirfly-io/shl/server";
|
|
63
|
+
*
|
|
64
|
+
* const storage = new ServerS3Storage({
|
|
65
|
+
* bucket: "my-shl-bucket",
|
|
66
|
+
* region: "us-east-1",
|
|
67
|
+
* baseUrl: "https://shl.example.com",
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare class ServerS3Storage implements SHLServerStorage {
|
|
72
|
+
private readonly _config;
|
|
73
|
+
private _client?;
|
|
74
|
+
constructor(config: S3StorageConfig);
|
|
75
|
+
get baseUrl(): string;
|
|
76
|
+
store(key: string, content: string | Uint8Array): Promise<void>;
|
|
77
|
+
delete(prefix: string): Promise<void>;
|
|
78
|
+
read(key: string): Promise<string | Uint8Array | null>;
|
|
79
|
+
updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
|
|
80
|
+
private _getClient;
|
|
81
|
+
private _s3Key;
|
|
82
|
+
private _contentType;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { HandlerRequest, HandlerResponse, SHLHandlerConfig, SHLServerStorage, ServerLocalStorage, ServerS3Storage, createHandler };
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { S as SHLHandlerConfig, H as HandlerRequest, a as HandlerResponse, b as SHLServerStorage } from './types-DKtPO4DP.js';
|
|
2
|
+
export { A as AccessEvent } from './types-DKtPO4DP.js';
|
|
3
|
+
import { L as LocalStorage, a as LocalStorageConfig, b as S3StorageConfig } from './storage-iI_tyHcX.js';
|
|
4
|
+
import { e as SHLMetadata } from './types-yk0mDByJ.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a framework-agnostic SHL request handler.
|
|
8
|
+
*
|
|
9
|
+
* Returns an async function that processes incoming requests and returns
|
|
10
|
+
* responses. This handler implements two routes:
|
|
11
|
+
*
|
|
12
|
+
* - `POST /{shlId}` — Manifest endpoint (validates passcode, checks access limits)
|
|
13
|
+
* - `GET /{shlId}/content` — Content endpoint (serves encrypted JWE)
|
|
14
|
+
*
|
|
15
|
+
* Framework adapters (Express, Fastify, Lambda) translate their native
|
|
16
|
+
* request/response types to/from `HandlerRequest`/`HandlerResponse`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const handle = createHandler({ storage });
|
|
21
|
+
* const response = await handle({
|
|
22
|
+
* method: "POST",
|
|
23
|
+
* path: "/abc123",
|
|
24
|
+
* body: { passcode: "1234" },
|
|
25
|
+
* headers: { "content-type": "application/json" },
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
declare function createHandler(config: SHLHandlerConfig): (req: HandlerRequest) => Promise<HandlerResponse>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Local filesystem server storage for SMART Health Links.
|
|
33
|
+
*
|
|
34
|
+
* Extends the base `LocalStorage` (write-only) with `read` and
|
|
35
|
+
* `updateMetadata` methods needed for serving SHLs.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { ServerLocalStorage } from "@fhirfly-io/shl/server";
|
|
40
|
+
*
|
|
41
|
+
* const storage = new ServerLocalStorage({
|
|
42
|
+
* directory: "./shl-data",
|
|
43
|
+
* baseUrl: "https://shl.example.com",
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare class ServerLocalStorage extends LocalStorage implements SHLServerStorage {
|
|
48
|
+
constructor(config: LocalStorageConfig);
|
|
49
|
+
read(key: string): Promise<string | Uint8Array | null>;
|
|
50
|
+
updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* S3-backed server storage for SMART Health Links.
|
|
54
|
+
*
|
|
55
|
+
* Implements the full `SHLServerStorage` interface with `read` and
|
|
56
|
+
* `updateMetadata` on top of S3.
|
|
57
|
+
*
|
|
58
|
+
* Uses conditional PutObject for optimistic concurrency on metadata updates.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { ServerS3Storage } from "@fhirfly-io/shl/server";
|
|
63
|
+
*
|
|
64
|
+
* const storage = new ServerS3Storage({
|
|
65
|
+
* bucket: "my-shl-bucket",
|
|
66
|
+
* region: "us-east-1",
|
|
67
|
+
* baseUrl: "https://shl.example.com",
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
declare class ServerS3Storage implements SHLServerStorage {
|
|
72
|
+
private readonly _config;
|
|
73
|
+
private _client?;
|
|
74
|
+
constructor(config: S3StorageConfig);
|
|
75
|
+
get baseUrl(): string;
|
|
76
|
+
store(key: string, content: string | Uint8Array): Promise<void>;
|
|
77
|
+
delete(prefix: string): Promise<void>;
|
|
78
|
+
read(key: string): Promise<string | Uint8Array | null>;
|
|
79
|
+
updateMetadata(shlId: string, updater: (current: SHLMetadata) => SHLMetadata | null): Promise<SHLMetadata | null>;
|
|
80
|
+
private _getClient;
|
|
81
|
+
private _s3Key;
|
|
82
|
+
private _contentType;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { HandlerRequest, HandlerResponse, SHLHandlerConfig, SHLServerStorage, ServerLocalStorage, ServerS3Storage, createHandler };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { LocalStorage, StorageError } from './chunk-XTLU6O32.js';
|
|
2
|
+
export { createHandler } from './chunk-7WIM2QP5.js';
|
|
3
|
+
import './chunk-PZ5AY32C.js';
|
|
4
|
+
import { existsSync, readFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
|
|
7
|
+
var ServerLocalStorage = class extends LocalStorage {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
super(config);
|
|
10
|
+
}
|
|
11
|
+
async read(key) {
|
|
12
|
+
const filePath = join(this.config.directory, key);
|
|
13
|
+
if (!existsSync(filePath)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(filePath, "utf8");
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async updateMetadata(shlId, updater) {
|
|
23
|
+
const key = `${shlId}/metadata.json`;
|
|
24
|
+
const raw = await this.read(key);
|
|
25
|
+
if (raw === null) return null;
|
|
26
|
+
const current = JSON.parse(raw);
|
|
27
|
+
const updated = updater(current);
|
|
28
|
+
if (updated === null) return null;
|
|
29
|
+
await this.store(key, JSON.stringify(updated));
|
|
30
|
+
return updated;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var _s3Module;
|
|
34
|
+
async function getS3Module() {
|
|
35
|
+
if (_s3Module) return _s3Module;
|
|
36
|
+
try {
|
|
37
|
+
_s3Module = await import('@aws-sdk/client-s3');
|
|
38
|
+
return _s3Module;
|
|
39
|
+
} catch {
|
|
40
|
+
throw new StorageError(
|
|
41
|
+
"@aws-sdk/client-s3 is required for ServerS3Storage. Install it: npm install @aws-sdk/client-s3",
|
|
42
|
+
"import"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var ServerS3Storage = class {
|
|
47
|
+
_config;
|
|
48
|
+
_client;
|
|
49
|
+
constructor(config) {
|
|
50
|
+
this._config = config;
|
|
51
|
+
}
|
|
52
|
+
get baseUrl() {
|
|
53
|
+
return this._config.baseUrl.replace(/\/+$/, "");
|
|
54
|
+
}
|
|
55
|
+
async store(key, content) {
|
|
56
|
+
try {
|
|
57
|
+
const s3 = await getS3Module();
|
|
58
|
+
const client = this._getClient(s3);
|
|
59
|
+
const body = typeof content === "string" ? Buffer.from(content, "utf8") : content;
|
|
60
|
+
const command = new s3.PutObjectCommand({
|
|
61
|
+
Bucket: this._config.bucket,
|
|
62
|
+
Key: this._s3Key(key),
|
|
63
|
+
Body: body,
|
|
64
|
+
ContentType: this._contentType(key)
|
|
65
|
+
});
|
|
66
|
+
await client.send(command);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err instanceof StorageError) throw err;
|
|
69
|
+
throw new StorageError(
|
|
70
|
+
`Failed to store ${key}: ${err instanceof Error ? err.message : String(err)}`,
|
|
71
|
+
"store"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async delete(prefix) {
|
|
76
|
+
try {
|
|
77
|
+
const s3 = await getS3Module();
|
|
78
|
+
const client = this._getClient(s3);
|
|
79
|
+
const s3Prefix = this._s3Key(prefix);
|
|
80
|
+
let continuationToken;
|
|
81
|
+
do {
|
|
82
|
+
const listInput = {
|
|
83
|
+
Bucket: this._config.bucket,
|
|
84
|
+
Prefix: s3Prefix
|
|
85
|
+
};
|
|
86
|
+
if (continuationToken) listInput["ContinuationToken"] = continuationToken;
|
|
87
|
+
const listCommand = new s3.ListObjectsV2Command(listInput);
|
|
88
|
+
const response = await client.send(listCommand);
|
|
89
|
+
const objects = response.Contents;
|
|
90
|
+
if (!objects || objects.length === 0) break;
|
|
91
|
+
const deleteCommand = new s3.DeleteObjectsCommand({
|
|
92
|
+
Bucket: this._config.bucket,
|
|
93
|
+
Delete: {
|
|
94
|
+
Objects: objects.map((obj) => ({ Key: obj.Key })),
|
|
95
|
+
Quiet: true
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
await client.send(deleteCommand);
|
|
99
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : void 0;
|
|
100
|
+
} while (continuationToken);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
if (err instanceof StorageError) throw err;
|
|
103
|
+
throw new StorageError(
|
|
104
|
+
`Failed to delete ${prefix}: ${err instanceof Error ? err.message : String(err)}`,
|
|
105
|
+
"delete"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async read(key) {
|
|
110
|
+
try {
|
|
111
|
+
const s3 = await getS3Module();
|
|
112
|
+
const client = this._getClient(s3);
|
|
113
|
+
const command = new s3.GetObjectCommand({
|
|
114
|
+
Bucket: this._config.bucket,
|
|
115
|
+
Key: this._s3Key(key)
|
|
116
|
+
});
|
|
117
|
+
const response = await client.send(command);
|
|
118
|
+
if (!response.Body) return null;
|
|
119
|
+
return response.Body.transformToString();
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const code = err.name;
|
|
122
|
+
if (code === "NoSuchKey") return null;
|
|
123
|
+
if (err instanceof StorageError) throw err;
|
|
124
|
+
throw new StorageError(
|
|
125
|
+
`Failed to read ${key}: ${err instanceof Error ? err.message : String(err)}`,
|
|
126
|
+
"read"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async updateMetadata(shlId, updater) {
|
|
131
|
+
const key = `${shlId}/metadata.json`;
|
|
132
|
+
const raw = await this.read(key);
|
|
133
|
+
if (raw === null) return null;
|
|
134
|
+
const current = JSON.parse(raw);
|
|
135
|
+
const updated = updater(current);
|
|
136
|
+
if (updated === null) return null;
|
|
137
|
+
await this.store(key, JSON.stringify(updated));
|
|
138
|
+
return updated;
|
|
139
|
+
}
|
|
140
|
+
_getClient(s3) {
|
|
141
|
+
if (!this._client) {
|
|
142
|
+
this._client = new s3.S3Client({ region: this._config.region });
|
|
143
|
+
}
|
|
144
|
+
return this._client;
|
|
145
|
+
}
|
|
146
|
+
_s3Key(key) {
|
|
147
|
+
const prefix = this._config.prefix?.replace(/\/+$/, "");
|
|
148
|
+
return prefix ? `${prefix}/${key}` : key;
|
|
149
|
+
}
|
|
150
|
+
_contentType(key) {
|
|
151
|
+
if (key.endsWith(".jwe")) return "application/jose";
|
|
152
|
+
if (key.endsWith(".json")) return "application/json";
|
|
153
|
+
return "application/octet-stream";
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export { ServerLocalStorage, ServerS3Storage };
|
|
158
|
+
//# sourceMappingURL=server.js.map
|
|
159
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/storage.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["// Copyright 2026 FHIRfly.io LLC. All rights reserved.\n// Licensed under the MIT License. See LICENSE file in the project root.\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { LocalStorage } from \"../shl/storage.js\";\nimport type { LocalStorageConfig, S3StorageConfig } from \"../shl/storage.js\";\nimport type { SHLServerStorage } from \"./types.js\";\nimport type { SHLMetadata } from \"../shl/types.js\";\nimport { StorageError } from \"../errors.js\";\n\n/**\n * Local filesystem server storage for SMART Health Links.\n *\n * Extends the base `LocalStorage` (write-only) with `read` and\n * `updateMetadata` methods needed for serving SHLs.\n *\n * @example\n * ```ts\n * import { ServerLocalStorage } from \"@fhirfly-io/shl/server\";\n *\n * const storage = new ServerLocalStorage({\n * directory: \"./shl-data\",\n * baseUrl: \"https://shl.example.com\",\n * });\n * ```\n */\nexport class ServerLocalStorage extends LocalStorage implements SHLServerStorage {\n constructor(config: LocalStorageConfig) {\n super(config);\n }\n\n async read(key: string): Promise<string | Uint8Array | null> {\n const filePath = join(this.config.directory, key);\n if (!existsSync(filePath)) {\n return null;\n }\n try {\n // Read as UTF-8 for JSON/JWE files, binary for others\n return readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n }\n\n async updateMetadata(\n shlId: string,\n updater: (current: SHLMetadata) => SHLMetadata | null,\n ): Promise<SHLMetadata | null> {\n const key = `${shlId}/metadata.json`;\n const raw = await this.read(key);\n if (raw === null) return null;\n\n const current = JSON.parse(raw as string) as SHLMetadata;\n const updated = updater(current);\n if (updated === null) return null;\n\n await this.store(key, JSON.stringify(updated));\n return updated;\n }\n}\n\n// Minimal S3 interfaces (same pattern as shl/storage.ts)\ninterface S3ClientInstance {\n send(command: unknown): Promise<unknown>;\n}\ninterface S3Module {\n S3Client: new (config: { region: string }) => S3ClientInstance;\n PutObjectCommand: new (input: Record<string, unknown>) => unknown;\n GetObjectCommand: new (input: Record<string, unknown>) => unknown;\n ListObjectsV2Command: new (input: Record<string, unknown>) => unknown;\n DeleteObjectsCommand: new (input: Record<string, unknown>) => unknown;\n}\n\nlet _s3Module: S3Module | undefined;\nasync function getS3Module(): Promise<S3Module> {\n if (_s3Module) return _s3Module;\n try {\n _s3Module = (await import(\"@aws-sdk/client-s3\")) as unknown as S3Module;\n return _s3Module;\n } catch {\n throw new StorageError(\n \"@aws-sdk/client-s3 is required for ServerS3Storage. Install it: npm install @aws-sdk/client-s3\",\n \"import\",\n );\n }\n}\n\n/**\n * S3-backed server storage for SMART Health Links.\n *\n * Implements the full `SHLServerStorage` interface with `read` and\n * `updateMetadata` on top of S3.\n *\n * Uses conditional PutObject for optimistic concurrency on metadata updates.\n *\n * @example\n * ```ts\n * import { ServerS3Storage } from \"@fhirfly-io/shl/server\";\n *\n * const storage = new ServerS3Storage({\n * bucket: \"my-shl-bucket\",\n * region: \"us-east-1\",\n * baseUrl: \"https://shl.example.com\",\n * });\n * ```\n */\nexport class ServerS3Storage implements SHLServerStorage {\n private readonly _config: S3StorageConfig;\n private _client?: S3ClientInstance;\n\n constructor(config: S3StorageConfig) {\n this._config = config;\n }\n\n get baseUrl(): string {\n return this._config.baseUrl.replace(/\\/+$/, \"\");\n }\n\n async store(key: string, content: string | Uint8Array): Promise<void> {\n try {\n const s3 = await getS3Module();\n const client = this._getClient(s3);\n const body = typeof content === \"string\" ? Buffer.from(content, \"utf8\") : content;\n\n const command = new s3.PutObjectCommand({\n Bucket: this._config.bucket,\n Key: this._s3Key(key),\n Body: body,\n ContentType: this._contentType(key),\n });\n\n await client.send(command);\n } catch (err) {\n if (err instanceof StorageError) throw err;\n throw new StorageError(\n `Failed to store ${key}: ${err instanceof Error ? err.message : String(err)}`,\n \"store\",\n );\n }\n }\n\n async delete(prefix: string): Promise<void> {\n try {\n const s3 = await getS3Module();\n const client = this._getClient(s3);\n const s3Prefix = this._s3Key(prefix);\n\n let continuationToken: string | undefined;\n do {\n const listInput: Record<string, unknown> = {\n Bucket: this._config.bucket,\n Prefix: s3Prefix,\n };\n if (continuationToken) listInput[\"ContinuationToken\"] = continuationToken;\n\n const listCommand = new s3.ListObjectsV2Command(listInput);\n const response = (await client.send(listCommand)) as {\n Contents?: Array<{ Key?: string }>;\n IsTruncated?: boolean;\n NextContinuationToken?: string;\n };\n\n const objects = response.Contents;\n if (!objects || objects.length === 0) break;\n\n const deleteCommand = new s3.DeleteObjectsCommand({\n Bucket: this._config.bucket,\n Delete: {\n Objects: objects.map((obj) => ({ Key: obj.Key })),\n Quiet: true,\n },\n });\n await client.send(deleteCommand);\n\n continuationToken = response.IsTruncated\n ? response.NextContinuationToken\n : undefined;\n } while (continuationToken);\n } catch (err) {\n if (err instanceof StorageError) throw err;\n throw new StorageError(\n `Failed to delete ${prefix}: ${err instanceof Error ? err.message : String(err)}`,\n \"delete\",\n );\n }\n }\n\n async read(key: string): Promise<string | Uint8Array | null> {\n try {\n const s3 = await getS3Module();\n const client = this._getClient(s3);\n\n const command = new s3.GetObjectCommand({\n Bucket: this._config.bucket,\n Key: this._s3Key(key),\n });\n\n const response = (await client.send(command)) as {\n Body?: { transformToString(): Promise<string> };\n };\n\n if (!response.Body) return null;\n return response.Body.transformToString();\n } catch (err) {\n // S3 returns NoSuchKey for missing objects\n const code = (err as { name?: string }).name;\n if (code === \"NoSuchKey\") return null;\n if (err instanceof StorageError) throw err;\n throw new StorageError(\n `Failed to read ${key}: ${err instanceof Error ? err.message : String(err)}`,\n \"read\",\n );\n }\n }\n\n async updateMetadata(\n shlId: string,\n updater: (current: SHLMetadata) => SHLMetadata | null,\n ): Promise<SHLMetadata | null> {\n const key = `${shlId}/metadata.json`;\n const raw = await this.read(key);\n if (raw === null) return null;\n\n const current = JSON.parse(raw as string) as SHLMetadata;\n const updated = updater(current);\n if (updated === null) return null;\n\n await this.store(key, JSON.stringify(updated));\n return updated;\n }\n\n private _getClient(s3: S3Module): S3ClientInstance {\n if (!this._client) {\n this._client = new s3.S3Client({ region: this._config.region });\n }\n return this._client;\n }\n\n private _s3Key(key: string): string {\n const prefix = this._config.prefix?.replace(/\\/+$/, \"\");\n return prefix ? `${prefix}/${key}` : key;\n }\n\n private _contentType(key: string): string {\n if (key.endsWith(\".jwe\")) return \"application/jose\";\n if (key.endsWith(\".json\")) return \"application/json\";\n return \"application/octet-stream\";\n }\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { b as SHLStorage } from './types-yk0mDByJ.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for local filesystem SHL storage.
|
|
5
|
+
*/
|
|
6
|
+
interface LocalStorageConfig {
|
|
7
|
+
/** Directory path for storing SHL files */
|
|
8
|
+
directory: string;
|
|
9
|
+
/** Base URL for serving the files (trailing slashes are stripped) */
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Local filesystem storage for SMART Health Links.
|
|
14
|
+
* Useful for development and testing.
|
|
15
|
+
*
|
|
16
|
+
* Files are written to `{directory}/{key}`. The user's server
|
|
17
|
+
* maps `{baseUrl}/{shlId}` to reads from this directory.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const storage = new SHL.LocalStorage({
|
|
22
|
+
* directory: "./shl-data",
|
|
23
|
+
* baseUrl: "http://localhost:3000/shl",
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare class LocalStorage implements SHLStorage {
|
|
28
|
+
private readonly _config;
|
|
29
|
+
constructor(config: LocalStorageConfig);
|
|
30
|
+
/** Returns the storage configuration. */
|
|
31
|
+
get config(): LocalStorageConfig;
|
|
32
|
+
/** Base URL with trailing slashes stripped. */
|
|
33
|
+
get baseUrl(): string;
|
|
34
|
+
store(key: string, content: string | Uint8Array): Promise<void>;
|
|
35
|
+
delete(prefix: string): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Configuration for S3-based SHL storage.
|
|
39
|
+
*/
|
|
40
|
+
interface S3StorageConfig {
|
|
41
|
+
/** S3 bucket name */
|
|
42
|
+
bucket: string;
|
|
43
|
+
/** AWS region */
|
|
44
|
+
region: string;
|
|
45
|
+
/** Optional key prefix */
|
|
46
|
+
prefix?: string;
|
|
47
|
+
/** Base URL for serving the files */
|
|
48
|
+
baseUrl: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* S3-backed storage for SMART Health Links.
|
|
52
|
+
*
|
|
53
|
+
* Requires `@aws-sdk/client-s3` as a peer dependency — install it separately:
|
|
54
|
+
* ```
|
|
55
|
+
* npm install @aws-sdk/client-s3
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const storage = new SHL.S3Storage({
|
|
61
|
+
* bucket: "my-shl-bucket",
|
|
62
|
+
* region: "us-east-1",
|
|
63
|
+
* baseUrl: "https://shl.example.com",
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare class S3Storage implements SHLStorage {
|
|
68
|
+
private readonly _config;
|
|
69
|
+
private _client?;
|
|
70
|
+
constructor(config: S3StorageConfig);
|
|
71
|
+
/** Returns the storage configuration. */
|
|
72
|
+
get config(): S3StorageConfig;
|
|
73
|
+
/** Base URL with trailing slashes stripped. */
|
|
74
|
+
get baseUrl(): string;
|
|
75
|
+
store(key: string, content: string | Uint8Array): Promise<void>;
|
|
76
|
+
delete(prefix: string): Promise<void>;
|
|
77
|
+
private _getClient;
|
|
78
|
+
private _s3Key;
|
|
79
|
+
private _contentType;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { LocalStorage as L, S3Storage as S, type LocalStorageConfig as a, type S3StorageConfig as b };
|