@forklaunch/infrastructure-s3 1.3.15 → 1.4.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.
- package/lib/eject/infrastructure/s3.ts +41 -148
- package/lib/index.d.mts +9 -130
- package/lib/index.d.ts +9 -130
- package/lib/index.js +28 -134
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +23 -131
- package/lib/index.mjs.map +1 -1
- package/package.json +6 -6
|
@@ -14,11 +14,9 @@ import {
|
|
|
14
14
|
OpenTelemetryCollector,
|
|
15
15
|
TelemetryOptions
|
|
16
16
|
} from '@forklaunch/core/http';
|
|
17
|
-
import {
|
|
18
|
-
getCurrentTenantId,
|
|
19
|
-
type FieldEncryptor
|
|
20
|
-
} from '@forklaunch/core/persistence';
|
|
17
|
+
import { type FieldEncryptor } from '@forklaunch/core/persistence';
|
|
21
18
|
import { ObjectStore } from '@forklaunch/core/objectstore';
|
|
19
|
+
import type { ComplianceContext } from '@forklaunch/core/cache';
|
|
22
20
|
import { Readable } from 'stream';
|
|
23
21
|
|
|
24
22
|
const ENCRYPTED_PREFIXES = ['v1:', 'v2:'] as const;
|
|
@@ -34,18 +32,10 @@ function isEncrypted(value: string): boolean {
|
|
|
34
32
|
export interface S3EncryptionOptions {
|
|
35
33
|
/** The FieldEncryptor instance to use for encrypting object bodies. */
|
|
36
34
|
encryptor: FieldEncryptor;
|
|
37
|
-
/** Set to true to disable encryption. Defaults to false (encryption enabled). */
|
|
38
|
-
disabled?: boolean;
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
/**
|
|
42
38
|
* Options for configuring the S3ObjectStore.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* const options: S3ObjectStoreOptions = {
|
|
46
|
-
* bucket: 'my-bucket',
|
|
47
|
-
* clientConfig: { region: 'us-west-2' }
|
|
48
|
-
* };
|
|
49
39
|
*/
|
|
50
40
|
interface S3ObjectStoreOptions {
|
|
51
41
|
/** The S3 bucket name. */
|
|
@@ -60,37 +50,15 @@ interface S3ObjectStoreOptions {
|
|
|
60
50
|
* S3-backed implementation of the ObjectStore interface.
|
|
61
51
|
* Provides methods for storing, retrieving, streaming, and deleting objects in S3.
|
|
62
52
|
*
|
|
63
|
-
* Encryption is
|
|
64
|
-
*
|
|
65
|
-
* with per-tenant key derivation.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* const store = new S3ObjectStore(otelCollector, { bucket: 'my-bucket' }, telemetryOptions);
|
|
69
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
70
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
53
|
+
* Encryption is activated per-operation when a `compliance` context is provided.
|
|
54
|
+
* Without it, object bodies are stored and read as plaintext.
|
|
71
55
|
*/
|
|
72
56
|
export class S3ObjectStore implements ObjectStore<S3Client> {
|
|
73
57
|
private s3: S3Client;
|
|
74
58
|
private bucket: string;
|
|
75
59
|
private initialized: boolean;
|
|
76
60
|
private encryptor?: FieldEncryptor;
|
|
77
|
-
private encryptionDisabled: boolean;
|
|
78
61
|
|
|
79
|
-
/**
|
|
80
|
-
* Creates a new S3ObjectStore instance.
|
|
81
|
-
* @param openTelemetryCollector - Collector for OpenTelemetry metrics.
|
|
82
|
-
* @param options - S3 configuration options.
|
|
83
|
-
* @param telemetryOptions - Telemetry configuration options.
|
|
84
|
-
* @param encryption - Encryption configuration (enabled by default when encryptor provided).
|
|
85
|
-
*
|
|
86
|
-
* @example
|
|
87
|
-
* const store = new S3ObjectStore(
|
|
88
|
-
* otelCollector,
|
|
89
|
-
* { bucket: 'my-bucket' },
|
|
90
|
-
* telemetryOptions,
|
|
91
|
-
* { encryptor }
|
|
92
|
-
* );
|
|
93
|
-
*/
|
|
94
62
|
constructor(
|
|
95
63
|
private openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>,
|
|
96
64
|
options: S3ObjectStoreOptions,
|
|
@@ -101,23 +69,22 @@ export class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
101
69
|
this.bucket = options.bucket;
|
|
102
70
|
this.initialized = false;
|
|
103
71
|
this.encryptor = encryption.encryptor;
|
|
104
|
-
this.encryptionDisabled = encryption.disabled ?? false;
|
|
105
72
|
}
|
|
106
73
|
|
|
107
74
|
// ---------------------------------------------------------------------------
|
|
108
|
-
// Encryption helpers
|
|
75
|
+
// Encryption helpers — only active when compliance context is provided
|
|
109
76
|
// ---------------------------------------------------------------------------
|
|
110
77
|
|
|
111
|
-
private encryptBody(body: string): string {
|
|
112
|
-
if (!
|
|
113
|
-
return this.encryptor.encrypt(body,
|
|
78
|
+
private encryptBody(body: string, compliance?: ComplianceContext): string {
|
|
79
|
+
if (!compliance || !this.encryptor) return body;
|
|
80
|
+
return this.encryptor.encrypt(body, compliance.tenantId) ?? body;
|
|
114
81
|
}
|
|
115
82
|
|
|
116
|
-
private decryptBody(body: string): string {
|
|
117
|
-
if (!
|
|
83
|
+
private decryptBody(body: string, compliance?: ComplianceContext): string {
|
|
84
|
+
if (!compliance || !this.encryptor) return body;
|
|
118
85
|
if (!isEncrypted(body)) return body;
|
|
119
86
|
try {
|
|
120
|
-
return this.encryptor.decrypt(body,
|
|
87
|
+
return this.encryptor.decrypt(body, compliance.tenantId) ?? body;
|
|
121
88
|
} catch {
|
|
122
89
|
return body;
|
|
123
90
|
}
|
|
@@ -137,21 +104,16 @@ export class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
137
104
|
this.initialized = true;
|
|
138
105
|
}
|
|
139
106
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*
|
|
145
|
-
* @example
|
|
146
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
147
|
-
*/
|
|
148
|
-
async putObject<T>(object: T & { key: string }): Promise<void> {
|
|
107
|
+
async putObject<T>(
|
|
108
|
+
object: T & { key: string },
|
|
109
|
+
compliance?: ComplianceContext
|
|
110
|
+
): Promise<void> {
|
|
149
111
|
if (!this.initialized) {
|
|
150
112
|
await this.ensureBucketExists();
|
|
151
113
|
}
|
|
152
114
|
|
|
153
115
|
const { key, ...rest } = object;
|
|
154
|
-
const body = this.encryptBody(JSON.stringify(rest));
|
|
116
|
+
const body = this.encryptBody(JSON.stringify(rest), compliance);
|
|
155
117
|
const params: PutObjectCommandInput = {
|
|
156
118
|
Bucket: this.bucket,
|
|
157
119
|
Key: key,
|
|
@@ -161,63 +123,33 @@ export class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
161
123
|
await this.s3.send(new PutObjectCommand(params));
|
|
162
124
|
}
|
|
163
125
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
* @example
|
|
170
|
-
* await store.putBatchObjects([
|
|
171
|
-
* { key: 'user-1', name: 'Alice' },
|
|
172
|
-
* { key: 'user-2', name: 'Bob' }
|
|
173
|
-
* ]);
|
|
174
|
-
*/
|
|
175
|
-
async putBatchObjects<T>(objects: (T & { key: string })[]): Promise<void> {
|
|
176
|
-
await Promise.all(objects.map((obj) => this.putObject(obj)));
|
|
126
|
+
async putBatchObjects<T>(
|
|
127
|
+
objects: (T & { key: string })[],
|
|
128
|
+
compliance?: ComplianceContext
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
await Promise.all(objects.map((obj) => this.putObject(obj, compliance)));
|
|
177
131
|
}
|
|
178
132
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
*/
|
|
185
|
-
async streamUploadObject<T>(object: T & { key: string }): Promise<void> {
|
|
186
|
-
await this.putObject(object);
|
|
133
|
+
async streamUploadObject<T>(
|
|
134
|
+
object: T & { key: string },
|
|
135
|
+
compliance?: ComplianceContext
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
await this.putObject(object, compliance);
|
|
187
138
|
}
|
|
188
139
|
|
|
189
|
-
/**
|
|
190
|
-
* Streams multiple object uploads to the S3 bucket.
|
|
191
|
-
* For compatibility; uses putBatchObjects internally.
|
|
192
|
-
* @template T - The type of the objects being stored.
|
|
193
|
-
* @param objects - The objects to stream-upload. Each must include a `key` property.
|
|
194
|
-
*/
|
|
195
140
|
async streamUploadBatchObjects<T>(
|
|
196
|
-
objects: (T & { key: string })[]
|
|
141
|
+
objects: (T & { key: string })[],
|
|
142
|
+
compliance?: ComplianceContext
|
|
197
143
|
): Promise<void> {
|
|
198
|
-
await this.putBatchObjects(objects);
|
|
144
|
+
await this.putBatchObjects(objects, compliance);
|
|
199
145
|
}
|
|
200
146
|
|
|
201
|
-
/**
|
|
202
|
-
* Deletes an object from the S3 bucket.
|
|
203
|
-
* @param objectKey - The key of the object to delete.
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* await store.deleteObject('user-1');
|
|
207
|
-
*/
|
|
208
147
|
async deleteObject(objectKey: string): Promise<void> {
|
|
209
148
|
await this.s3.send(
|
|
210
149
|
new DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
211
150
|
);
|
|
212
151
|
}
|
|
213
152
|
|
|
214
|
-
/**
|
|
215
|
-
* Deletes multiple objects from the S3 bucket.
|
|
216
|
-
* @param objectKeys - The keys of the objects to delete.
|
|
217
|
-
*
|
|
218
|
-
* @example
|
|
219
|
-
* await store.deleteBatchObjects(['user-1', 'user-2']);
|
|
220
|
-
*/
|
|
221
153
|
async deleteBatchObjects(objectKeys: string[]): Promise<void> {
|
|
222
154
|
const params: DeleteObjectsCommandInput = {
|
|
223
155
|
Bucket: this.bucket,
|
|
@@ -228,16 +160,10 @@ export class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
228
160
|
await this.s3.send(new DeleteObjectsCommand(params));
|
|
229
161
|
}
|
|
230
162
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
* @returns The parsed object.
|
|
236
|
-
*
|
|
237
|
-
* @example
|
|
238
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
239
|
-
*/
|
|
240
|
-
async readObject<T>(objectKey: string): Promise<T> {
|
|
163
|
+
async readObject<T>(
|
|
164
|
+
objectKey: string,
|
|
165
|
+
compliance?: ComplianceContext
|
|
166
|
+
): Promise<T> {
|
|
241
167
|
const resp = await this.s3.send(
|
|
242
168
|
new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
243
169
|
);
|
|
@@ -247,34 +173,18 @@ export class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
247
173
|
}
|
|
248
174
|
|
|
249
175
|
const raw = await resp.Body.transformToString();
|
|
250
|
-
return JSON.parse(this.decryptBody(raw)) as T;
|
|
176
|
+
return JSON.parse(this.decryptBody(raw, compliance)) as T;
|
|
251
177
|
}
|
|
252
178
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
* const users = await store.readBatchObjects<{ name: string }>(['user-1', 'user-2']);
|
|
261
|
-
*/
|
|
262
|
-
async readBatchObjects<T>(objectKeys: string[]): Promise<T[]> {
|
|
263
|
-
return Promise.all(objectKeys.map((key) => this.readObject<T>(key)));
|
|
179
|
+
async readBatchObjects<T>(
|
|
180
|
+
objectKeys: string[],
|
|
181
|
+
compliance?: ComplianceContext
|
|
182
|
+
): Promise<T[]> {
|
|
183
|
+
return Promise.all(
|
|
184
|
+
objectKeys.map((key) => this.readObject<T>(key, compliance))
|
|
185
|
+
);
|
|
264
186
|
}
|
|
265
187
|
|
|
266
|
-
/**
|
|
267
|
-
* Streams an object download from the S3 bucket.
|
|
268
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
269
|
-
* Use readObject for encrypted objects.
|
|
270
|
-
* @param objectKey - The key of the object to download.
|
|
271
|
-
* @returns A readable stream of the object's contents.
|
|
272
|
-
* @throws If the S3 response does not include a readable stream.
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* const stream = await store.streamDownloadObject('user-1');
|
|
276
|
-
* stream.pipe(fs.createWriteStream('user-1.json'));
|
|
277
|
-
*/
|
|
278
188
|
async streamDownloadObject(objectKey: string): Promise<Readable> {
|
|
279
189
|
const resp = await this.s3.send(
|
|
280
190
|
new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
@@ -289,27 +199,10 @@ export class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
289
199
|
);
|
|
290
200
|
}
|
|
291
201
|
|
|
292
|
-
/**
|
|
293
|
-
* Streams multiple object downloads from the S3 bucket.
|
|
294
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
295
|
-
* @param objectKeys - The keys of the objects to download.
|
|
296
|
-
* @returns An array of readable streams.
|
|
297
|
-
*
|
|
298
|
-
* @example
|
|
299
|
-
* const streams = await store.streamDownloadBatchObjects(['user-1', 'user-2']);
|
|
300
|
-
* streams[0].pipe(fs.createWriteStream('user-1.json'));
|
|
301
|
-
*/
|
|
302
202
|
async streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]> {
|
|
303
203
|
return Promise.all(objectKeys.map((key) => this.streamDownloadObject(key)));
|
|
304
204
|
}
|
|
305
205
|
|
|
306
|
-
/**
|
|
307
|
-
* Gets the underlying S3 client instance.
|
|
308
|
-
* @returns The S3Client instance used by this store.
|
|
309
|
-
*
|
|
310
|
-
* @example
|
|
311
|
-
* const s3Client = store.getClient();
|
|
312
|
-
*/
|
|
313
206
|
getClient(): S3Client {
|
|
314
207
|
return this.s3;
|
|
315
208
|
}
|
package/lib/index.d.mts
CHANGED
|
@@ -2,6 +2,7 @@ import { S3Client } from '@aws-sdk/client-s3';
|
|
|
2
2
|
import { OpenTelemetryCollector, MetricsDefinition, TelemetryOptions } from '@forklaunch/core/http';
|
|
3
3
|
import { FieldEncryptor } from '@forklaunch/core/persistence';
|
|
4
4
|
import { ObjectStore } from '@forklaunch/core/objectstore';
|
|
5
|
+
import { ComplianceContext } from '@forklaunch/core/cache';
|
|
5
6
|
import { Readable } from 'stream';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -11,17 +12,9 @@ import { Readable } from 'stream';
|
|
|
11
12
|
interface S3EncryptionOptions {
|
|
12
13
|
/** The FieldEncryptor instance to use for encrypting object bodies. */
|
|
13
14
|
encryptor: FieldEncryptor;
|
|
14
|
-
/** Set to true to disable encryption. Defaults to false (encryption enabled). */
|
|
15
|
-
disabled?: boolean;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
17
|
* Options for configuring the S3ObjectStore.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* const options: S3ObjectStoreOptions = {
|
|
22
|
-
* bucket: 'my-bucket',
|
|
23
|
-
* clientConfig: { region: 'us-west-2' }
|
|
24
|
-
* };
|
|
25
18
|
*/
|
|
26
19
|
interface S3ObjectStoreOptions {
|
|
27
20
|
/** The S3 bucket name. */
|
|
@@ -35,14 +28,8 @@ interface S3ObjectStoreOptions {
|
|
|
35
28
|
* S3-backed implementation of the ObjectStore interface.
|
|
36
29
|
* Provides methods for storing, retrieving, streaming, and deleting objects in S3.
|
|
37
30
|
*
|
|
38
|
-
* Encryption is
|
|
39
|
-
*
|
|
40
|
-
* with per-tenant key derivation.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* const store = new S3ObjectStore(otelCollector, { bucket: 'my-bucket' }, telemetryOptions);
|
|
44
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
45
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
31
|
+
* Encryption is activated per-operation when a `compliance` context is provided.
|
|
32
|
+
* Without it, object bodies are stored and read as plaintext.
|
|
46
33
|
*/
|
|
47
34
|
declare class S3ObjectStore implements ObjectStore<S3Client> {
|
|
48
35
|
private openTelemetryCollector;
|
|
@@ -51,136 +38,28 @@ declare class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
51
38
|
private bucket;
|
|
52
39
|
private initialized;
|
|
53
40
|
private encryptor?;
|
|
54
|
-
private encryptionDisabled;
|
|
55
|
-
/**
|
|
56
|
-
* Creates a new S3ObjectStore instance.
|
|
57
|
-
* @param openTelemetryCollector - Collector for OpenTelemetry metrics.
|
|
58
|
-
* @param options - S3 configuration options.
|
|
59
|
-
* @param telemetryOptions - Telemetry configuration options.
|
|
60
|
-
* @param encryption - Encryption configuration (enabled by default when encryptor provided).
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* const store = new S3ObjectStore(
|
|
64
|
-
* otelCollector,
|
|
65
|
-
* { bucket: 'my-bucket' },
|
|
66
|
-
* telemetryOptions,
|
|
67
|
-
* { encryptor }
|
|
68
|
-
* );
|
|
69
|
-
*/
|
|
70
41
|
constructor(openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>, options: S3ObjectStoreOptions, telemetryOptions: TelemetryOptions, encryption: S3EncryptionOptions);
|
|
71
42
|
private encryptBody;
|
|
72
43
|
private decryptBody;
|
|
73
44
|
private ensureBucketExists;
|
|
74
|
-
/**
|
|
75
|
-
* Stores an object in the S3 bucket.
|
|
76
|
-
* @template T - The type of the object being stored.
|
|
77
|
-
* @param object - The object to store. Must include a `key` property.
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
81
|
-
*/
|
|
82
45
|
putObject<T>(object: T & {
|
|
83
46
|
key: string;
|
|
84
|
-
}): Promise<void>;
|
|
85
|
-
/**
|
|
86
|
-
* Stores multiple objects in the S3 bucket.
|
|
87
|
-
* @template T - The type of the objects being stored.
|
|
88
|
-
* @param objects - The objects to store. Each must include a `key` property.
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* await store.putBatchObjects([
|
|
92
|
-
* { key: 'user-1', name: 'Alice' },
|
|
93
|
-
* { key: 'user-2', name: 'Bob' }
|
|
94
|
-
* ]);
|
|
95
|
-
*/
|
|
47
|
+
}, compliance?: ComplianceContext): Promise<void>;
|
|
96
48
|
putBatchObjects<T>(objects: (T & {
|
|
97
49
|
key: string;
|
|
98
|
-
})[]): Promise<void>;
|
|
99
|
-
/**
|
|
100
|
-
* Streams an object upload to the S3 bucket.
|
|
101
|
-
* For compatibility; uses putObject internally.
|
|
102
|
-
* @template T - The type of the object being stored.
|
|
103
|
-
* @param object - The object to stream-upload. Must include a `key` property.
|
|
104
|
-
*/
|
|
50
|
+
})[], compliance?: ComplianceContext): Promise<void>;
|
|
105
51
|
streamUploadObject<T>(object: T & {
|
|
106
52
|
key: string;
|
|
107
|
-
}): Promise<void>;
|
|
108
|
-
/**
|
|
109
|
-
* Streams multiple object uploads to the S3 bucket.
|
|
110
|
-
* For compatibility; uses putBatchObjects internally.
|
|
111
|
-
* @template T - The type of the objects being stored.
|
|
112
|
-
* @param objects - The objects to stream-upload. Each must include a `key` property.
|
|
113
|
-
*/
|
|
53
|
+
}, compliance?: ComplianceContext): Promise<void>;
|
|
114
54
|
streamUploadBatchObjects<T>(objects: (T & {
|
|
115
55
|
key: string;
|
|
116
|
-
})[]): Promise<void>;
|
|
117
|
-
/**
|
|
118
|
-
* Deletes an object from the S3 bucket.
|
|
119
|
-
* @param objectKey - The key of the object to delete.
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* await store.deleteObject('user-1');
|
|
123
|
-
*/
|
|
56
|
+
})[], compliance?: ComplianceContext): Promise<void>;
|
|
124
57
|
deleteObject(objectKey: string): Promise<void>;
|
|
125
|
-
/**
|
|
126
|
-
* Deletes multiple objects from the S3 bucket.
|
|
127
|
-
* @param objectKeys - The keys of the objects to delete.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* await store.deleteBatchObjects(['user-1', 'user-2']);
|
|
131
|
-
*/
|
|
132
58
|
deleteBatchObjects(objectKeys: string[]): Promise<void>;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* @template T - The expected type of the object.
|
|
136
|
-
* @param objectKey - The key of the object to read.
|
|
137
|
-
* @returns The parsed object.
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
141
|
-
*/
|
|
142
|
-
readObject<T>(objectKey: string): Promise<T>;
|
|
143
|
-
/**
|
|
144
|
-
* Reads multiple objects from the S3 bucket.
|
|
145
|
-
* @template T - The expected type of the objects.
|
|
146
|
-
* @param objectKeys - The keys of the objects to read.
|
|
147
|
-
* @returns An array of parsed objects.
|
|
148
|
-
*
|
|
149
|
-
* @example
|
|
150
|
-
* const users = await store.readBatchObjects<{ name: string }>(['user-1', 'user-2']);
|
|
151
|
-
*/
|
|
152
|
-
readBatchObjects<T>(objectKeys: string[]): Promise<T[]>;
|
|
153
|
-
/**
|
|
154
|
-
* Streams an object download from the S3 bucket.
|
|
155
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
156
|
-
* Use readObject for encrypted objects.
|
|
157
|
-
* @param objectKey - The key of the object to download.
|
|
158
|
-
* @returns A readable stream of the object's contents.
|
|
159
|
-
* @throws If the S3 response does not include a readable stream.
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* const stream = await store.streamDownloadObject('user-1');
|
|
163
|
-
* stream.pipe(fs.createWriteStream('user-1.json'));
|
|
164
|
-
*/
|
|
59
|
+
readObject<T>(objectKey: string, compliance?: ComplianceContext): Promise<T>;
|
|
60
|
+
readBatchObjects<T>(objectKeys: string[], compliance?: ComplianceContext): Promise<T[]>;
|
|
165
61
|
streamDownloadObject(objectKey: string): Promise<Readable>;
|
|
166
|
-
/**
|
|
167
|
-
* Streams multiple object downloads from the S3 bucket.
|
|
168
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
169
|
-
* @param objectKeys - The keys of the objects to download.
|
|
170
|
-
* @returns An array of readable streams.
|
|
171
|
-
*
|
|
172
|
-
* @example
|
|
173
|
-
* const streams = await store.streamDownloadBatchObjects(['user-1', 'user-2']);
|
|
174
|
-
* streams[0].pipe(fs.createWriteStream('user-1.json'));
|
|
175
|
-
*/
|
|
176
62
|
streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]>;
|
|
177
|
-
/**
|
|
178
|
-
* Gets the underlying S3 client instance.
|
|
179
|
-
* @returns The S3Client instance used by this store.
|
|
180
|
-
*
|
|
181
|
-
* @example
|
|
182
|
-
* const s3Client = store.getClient();
|
|
183
|
-
*/
|
|
184
63
|
getClient(): S3Client;
|
|
185
64
|
}
|
|
186
65
|
|
package/lib/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { S3Client } from '@aws-sdk/client-s3';
|
|
|
2
2
|
import { OpenTelemetryCollector, MetricsDefinition, TelemetryOptions } from '@forklaunch/core/http';
|
|
3
3
|
import { FieldEncryptor } from '@forklaunch/core/persistence';
|
|
4
4
|
import { ObjectStore } from '@forklaunch/core/objectstore';
|
|
5
|
+
import { ComplianceContext } from '@forklaunch/core/cache';
|
|
5
6
|
import { Readable } from 'stream';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -11,17 +12,9 @@ import { Readable } from 'stream';
|
|
|
11
12
|
interface S3EncryptionOptions {
|
|
12
13
|
/** The FieldEncryptor instance to use for encrypting object bodies. */
|
|
13
14
|
encryptor: FieldEncryptor;
|
|
14
|
-
/** Set to true to disable encryption. Defaults to false (encryption enabled). */
|
|
15
|
-
disabled?: boolean;
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
17
|
* Options for configuring the S3ObjectStore.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* const options: S3ObjectStoreOptions = {
|
|
22
|
-
* bucket: 'my-bucket',
|
|
23
|
-
* clientConfig: { region: 'us-west-2' }
|
|
24
|
-
* };
|
|
25
18
|
*/
|
|
26
19
|
interface S3ObjectStoreOptions {
|
|
27
20
|
/** The S3 bucket name. */
|
|
@@ -35,14 +28,8 @@ interface S3ObjectStoreOptions {
|
|
|
35
28
|
* S3-backed implementation of the ObjectStore interface.
|
|
36
29
|
* Provides methods for storing, retrieving, streaming, and deleting objects in S3.
|
|
37
30
|
*
|
|
38
|
-
* Encryption is
|
|
39
|
-
*
|
|
40
|
-
* with per-tenant key derivation.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* const store = new S3ObjectStore(otelCollector, { bucket: 'my-bucket' }, telemetryOptions);
|
|
44
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
45
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
31
|
+
* Encryption is activated per-operation when a `compliance` context is provided.
|
|
32
|
+
* Without it, object bodies are stored and read as plaintext.
|
|
46
33
|
*/
|
|
47
34
|
declare class S3ObjectStore implements ObjectStore<S3Client> {
|
|
48
35
|
private openTelemetryCollector;
|
|
@@ -51,136 +38,28 @@ declare class S3ObjectStore implements ObjectStore<S3Client> {
|
|
|
51
38
|
private bucket;
|
|
52
39
|
private initialized;
|
|
53
40
|
private encryptor?;
|
|
54
|
-
private encryptionDisabled;
|
|
55
|
-
/**
|
|
56
|
-
* Creates a new S3ObjectStore instance.
|
|
57
|
-
* @param openTelemetryCollector - Collector for OpenTelemetry metrics.
|
|
58
|
-
* @param options - S3 configuration options.
|
|
59
|
-
* @param telemetryOptions - Telemetry configuration options.
|
|
60
|
-
* @param encryption - Encryption configuration (enabled by default when encryptor provided).
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* const store = new S3ObjectStore(
|
|
64
|
-
* otelCollector,
|
|
65
|
-
* { bucket: 'my-bucket' },
|
|
66
|
-
* telemetryOptions,
|
|
67
|
-
* { encryptor }
|
|
68
|
-
* );
|
|
69
|
-
*/
|
|
70
41
|
constructor(openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>, options: S3ObjectStoreOptions, telemetryOptions: TelemetryOptions, encryption: S3EncryptionOptions);
|
|
71
42
|
private encryptBody;
|
|
72
43
|
private decryptBody;
|
|
73
44
|
private ensureBucketExists;
|
|
74
|
-
/**
|
|
75
|
-
* Stores an object in the S3 bucket.
|
|
76
|
-
* @template T - The type of the object being stored.
|
|
77
|
-
* @param object - The object to store. Must include a `key` property.
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
81
|
-
*/
|
|
82
45
|
putObject<T>(object: T & {
|
|
83
46
|
key: string;
|
|
84
|
-
}): Promise<void>;
|
|
85
|
-
/**
|
|
86
|
-
* Stores multiple objects in the S3 bucket.
|
|
87
|
-
* @template T - The type of the objects being stored.
|
|
88
|
-
* @param objects - The objects to store. Each must include a `key` property.
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* await store.putBatchObjects([
|
|
92
|
-
* { key: 'user-1', name: 'Alice' },
|
|
93
|
-
* { key: 'user-2', name: 'Bob' }
|
|
94
|
-
* ]);
|
|
95
|
-
*/
|
|
47
|
+
}, compliance?: ComplianceContext): Promise<void>;
|
|
96
48
|
putBatchObjects<T>(objects: (T & {
|
|
97
49
|
key: string;
|
|
98
|
-
})[]): Promise<void>;
|
|
99
|
-
/**
|
|
100
|
-
* Streams an object upload to the S3 bucket.
|
|
101
|
-
* For compatibility; uses putObject internally.
|
|
102
|
-
* @template T - The type of the object being stored.
|
|
103
|
-
* @param object - The object to stream-upload. Must include a `key` property.
|
|
104
|
-
*/
|
|
50
|
+
})[], compliance?: ComplianceContext): Promise<void>;
|
|
105
51
|
streamUploadObject<T>(object: T & {
|
|
106
52
|
key: string;
|
|
107
|
-
}): Promise<void>;
|
|
108
|
-
/**
|
|
109
|
-
* Streams multiple object uploads to the S3 bucket.
|
|
110
|
-
* For compatibility; uses putBatchObjects internally.
|
|
111
|
-
* @template T - The type of the objects being stored.
|
|
112
|
-
* @param objects - The objects to stream-upload. Each must include a `key` property.
|
|
113
|
-
*/
|
|
53
|
+
}, compliance?: ComplianceContext): Promise<void>;
|
|
114
54
|
streamUploadBatchObjects<T>(objects: (T & {
|
|
115
55
|
key: string;
|
|
116
|
-
})[]): Promise<void>;
|
|
117
|
-
/**
|
|
118
|
-
* Deletes an object from the S3 bucket.
|
|
119
|
-
* @param objectKey - The key of the object to delete.
|
|
120
|
-
*
|
|
121
|
-
* @example
|
|
122
|
-
* await store.deleteObject('user-1');
|
|
123
|
-
*/
|
|
56
|
+
})[], compliance?: ComplianceContext): Promise<void>;
|
|
124
57
|
deleteObject(objectKey: string): Promise<void>;
|
|
125
|
-
/**
|
|
126
|
-
* Deletes multiple objects from the S3 bucket.
|
|
127
|
-
* @param objectKeys - The keys of the objects to delete.
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* await store.deleteBatchObjects(['user-1', 'user-2']);
|
|
131
|
-
*/
|
|
132
58
|
deleteBatchObjects(objectKeys: string[]): Promise<void>;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* @template T - The expected type of the object.
|
|
136
|
-
* @param objectKey - The key of the object to read.
|
|
137
|
-
* @returns The parsed object.
|
|
138
|
-
*
|
|
139
|
-
* @example
|
|
140
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
141
|
-
*/
|
|
142
|
-
readObject<T>(objectKey: string): Promise<T>;
|
|
143
|
-
/**
|
|
144
|
-
* Reads multiple objects from the S3 bucket.
|
|
145
|
-
* @template T - The expected type of the objects.
|
|
146
|
-
* @param objectKeys - The keys of the objects to read.
|
|
147
|
-
* @returns An array of parsed objects.
|
|
148
|
-
*
|
|
149
|
-
* @example
|
|
150
|
-
* const users = await store.readBatchObjects<{ name: string }>(['user-1', 'user-2']);
|
|
151
|
-
*/
|
|
152
|
-
readBatchObjects<T>(objectKeys: string[]): Promise<T[]>;
|
|
153
|
-
/**
|
|
154
|
-
* Streams an object download from the S3 bucket.
|
|
155
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
156
|
-
* Use readObject for encrypted objects.
|
|
157
|
-
* @param objectKey - The key of the object to download.
|
|
158
|
-
* @returns A readable stream of the object's contents.
|
|
159
|
-
* @throws If the S3 response does not include a readable stream.
|
|
160
|
-
*
|
|
161
|
-
* @example
|
|
162
|
-
* const stream = await store.streamDownloadObject('user-1');
|
|
163
|
-
* stream.pipe(fs.createWriteStream('user-1.json'));
|
|
164
|
-
*/
|
|
59
|
+
readObject<T>(objectKey: string, compliance?: ComplianceContext): Promise<T>;
|
|
60
|
+
readBatchObjects<T>(objectKeys: string[], compliance?: ComplianceContext): Promise<T[]>;
|
|
165
61
|
streamDownloadObject(objectKey: string): Promise<Readable>;
|
|
166
|
-
/**
|
|
167
|
-
* Streams multiple object downloads from the S3 bucket.
|
|
168
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
169
|
-
* @param objectKeys - The keys of the objects to download.
|
|
170
|
-
* @returns An array of readable streams.
|
|
171
|
-
*
|
|
172
|
-
* @example
|
|
173
|
-
* const streams = await store.streamDownloadBatchObjects(['user-1', 'user-2']);
|
|
174
|
-
* streams[0].pipe(fs.createWriteStream('user-1.json'));
|
|
175
|
-
*/
|
|
176
62
|
streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]>;
|
|
177
|
-
/**
|
|
178
|
-
* Gets the underlying S3 client instance.
|
|
179
|
-
* @returns The S3Client instance used by this store.
|
|
180
|
-
*
|
|
181
|
-
* @example
|
|
182
|
-
* const s3Client = store.getClient();
|
|
183
|
-
*/
|
|
184
63
|
getClient(): S3Client;
|
|
185
64
|
}
|
|
186
65
|
|
package/lib/index.js
CHANGED
|
@@ -24,28 +24,12 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
27
|
-
var import_persistence = require("@forklaunch/core/persistence");
|
|
28
27
|
var import_stream = require("stream");
|
|
29
28
|
var ENCRYPTED_PREFIXES = ["v1:", "v2:"];
|
|
30
29
|
function isEncrypted(value) {
|
|
31
30
|
return ENCRYPTED_PREFIXES.some((p) => value.startsWith(p));
|
|
32
31
|
}
|
|
33
32
|
var S3ObjectStore = class {
|
|
34
|
-
/**
|
|
35
|
-
* Creates a new S3ObjectStore instance.
|
|
36
|
-
* @param openTelemetryCollector - Collector for OpenTelemetry metrics.
|
|
37
|
-
* @param options - S3 configuration options.
|
|
38
|
-
* @param telemetryOptions - Telemetry configuration options.
|
|
39
|
-
* @param encryption - Encryption configuration (enabled by default when encryptor provided).
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* const store = new S3ObjectStore(
|
|
43
|
-
* otelCollector,
|
|
44
|
-
* { bucket: 'my-bucket' },
|
|
45
|
-
* telemetryOptions,
|
|
46
|
-
* { encryptor }
|
|
47
|
-
* );
|
|
48
|
-
*/
|
|
49
33
|
constructor(openTelemetryCollector, options, telemetryOptions, encryption) {
|
|
50
34
|
this.openTelemetryCollector = openTelemetryCollector;
|
|
51
35
|
this.telemetryOptions = telemetryOptions;
|
|
@@ -53,25 +37,25 @@ var S3ObjectStore = class {
|
|
|
53
37
|
this.bucket = options.bucket;
|
|
54
38
|
this.initialized = false;
|
|
55
39
|
this.encryptor = encryption.encryptor;
|
|
56
|
-
this.encryptionDisabled = encryption.disabled ?? false;
|
|
57
40
|
}
|
|
41
|
+
openTelemetryCollector;
|
|
42
|
+
telemetryOptions;
|
|
58
43
|
s3;
|
|
59
44
|
bucket;
|
|
60
45
|
initialized;
|
|
61
46
|
encryptor;
|
|
62
|
-
encryptionDisabled;
|
|
63
47
|
// ---------------------------------------------------------------------------
|
|
64
|
-
// Encryption helpers
|
|
48
|
+
// Encryption helpers — only active when compliance context is provided
|
|
65
49
|
// ---------------------------------------------------------------------------
|
|
66
|
-
encryptBody(body) {
|
|
67
|
-
if (!
|
|
68
|
-
return this.encryptor.encrypt(body,
|
|
50
|
+
encryptBody(body, compliance) {
|
|
51
|
+
if (!compliance || !this.encryptor) return body;
|
|
52
|
+
return this.encryptor.encrypt(body, compliance.tenantId) ?? body;
|
|
69
53
|
}
|
|
70
|
-
decryptBody(body) {
|
|
71
|
-
if (!
|
|
54
|
+
decryptBody(body, compliance) {
|
|
55
|
+
if (!compliance || !this.encryptor) return body;
|
|
72
56
|
if (!isEncrypted(body)) return body;
|
|
73
57
|
try {
|
|
74
|
-
return this.encryptor.decrypt(body,
|
|
58
|
+
return this.encryptor.decrypt(body, compliance.tenantId) ?? body;
|
|
75
59
|
} catch {
|
|
76
60
|
return body;
|
|
77
61
|
}
|
|
@@ -87,20 +71,12 @@ var S3ObjectStore = class {
|
|
|
87
71
|
}
|
|
88
72
|
this.initialized = true;
|
|
89
73
|
}
|
|
90
|
-
|
|
91
|
-
* Stores an object in the S3 bucket.
|
|
92
|
-
* @template T - The type of the object being stored.
|
|
93
|
-
* @param object - The object to store. Must include a `key` property.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
97
|
-
*/
|
|
98
|
-
async putObject(object) {
|
|
74
|
+
async putObject(object, compliance) {
|
|
99
75
|
if (!this.initialized) {
|
|
100
76
|
await this.ensureBucketExists();
|
|
101
77
|
}
|
|
102
78
|
const { key, ...rest } = object;
|
|
103
|
-
const body = this.encryptBody(JSON.stringify(rest));
|
|
79
|
+
const body = this.encryptBody(JSON.stringify(rest), compliance);
|
|
104
80
|
const params = {
|
|
105
81
|
Bucket: this.bucket,
|
|
106
82
|
Key: key,
|
|
@@ -109,57 +85,20 @@ var S3ObjectStore = class {
|
|
|
109
85
|
};
|
|
110
86
|
await this.s3.send(new import_client_s3.PutObjectCommand(params));
|
|
111
87
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* ]);
|
|
122
|
-
*/
|
|
123
|
-
async putBatchObjects(objects) {
|
|
124
|
-
await Promise.all(objects.map((obj) => this.putObject(obj)));
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Streams an object upload to the S3 bucket.
|
|
128
|
-
* For compatibility; uses putObject internally.
|
|
129
|
-
* @template T - The type of the object being stored.
|
|
130
|
-
* @param object - The object to stream-upload. Must include a `key` property.
|
|
131
|
-
*/
|
|
132
|
-
async streamUploadObject(object) {
|
|
133
|
-
await this.putObject(object);
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Streams multiple object uploads to the S3 bucket.
|
|
137
|
-
* For compatibility; uses putBatchObjects internally.
|
|
138
|
-
* @template T - The type of the objects being stored.
|
|
139
|
-
* @param objects - The objects to stream-upload. Each must include a `key` property.
|
|
140
|
-
*/
|
|
141
|
-
async streamUploadBatchObjects(objects) {
|
|
142
|
-
await this.putBatchObjects(objects);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Deletes an object from the S3 bucket.
|
|
146
|
-
* @param objectKey - The key of the object to delete.
|
|
147
|
-
*
|
|
148
|
-
* @example
|
|
149
|
-
* await store.deleteObject('user-1');
|
|
150
|
-
*/
|
|
88
|
+
async putBatchObjects(objects, compliance) {
|
|
89
|
+
await Promise.all(objects.map((obj) => this.putObject(obj, compliance)));
|
|
90
|
+
}
|
|
91
|
+
async streamUploadObject(object, compliance) {
|
|
92
|
+
await this.putObject(object, compliance);
|
|
93
|
+
}
|
|
94
|
+
async streamUploadBatchObjects(objects, compliance) {
|
|
95
|
+
await this.putBatchObjects(objects, compliance);
|
|
96
|
+
}
|
|
151
97
|
async deleteObject(objectKey) {
|
|
152
98
|
await this.s3.send(
|
|
153
99
|
new import_client_s3.DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
154
100
|
);
|
|
155
101
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Deletes multiple objects from the S3 bucket.
|
|
158
|
-
* @param objectKeys - The keys of the objects to delete.
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* await store.deleteBatchObjects(['user-1', 'user-2']);
|
|
162
|
-
*/
|
|
163
102
|
async deleteBatchObjects(objectKeys) {
|
|
164
103
|
const params = {
|
|
165
104
|
Bucket: this.bucket,
|
|
@@ -169,16 +108,7 @@ var S3ObjectStore = class {
|
|
|
169
108
|
};
|
|
170
109
|
await this.s3.send(new import_client_s3.DeleteObjectsCommand(params));
|
|
171
110
|
}
|
|
172
|
-
|
|
173
|
-
* Reads an object from the S3 bucket.
|
|
174
|
-
* @template T - The expected type of the object.
|
|
175
|
-
* @param objectKey - The key of the object to read.
|
|
176
|
-
* @returns The parsed object.
|
|
177
|
-
*
|
|
178
|
-
* @example
|
|
179
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
180
|
-
*/
|
|
181
|
-
async readObject(objectKey) {
|
|
111
|
+
async readObject(objectKey, compliance) {
|
|
182
112
|
const resp = await this.s3.send(
|
|
183
113
|
new import_client_s3.GetObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
184
114
|
);
|
|
@@ -186,32 +116,13 @@ var S3ObjectStore = class {
|
|
|
186
116
|
throw new Error("S3 did not return a body");
|
|
187
117
|
}
|
|
188
118
|
const raw = await resp.Body.transformToString();
|
|
189
|
-
return JSON.parse(this.decryptBody(raw));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* const users = await store.readBatchObjects<{ name: string }>(['user-1', 'user-2']);
|
|
199
|
-
*/
|
|
200
|
-
async readBatchObjects(objectKeys) {
|
|
201
|
-
return Promise.all(objectKeys.map((key) => this.readObject(key)));
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Streams an object download from the S3 bucket.
|
|
205
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
206
|
-
* Use readObject for encrypted objects.
|
|
207
|
-
* @param objectKey - The key of the object to download.
|
|
208
|
-
* @returns A readable stream of the object's contents.
|
|
209
|
-
* @throws If the S3 response does not include a readable stream.
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* const stream = await store.streamDownloadObject('user-1');
|
|
213
|
-
* stream.pipe(fs.createWriteStream('user-1.json'));
|
|
214
|
-
*/
|
|
119
|
+
return JSON.parse(this.decryptBody(raw, compliance));
|
|
120
|
+
}
|
|
121
|
+
async readBatchObjects(objectKeys, compliance) {
|
|
122
|
+
return Promise.all(
|
|
123
|
+
objectKeys.map((key) => this.readObject(key, compliance))
|
|
124
|
+
);
|
|
125
|
+
}
|
|
215
126
|
async streamDownloadObject(objectKey) {
|
|
216
127
|
const resp = await this.s3.send(
|
|
217
128
|
new import_client_s3.GetObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
@@ -224,26 +135,9 @@ var S3ObjectStore = class {
|
|
|
224
135
|
webStream
|
|
225
136
|
);
|
|
226
137
|
}
|
|
227
|
-
/**
|
|
228
|
-
* Streams multiple object downloads from the S3 bucket.
|
|
229
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
230
|
-
* @param objectKeys - The keys of the objects to download.
|
|
231
|
-
* @returns An array of readable streams.
|
|
232
|
-
*
|
|
233
|
-
* @example
|
|
234
|
-
* const streams = await store.streamDownloadBatchObjects(['user-1', 'user-2']);
|
|
235
|
-
* streams[0].pipe(fs.createWriteStream('user-1.json'));
|
|
236
|
-
*/
|
|
237
138
|
async streamDownloadBatchObjects(objectKeys) {
|
|
238
139
|
return Promise.all(objectKeys.map((key) => this.streamDownloadObject(key)));
|
|
239
140
|
}
|
|
240
|
-
/**
|
|
241
|
-
* Gets the underlying S3 client instance.
|
|
242
|
-
* @returns The S3Client instance used by this store.
|
|
243
|
-
*
|
|
244
|
-
* @example
|
|
245
|
-
* const s3Client = store.getClient();
|
|
246
|
-
*/
|
|
247
141
|
getClient() {
|
|
248
142
|
return this.s3;
|
|
249
143
|
}
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["import {\n CreateBucketCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n DeleteObjectsCommandInput,\n GetObjectCommand,\n HeadBucketCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n S3Client\n} from '@aws-sdk/client-s3';\nimport {\n MetricsDefinition,\n OpenTelemetryCollector,\n TelemetryOptions\n} from '@forklaunch/core/http';\nimport {\n getCurrentTenantId,\n type FieldEncryptor\n} from '@forklaunch/core/persistence';\nimport { ObjectStore } from '@forklaunch/core/objectstore';\nimport { Readable } from 'stream';\n\nconst ENCRYPTED_PREFIXES = ['v1:', 'v2:'] as const;\n\nfunction isEncrypted(value: string): boolean {\n return ENCRYPTED_PREFIXES.some((p) => value.startsWith(p));\n}\n\n/**\n * Options for configuring encryption on the S3 object store.\n * Required — every consumer must explicitly configure encryption.\n */\nexport interface S3EncryptionOptions {\n /** The FieldEncryptor instance to use for encrypting object bodies. */\n encryptor: FieldEncryptor;\n /** Set to true to disable encryption. Defaults to false (encryption enabled). */\n disabled?: boolean;\n}\n\n/**\n * Options for configuring the S3ObjectStore.\n *\n * @example\n * const options: S3ObjectStoreOptions = {\n * bucket: 'my-bucket',\n * clientConfig: { region: 'us-west-2' }\n * };\n */\ninterface S3ObjectStoreOptions {\n /** The S3 bucket name. */\n bucket: string;\n /** Optional existing S3 client instance. */\n client?: S3Client;\n /** Optional configuration for creating a new S3 client. */\n clientConfig?: ConstructorParameters<typeof S3Client>[0];\n}\n\n/**\n * S3-backed implementation of the ObjectStore interface.\n * Provides methods for storing, retrieving, streaming, and deleting objects in S3.\n *\n * Encryption is enabled by default when an encryptor is provided. Object bodies\n * are encrypted before upload and decrypted after download using AES-256-GCM\n * with per-tenant key derivation.\n *\n * @example\n * const store = new S3ObjectStore(otelCollector, { bucket: 'my-bucket' }, telemetryOptions);\n * await store.putObject({ key: 'user-1', name: 'Alice' });\n * const user = await store.readObject<{ name: string }>('user-1');\n */\nexport class S3ObjectStore implements ObjectStore<S3Client> {\n private s3: S3Client;\n private bucket: string;\n private initialized: boolean;\n private encryptor?: FieldEncryptor;\n private encryptionDisabled: boolean;\n\n /**\n * Creates a new S3ObjectStore instance.\n * @param openTelemetryCollector - Collector for OpenTelemetry metrics.\n * @param options - S3 configuration options.\n * @param telemetryOptions - Telemetry configuration options.\n * @param encryption - Encryption configuration (enabled by default when encryptor provided).\n *\n * @example\n * const store = new S3ObjectStore(\n * otelCollector,\n * { bucket: 'my-bucket' },\n * telemetryOptions,\n * { encryptor }\n * );\n */\n constructor(\n private openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>,\n options: S3ObjectStoreOptions,\n private telemetryOptions: TelemetryOptions,\n encryption: S3EncryptionOptions\n ) {\n this.s3 = options.client || new S3Client(options.clientConfig || {});\n this.bucket = options.bucket;\n this.initialized = false;\n this.encryptor = encryption.encryptor;\n this.encryptionDisabled = encryption.disabled ?? false;\n }\n\n // ---------------------------------------------------------------------------\n // Encryption helpers\n // ---------------------------------------------------------------------------\n\n private encryptBody(body: string): string {\n if (!this.encryptor || this.encryptionDisabled) return body;\n return this.encryptor.encrypt(body, getCurrentTenantId()) ?? body;\n }\n\n private decryptBody(body: string): string {\n if (!this.encryptor || this.encryptionDisabled) return body;\n if (!isEncrypted(body)) return body;\n try {\n return this.encryptor.decrypt(body, getCurrentTenantId()) ?? body;\n } catch {\n return body;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private async ensureBucketExists() {\n try {\n await this.s3.send(new HeadBucketCommand({ Bucket: this.bucket }));\n } catch {\n await this.s3.send(new CreateBucketCommand({ Bucket: this.bucket }));\n }\n\n this.initialized = true;\n }\n\n /**\n * Stores an object in the S3 bucket.\n * @template T - The type of the object being stored.\n * @param object - The object to store. Must include a `key` property.\n *\n * @example\n * await store.putObject({ key: 'user-1', name: 'Alice' });\n */\n async putObject<T>(object: T & { key: string }): Promise<void> {\n if (!this.initialized) {\n await this.ensureBucketExists();\n }\n\n const { key, ...rest } = object;\n const body = this.encryptBody(JSON.stringify(rest));\n const params: PutObjectCommandInput = {\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: 'application/json'\n };\n await this.s3.send(new PutObjectCommand(params));\n }\n\n /**\n * Stores multiple objects in the S3 bucket.\n * @template T - The type of the objects being stored.\n * @param objects - The objects to store. Each must include a `key` property.\n *\n * @example\n * await store.putBatchObjects([\n * { key: 'user-1', name: 'Alice' },\n * { key: 'user-2', name: 'Bob' }\n * ]);\n */\n async putBatchObjects<T>(objects: (T & { key: string })[]): Promise<void> {\n await Promise.all(objects.map((obj) => this.putObject(obj)));\n }\n\n /**\n * Streams an object upload to the S3 bucket.\n * For compatibility; uses putObject internally.\n * @template T - The type of the object being stored.\n * @param object - The object to stream-upload. Must include a `key` property.\n */\n async streamUploadObject<T>(object: T & { key: string }): Promise<void> {\n await this.putObject(object);\n }\n\n /**\n * Streams multiple object uploads to the S3 bucket.\n * For compatibility; uses putBatchObjects internally.\n * @template T - The type of the objects being stored.\n * @param objects - The objects to stream-upload. Each must include a `key` property.\n */\n async streamUploadBatchObjects<T>(\n objects: (T & { key: string })[]\n ): Promise<void> {\n await this.putBatchObjects(objects);\n }\n\n /**\n * Deletes an object from the S3 bucket.\n * @param objectKey - The key of the object to delete.\n *\n * @example\n * await store.deleteObject('user-1');\n */\n async deleteObject(objectKey: string): Promise<void> {\n await this.s3.send(\n new DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n }\n\n /**\n * Deletes multiple objects from the S3 bucket.\n * @param objectKeys - The keys of the objects to delete.\n *\n * @example\n * await store.deleteBatchObjects(['user-1', 'user-2']);\n */\n async deleteBatchObjects(objectKeys: string[]): Promise<void> {\n const params: DeleteObjectsCommandInput = {\n Bucket: this.bucket,\n Delete: {\n Objects: objectKeys.map((Key) => ({ Key }))\n }\n };\n await this.s3.send(new DeleteObjectsCommand(params));\n }\n\n /**\n * Reads an object from the S3 bucket.\n * @template T - The expected type of the object.\n * @param objectKey - The key of the object to read.\n * @returns The parsed object.\n *\n * @example\n * const user = await store.readObject<{ name: string }>('user-1');\n */\n async readObject<T>(objectKey: string): Promise<T> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n\n if (!resp.Body) {\n throw new Error('S3 did not return a body');\n }\n\n const raw = await resp.Body.transformToString();\n return JSON.parse(this.decryptBody(raw)) as T;\n }\n\n /**\n * Reads multiple objects from the S3 bucket.\n * @template T - The expected type of the objects.\n * @param objectKeys - The keys of the objects to read.\n * @returns An array of parsed objects.\n *\n * @example\n * const users = await store.readBatchObjects<{ name: string }>(['user-1', 'user-2']);\n */\n async readBatchObjects<T>(objectKeys: string[]): Promise<T[]> {\n return Promise.all(objectKeys.map((key) => this.readObject<T>(key)));\n }\n\n /**\n * Streams an object download from the S3 bucket.\n * Note: Streaming bypasses application-level encryption/decryption.\n * Use readObject for encrypted objects.\n * @param objectKey - The key of the object to download.\n * @returns A readable stream of the object's contents.\n * @throws If the S3 response does not include a readable stream.\n *\n * @example\n * const stream = await store.streamDownloadObject('user-1');\n * stream.pipe(fs.createWriteStream('user-1.json'));\n */\n async streamDownloadObject(objectKey: string): Promise<Readable> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n const webStream = resp.Body?.transformToWebStream();\n if (!webStream) {\n throw new Error('S3 did not return a stream');\n }\n\n return Readable.fromWeb(\n webStream as Parameters<typeof Readable.fromWeb>[0]\n );\n }\n\n /**\n * Streams multiple object downloads from the S3 bucket.\n * Note: Streaming bypasses application-level encryption/decryption.\n * @param objectKeys - The keys of the objects to download.\n * @returns An array of readable streams.\n *\n * @example\n * const streams = await store.streamDownloadBatchObjects(['user-1', 'user-2']);\n * streams[0].pipe(fs.createWriteStream('user-1.json'));\n */\n async streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]> {\n return Promise.all(objectKeys.map((key) => this.streamDownloadObject(key)));\n }\n\n /**\n * Gets the underlying S3 client instance.\n * @returns The S3Client instance used by this store.\n *\n * @example\n * const s3Client = store.getClient();\n */\n getClient(): S3Client {\n return this.s3;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["import {\n CreateBucketCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n DeleteObjectsCommandInput,\n GetObjectCommand,\n HeadBucketCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n S3Client\n} from '@aws-sdk/client-s3';\nimport {\n MetricsDefinition,\n OpenTelemetryCollector,\n TelemetryOptions\n} from '@forklaunch/core/http';\nimport { type FieldEncryptor } from '@forklaunch/core/persistence';\nimport { ObjectStore } from '@forklaunch/core/objectstore';\nimport type { ComplianceContext } from '@forklaunch/core/cache';\nimport { Readable } from 'stream';\n\nconst ENCRYPTED_PREFIXES = ['v1:', 'v2:'] as const;\n\nfunction isEncrypted(value: string): boolean {\n return ENCRYPTED_PREFIXES.some((p) => value.startsWith(p));\n}\n\n/**\n * Options for configuring encryption on the S3 object store.\n * Required — every consumer must explicitly configure encryption.\n */\nexport interface S3EncryptionOptions {\n /** The FieldEncryptor instance to use for encrypting object bodies. */\n encryptor: FieldEncryptor;\n}\n\n/**\n * Options for configuring the S3ObjectStore.\n */\ninterface S3ObjectStoreOptions {\n /** The S3 bucket name. */\n bucket: string;\n /** Optional existing S3 client instance. */\n client?: S3Client;\n /** Optional configuration for creating a new S3 client. */\n clientConfig?: ConstructorParameters<typeof S3Client>[0];\n}\n\n/**\n * S3-backed implementation of the ObjectStore interface.\n * Provides methods for storing, retrieving, streaming, and deleting objects in S3.\n *\n * Encryption is activated per-operation when a `compliance` context is provided.\n * Without it, object bodies are stored and read as plaintext.\n */\nexport class S3ObjectStore implements ObjectStore<S3Client> {\n private s3: S3Client;\n private bucket: string;\n private initialized: boolean;\n private encryptor?: FieldEncryptor;\n\n constructor(\n private openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>,\n options: S3ObjectStoreOptions,\n private telemetryOptions: TelemetryOptions,\n encryption: S3EncryptionOptions\n ) {\n this.s3 = options.client || new S3Client(options.clientConfig || {});\n this.bucket = options.bucket;\n this.initialized = false;\n this.encryptor = encryption.encryptor;\n }\n\n // ---------------------------------------------------------------------------\n // Encryption helpers — only active when compliance context is provided\n // ---------------------------------------------------------------------------\n\n private encryptBody(body: string, compliance?: ComplianceContext): string {\n if (!compliance || !this.encryptor) return body;\n return this.encryptor.encrypt(body, compliance.tenantId) ?? body;\n }\n\n private decryptBody(body: string, compliance?: ComplianceContext): string {\n if (!compliance || !this.encryptor) return body;\n if (!isEncrypted(body)) return body;\n try {\n return this.encryptor.decrypt(body, compliance.tenantId) ?? body;\n } catch {\n return body;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private async ensureBucketExists() {\n try {\n await this.s3.send(new HeadBucketCommand({ Bucket: this.bucket }));\n } catch {\n await this.s3.send(new CreateBucketCommand({ Bucket: this.bucket }));\n }\n\n this.initialized = true;\n }\n\n async putObject<T>(\n object: T & { key: string },\n compliance?: ComplianceContext\n ): Promise<void> {\n if (!this.initialized) {\n await this.ensureBucketExists();\n }\n\n const { key, ...rest } = object;\n const body = this.encryptBody(JSON.stringify(rest), compliance);\n const params: PutObjectCommandInput = {\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: 'application/json'\n };\n await this.s3.send(new PutObjectCommand(params));\n }\n\n async putBatchObjects<T>(\n objects: (T & { key: string })[],\n compliance?: ComplianceContext\n ): Promise<void> {\n await Promise.all(objects.map((obj) => this.putObject(obj, compliance)));\n }\n\n async streamUploadObject<T>(\n object: T & { key: string },\n compliance?: ComplianceContext\n ): Promise<void> {\n await this.putObject(object, compliance);\n }\n\n async streamUploadBatchObjects<T>(\n objects: (T & { key: string })[],\n compliance?: ComplianceContext\n ): Promise<void> {\n await this.putBatchObjects(objects, compliance);\n }\n\n async deleteObject(objectKey: string): Promise<void> {\n await this.s3.send(\n new DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n }\n\n async deleteBatchObjects(objectKeys: string[]): Promise<void> {\n const params: DeleteObjectsCommandInput = {\n Bucket: this.bucket,\n Delete: {\n Objects: objectKeys.map((Key) => ({ Key }))\n }\n };\n await this.s3.send(new DeleteObjectsCommand(params));\n }\n\n async readObject<T>(\n objectKey: string,\n compliance?: ComplianceContext\n ): Promise<T> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n\n if (!resp.Body) {\n throw new Error('S3 did not return a body');\n }\n\n const raw = await resp.Body.transformToString();\n return JSON.parse(this.decryptBody(raw, compliance)) as T;\n }\n\n async readBatchObjects<T>(\n objectKeys: string[],\n compliance?: ComplianceContext\n ): Promise<T[]> {\n return Promise.all(\n objectKeys.map((key) => this.readObject<T>(key, compliance))\n );\n }\n\n async streamDownloadObject(objectKey: string): Promise<Readable> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n const webStream = resp.Body?.transformToWebStream();\n if (!webStream) {\n throw new Error('S3 did not return a stream');\n }\n\n return Readable.fromWeb(\n webStream as Parameters<typeof Readable.fromWeb>[0]\n );\n }\n\n async streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]> {\n return Promise.all(objectKeys.map((key) => this.streamDownloadObject(key)));\n }\n\n getClient(): S3Client {\n return this.s3;\n }\n}\n"],"mappings":"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","names":[]}
|
package/lib/index.mjs
CHANGED
|
@@ -8,30 +8,12 @@ import {
|
|
|
8
8
|
PutObjectCommand,
|
|
9
9
|
S3Client
|
|
10
10
|
} from "@aws-sdk/client-s3";
|
|
11
|
-
import {
|
|
12
|
-
getCurrentTenantId
|
|
13
|
-
} from "@forklaunch/core/persistence";
|
|
14
11
|
import { Readable } from "stream";
|
|
15
12
|
var ENCRYPTED_PREFIXES = ["v1:", "v2:"];
|
|
16
13
|
function isEncrypted(value) {
|
|
17
14
|
return ENCRYPTED_PREFIXES.some((p) => value.startsWith(p));
|
|
18
15
|
}
|
|
19
16
|
var S3ObjectStore = class {
|
|
20
|
-
/**
|
|
21
|
-
* Creates a new S3ObjectStore instance.
|
|
22
|
-
* @param openTelemetryCollector - Collector for OpenTelemetry metrics.
|
|
23
|
-
* @param options - S3 configuration options.
|
|
24
|
-
* @param telemetryOptions - Telemetry configuration options.
|
|
25
|
-
* @param encryption - Encryption configuration (enabled by default when encryptor provided).
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* const store = new S3ObjectStore(
|
|
29
|
-
* otelCollector,
|
|
30
|
-
* { bucket: 'my-bucket' },
|
|
31
|
-
* telemetryOptions,
|
|
32
|
-
* { encryptor }
|
|
33
|
-
* );
|
|
34
|
-
*/
|
|
35
17
|
constructor(openTelemetryCollector, options, telemetryOptions, encryption) {
|
|
36
18
|
this.openTelemetryCollector = openTelemetryCollector;
|
|
37
19
|
this.telemetryOptions = telemetryOptions;
|
|
@@ -39,25 +21,25 @@ var S3ObjectStore = class {
|
|
|
39
21
|
this.bucket = options.bucket;
|
|
40
22
|
this.initialized = false;
|
|
41
23
|
this.encryptor = encryption.encryptor;
|
|
42
|
-
this.encryptionDisabled = encryption.disabled ?? false;
|
|
43
24
|
}
|
|
25
|
+
openTelemetryCollector;
|
|
26
|
+
telemetryOptions;
|
|
44
27
|
s3;
|
|
45
28
|
bucket;
|
|
46
29
|
initialized;
|
|
47
30
|
encryptor;
|
|
48
|
-
encryptionDisabled;
|
|
49
31
|
// ---------------------------------------------------------------------------
|
|
50
|
-
// Encryption helpers
|
|
32
|
+
// Encryption helpers — only active when compliance context is provided
|
|
51
33
|
// ---------------------------------------------------------------------------
|
|
52
|
-
encryptBody(body) {
|
|
53
|
-
if (!
|
|
54
|
-
return this.encryptor.encrypt(body,
|
|
34
|
+
encryptBody(body, compliance) {
|
|
35
|
+
if (!compliance || !this.encryptor) return body;
|
|
36
|
+
return this.encryptor.encrypt(body, compliance.tenantId) ?? body;
|
|
55
37
|
}
|
|
56
|
-
decryptBody(body) {
|
|
57
|
-
if (!
|
|
38
|
+
decryptBody(body, compliance) {
|
|
39
|
+
if (!compliance || !this.encryptor) return body;
|
|
58
40
|
if (!isEncrypted(body)) return body;
|
|
59
41
|
try {
|
|
60
|
-
return this.encryptor.decrypt(body,
|
|
42
|
+
return this.encryptor.decrypt(body, compliance.tenantId) ?? body;
|
|
61
43
|
} catch {
|
|
62
44
|
return body;
|
|
63
45
|
}
|
|
@@ -73,20 +55,12 @@ var S3ObjectStore = class {
|
|
|
73
55
|
}
|
|
74
56
|
this.initialized = true;
|
|
75
57
|
}
|
|
76
|
-
|
|
77
|
-
* Stores an object in the S3 bucket.
|
|
78
|
-
* @template T - The type of the object being stored.
|
|
79
|
-
* @param object - The object to store. Must include a `key` property.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* await store.putObject({ key: 'user-1', name: 'Alice' });
|
|
83
|
-
*/
|
|
84
|
-
async putObject(object) {
|
|
58
|
+
async putObject(object, compliance) {
|
|
85
59
|
if (!this.initialized) {
|
|
86
60
|
await this.ensureBucketExists();
|
|
87
61
|
}
|
|
88
62
|
const { key, ...rest } = object;
|
|
89
|
-
const body = this.encryptBody(JSON.stringify(rest));
|
|
63
|
+
const body = this.encryptBody(JSON.stringify(rest), compliance);
|
|
90
64
|
const params = {
|
|
91
65
|
Bucket: this.bucket,
|
|
92
66
|
Key: key,
|
|
@@ -95,57 +69,20 @@ var S3ObjectStore = class {
|
|
|
95
69
|
};
|
|
96
70
|
await this.s3.send(new PutObjectCommand(params));
|
|
97
71
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* @template T - The type of the objects being stored.
|
|
101
|
-
* @param objects - The objects to store. Each must include a `key` property.
|
|
102
|
-
*
|
|
103
|
-
* @example
|
|
104
|
-
* await store.putBatchObjects([
|
|
105
|
-
* { key: 'user-1', name: 'Alice' },
|
|
106
|
-
* { key: 'user-2', name: 'Bob' }
|
|
107
|
-
* ]);
|
|
108
|
-
*/
|
|
109
|
-
async putBatchObjects(objects) {
|
|
110
|
-
await Promise.all(objects.map((obj) => this.putObject(obj)));
|
|
72
|
+
async putBatchObjects(objects, compliance) {
|
|
73
|
+
await Promise.all(objects.map((obj) => this.putObject(obj, compliance)));
|
|
111
74
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
* For compatibility; uses putObject internally.
|
|
115
|
-
* @template T - The type of the object being stored.
|
|
116
|
-
* @param object - The object to stream-upload. Must include a `key` property.
|
|
117
|
-
*/
|
|
118
|
-
async streamUploadObject(object) {
|
|
119
|
-
await this.putObject(object);
|
|
75
|
+
async streamUploadObject(object, compliance) {
|
|
76
|
+
await this.putObject(object, compliance);
|
|
120
77
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
* For compatibility; uses putBatchObjects internally.
|
|
124
|
-
* @template T - The type of the objects being stored.
|
|
125
|
-
* @param objects - The objects to stream-upload. Each must include a `key` property.
|
|
126
|
-
*/
|
|
127
|
-
async streamUploadBatchObjects(objects) {
|
|
128
|
-
await this.putBatchObjects(objects);
|
|
78
|
+
async streamUploadBatchObjects(objects, compliance) {
|
|
79
|
+
await this.putBatchObjects(objects, compliance);
|
|
129
80
|
}
|
|
130
|
-
/**
|
|
131
|
-
* Deletes an object from the S3 bucket.
|
|
132
|
-
* @param objectKey - The key of the object to delete.
|
|
133
|
-
*
|
|
134
|
-
* @example
|
|
135
|
-
* await store.deleteObject('user-1');
|
|
136
|
-
*/
|
|
137
81
|
async deleteObject(objectKey) {
|
|
138
82
|
await this.s3.send(
|
|
139
83
|
new DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
140
84
|
);
|
|
141
85
|
}
|
|
142
|
-
/**
|
|
143
|
-
* Deletes multiple objects from the S3 bucket.
|
|
144
|
-
* @param objectKeys - The keys of the objects to delete.
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* await store.deleteBatchObjects(['user-1', 'user-2']);
|
|
148
|
-
*/
|
|
149
86
|
async deleteBatchObjects(objectKeys) {
|
|
150
87
|
const params = {
|
|
151
88
|
Bucket: this.bucket,
|
|
@@ -155,16 +92,7 @@ var S3ObjectStore = class {
|
|
|
155
92
|
};
|
|
156
93
|
await this.s3.send(new DeleteObjectsCommand(params));
|
|
157
94
|
}
|
|
158
|
-
|
|
159
|
-
* Reads an object from the S3 bucket.
|
|
160
|
-
* @template T - The expected type of the object.
|
|
161
|
-
* @param objectKey - The key of the object to read.
|
|
162
|
-
* @returns The parsed object.
|
|
163
|
-
*
|
|
164
|
-
* @example
|
|
165
|
-
* const user = await store.readObject<{ name: string }>('user-1');
|
|
166
|
-
*/
|
|
167
|
-
async readObject(objectKey) {
|
|
95
|
+
async readObject(objectKey, compliance) {
|
|
168
96
|
const resp = await this.s3.send(
|
|
169
97
|
new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
170
98
|
);
|
|
@@ -172,32 +100,13 @@ var S3ObjectStore = class {
|
|
|
172
100
|
throw new Error("S3 did not return a body");
|
|
173
101
|
}
|
|
174
102
|
const raw = await resp.Body.transformToString();
|
|
175
|
-
return JSON.parse(this.decryptBody(raw));
|
|
103
|
+
return JSON.parse(this.decryptBody(raw, compliance));
|
|
176
104
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
* @returns An array of parsed objects.
|
|
182
|
-
*
|
|
183
|
-
* @example
|
|
184
|
-
* const users = await store.readBatchObjects<{ name: string }>(['user-1', 'user-2']);
|
|
185
|
-
*/
|
|
186
|
-
async readBatchObjects(objectKeys) {
|
|
187
|
-
return Promise.all(objectKeys.map((key) => this.readObject(key)));
|
|
105
|
+
async readBatchObjects(objectKeys, compliance) {
|
|
106
|
+
return Promise.all(
|
|
107
|
+
objectKeys.map((key) => this.readObject(key, compliance))
|
|
108
|
+
);
|
|
188
109
|
}
|
|
189
|
-
/**
|
|
190
|
-
* Streams an object download from the S3 bucket.
|
|
191
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
192
|
-
* Use readObject for encrypted objects.
|
|
193
|
-
* @param objectKey - The key of the object to download.
|
|
194
|
-
* @returns A readable stream of the object's contents.
|
|
195
|
-
* @throws If the S3 response does not include a readable stream.
|
|
196
|
-
*
|
|
197
|
-
* @example
|
|
198
|
-
* const stream = await store.streamDownloadObject('user-1');
|
|
199
|
-
* stream.pipe(fs.createWriteStream('user-1.json'));
|
|
200
|
-
*/
|
|
201
110
|
async streamDownloadObject(objectKey) {
|
|
202
111
|
const resp = await this.s3.send(
|
|
203
112
|
new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })
|
|
@@ -210,26 +119,9 @@ var S3ObjectStore = class {
|
|
|
210
119
|
webStream
|
|
211
120
|
);
|
|
212
121
|
}
|
|
213
|
-
/**
|
|
214
|
-
* Streams multiple object downloads from the S3 bucket.
|
|
215
|
-
* Note: Streaming bypasses application-level encryption/decryption.
|
|
216
|
-
* @param objectKeys - The keys of the objects to download.
|
|
217
|
-
* @returns An array of readable streams.
|
|
218
|
-
*
|
|
219
|
-
* @example
|
|
220
|
-
* const streams = await store.streamDownloadBatchObjects(['user-1', 'user-2']);
|
|
221
|
-
* streams[0].pipe(fs.createWriteStream('user-1.json'));
|
|
222
|
-
*/
|
|
223
122
|
async streamDownloadBatchObjects(objectKeys) {
|
|
224
123
|
return Promise.all(objectKeys.map((key) => this.streamDownloadObject(key)));
|
|
225
124
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Gets the underlying S3 client instance.
|
|
228
|
-
* @returns The S3Client instance used by this store.
|
|
229
|
-
*
|
|
230
|
-
* @example
|
|
231
|
-
* const s3Client = store.getClient();
|
|
232
|
-
*/
|
|
233
125
|
getClient() {
|
|
234
126
|
return this.s3;
|
|
235
127
|
}
|
package/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["import {\n CreateBucketCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n DeleteObjectsCommandInput,\n GetObjectCommand,\n HeadBucketCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n S3Client\n} from '@aws-sdk/client-s3';\nimport {\n MetricsDefinition,\n OpenTelemetryCollector,\n TelemetryOptions\n} from '@forklaunch/core/http';\nimport {\n getCurrentTenantId,\n type FieldEncryptor\n} from '@forklaunch/core/persistence';\nimport { ObjectStore } from '@forklaunch/core/objectstore';\nimport { Readable } from 'stream';\n\nconst ENCRYPTED_PREFIXES = ['v1:', 'v2:'] as const;\n\nfunction isEncrypted(value: string): boolean {\n return ENCRYPTED_PREFIXES.some((p) => value.startsWith(p));\n}\n\n/**\n * Options for configuring encryption on the S3 object store.\n * Required — every consumer must explicitly configure encryption.\n */\nexport interface S3EncryptionOptions {\n /** The FieldEncryptor instance to use for encrypting object bodies. */\n encryptor: FieldEncryptor;\n /** Set to true to disable encryption. Defaults to false (encryption enabled). */\n disabled?: boolean;\n}\n\n/**\n * Options for configuring the S3ObjectStore.\n *\n * @example\n * const options: S3ObjectStoreOptions = {\n * bucket: 'my-bucket',\n * clientConfig: { region: 'us-west-2' }\n * };\n */\ninterface S3ObjectStoreOptions {\n /** The S3 bucket name. */\n bucket: string;\n /** Optional existing S3 client instance. */\n client?: S3Client;\n /** Optional configuration for creating a new S3 client. */\n clientConfig?: ConstructorParameters<typeof S3Client>[0];\n}\n\n/**\n * S3-backed implementation of the ObjectStore interface.\n * Provides methods for storing, retrieving, streaming, and deleting objects in S3.\n *\n * Encryption is enabled by default when an encryptor is provided. Object bodies\n * are encrypted before upload and decrypted after download using AES-256-GCM\n * with per-tenant key derivation.\n *\n * @example\n * const store = new S3ObjectStore(otelCollector, { bucket: 'my-bucket' }, telemetryOptions);\n * await store.putObject({ key: 'user-1', name: 'Alice' });\n * const user = await store.readObject<{ name: string }>('user-1');\n */\nexport class S3ObjectStore implements ObjectStore<S3Client> {\n private s3: S3Client;\n private bucket: string;\n private initialized: boolean;\n private encryptor?: FieldEncryptor;\n private encryptionDisabled: boolean;\n\n /**\n * Creates a new S3ObjectStore instance.\n * @param openTelemetryCollector - Collector for OpenTelemetry metrics.\n * @param options - S3 configuration options.\n * @param telemetryOptions - Telemetry configuration options.\n * @param encryption - Encryption configuration (enabled by default when encryptor provided).\n *\n * @example\n * const store = new S3ObjectStore(\n * otelCollector,\n * { bucket: 'my-bucket' },\n * telemetryOptions,\n * { encryptor }\n * );\n */\n constructor(\n private openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>,\n options: S3ObjectStoreOptions,\n private telemetryOptions: TelemetryOptions,\n encryption: S3EncryptionOptions\n ) {\n this.s3 = options.client || new S3Client(options.clientConfig || {});\n this.bucket = options.bucket;\n this.initialized = false;\n this.encryptor = encryption.encryptor;\n this.encryptionDisabled = encryption.disabled ?? false;\n }\n\n // ---------------------------------------------------------------------------\n // Encryption helpers\n // ---------------------------------------------------------------------------\n\n private encryptBody(body: string): string {\n if (!this.encryptor || this.encryptionDisabled) return body;\n return this.encryptor.encrypt(body, getCurrentTenantId()) ?? body;\n }\n\n private decryptBody(body: string): string {\n if (!this.encryptor || this.encryptionDisabled) return body;\n if (!isEncrypted(body)) return body;\n try {\n return this.encryptor.decrypt(body, getCurrentTenantId()) ?? body;\n } catch {\n return body;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private async ensureBucketExists() {\n try {\n await this.s3.send(new HeadBucketCommand({ Bucket: this.bucket }));\n } catch {\n await this.s3.send(new CreateBucketCommand({ Bucket: this.bucket }));\n }\n\n this.initialized = true;\n }\n\n /**\n * Stores an object in the S3 bucket.\n * @template T - The type of the object being stored.\n * @param object - The object to store. Must include a `key` property.\n *\n * @example\n * await store.putObject({ key: 'user-1', name: 'Alice' });\n */\n async putObject<T>(object: T & { key: string }): Promise<void> {\n if (!this.initialized) {\n await this.ensureBucketExists();\n }\n\n const { key, ...rest } = object;\n const body = this.encryptBody(JSON.stringify(rest));\n const params: PutObjectCommandInput = {\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: 'application/json'\n };\n await this.s3.send(new PutObjectCommand(params));\n }\n\n /**\n * Stores multiple objects in the S3 bucket.\n * @template T - The type of the objects being stored.\n * @param objects - The objects to store. Each must include a `key` property.\n *\n * @example\n * await store.putBatchObjects([\n * { key: 'user-1', name: 'Alice' },\n * { key: 'user-2', name: 'Bob' }\n * ]);\n */\n async putBatchObjects<T>(objects: (T & { key: string })[]): Promise<void> {\n await Promise.all(objects.map((obj) => this.putObject(obj)));\n }\n\n /**\n * Streams an object upload to the S3 bucket.\n * For compatibility; uses putObject internally.\n * @template T - The type of the object being stored.\n * @param object - The object to stream-upload. Must include a `key` property.\n */\n async streamUploadObject<T>(object: T & { key: string }): Promise<void> {\n await this.putObject(object);\n }\n\n /**\n * Streams multiple object uploads to the S3 bucket.\n * For compatibility; uses putBatchObjects internally.\n * @template T - The type of the objects being stored.\n * @param objects - The objects to stream-upload. Each must include a `key` property.\n */\n async streamUploadBatchObjects<T>(\n objects: (T & { key: string })[]\n ): Promise<void> {\n await this.putBatchObjects(objects);\n }\n\n /**\n * Deletes an object from the S3 bucket.\n * @param objectKey - The key of the object to delete.\n *\n * @example\n * await store.deleteObject('user-1');\n */\n async deleteObject(objectKey: string): Promise<void> {\n await this.s3.send(\n new DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n }\n\n /**\n * Deletes multiple objects from the S3 bucket.\n * @param objectKeys - The keys of the objects to delete.\n *\n * @example\n * await store.deleteBatchObjects(['user-1', 'user-2']);\n */\n async deleteBatchObjects(objectKeys: string[]): Promise<void> {\n const params: DeleteObjectsCommandInput = {\n Bucket: this.bucket,\n Delete: {\n Objects: objectKeys.map((Key) => ({ Key }))\n }\n };\n await this.s3.send(new DeleteObjectsCommand(params));\n }\n\n /**\n * Reads an object from the S3 bucket.\n * @template T - The expected type of the object.\n * @param objectKey - The key of the object to read.\n * @returns The parsed object.\n *\n * @example\n * const user = await store.readObject<{ name: string }>('user-1');\n */\n async readObject<T>(objectKey: string): Promise<T> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n\n if (!resp.Body) {\n throw new Error('S3 did not return a body');\n }\n\n const raw = await resp.Body.transformToString();\n return JSON.parse(this.decryptBody(raw)) as T;\n }\n\n /**\n * Reads multiple objects from the S3 bucket.\n * @template T - The expected type of the objects.\n * @param objectKeys - The keys of the objects to read.\n * @returns An array of parsed objects.\n *\n * @example\n * const users = await store.readBatchObjects<{ name: string }>(['user-1', 'user-2']);\n */\n async readBatchObjects<T>(objectKeys: string[]): Promise<T[]> {\n return Promise.all(objectKeys.map((key) => this.readObject<T>(key)));\n }\n\n /**\n * Streams an object download from the S3 bucket.\n * Note: Streaming bypasses application-level encryption/decryption.\n * Use readObject for encrypted objects.\n * @param objectKey - The key of the object to download.\n * @returns A readable stream of the object's contents.\n * @throws If the S3 response does not include a readable stream.\n *\n * @example\n * const stream = await store.streamDownloadObject('user-1');\n * stream.pipe(fs.createWriteStream('user-1.json'));\n */\n async streamDownloadObject(objectKey: string): Promise<Readable> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n const webStream = resp.Body?.transformToWebStream();\n if (!webStream) {\n throw new Error('S3 did not return a stream');\n }\n\n return Readable.fromWeb(\n webStream as Parameters<typeof Readable.fromWeb>[0]\n );\n }\n\n /**\n * Streams multiple object downloads from the S3 bucket.\n * Note: Streaming bypasses application-level encryption/decryption.\n * @param objectKeys - The keys of the objects to download.\n * @returns An array of readable streams.\n *\n * @example\n * const streams = await store.streamDownloadBatchObjects(['user-1', 'user-2']);\n * streams[0].pipe(fs.createWriteStream('user-1.json'));\n */\n async streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]> {\n return Promise.all(objectKeys.map((key) => this.streamDownloadObject(key)));\n }\n\n /**\n * Gets the underlying S3 client instance.\n * @returns The S3Client instance used by this store.\n *\n * @example\n * const s3Client = store.getClient();\n */\n getClient(): S3Client {\n return this.s3;\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["import {\n CreateBucketCommand,\n DeleteObjectCommand,\n DeleteObjectsCommand,\n DeleteObjectsCommandInput,\n GetObjectCommand,\n HeadBucketCommand,\n PutObjectCommand,\n PutObjectCommandInput,\n S3Client\n} from '@aws-sdk/client-s3';\nimport {\n MetricsDefinition,\n OpenTelemetryCollector,\n TelemetryOptions\n} from '@forklaunch/core/http';\nimport { type FieldEncryptor } from '@forklaunch/core/persistence';\nimport { ObjectStore } from '@forklaunch/core/objectstore';\nimport type { ComplianceContext } from '@forklaunch/core/cache';\nimport { Readable } from 'stream';\n\nconst ENCRYPTED_PREFIXES = ['v1:', 'v2:'] as const;\n\nfunction isEncrypted(value: string): boolean {\n return ENCRYPTED_PREFIXES.some((p) => value.startsWith(p));\n}\n\n/**\n * Options for configuring encryption on the S3 object store.\n * Required — every consumer must explicitly configure encryption.\n */\nexport interface S3EncryptionOptions {\n /** The FieldEncryptor instance to use for encrypting object bodies. */\n encryptor: FieldEncryptor;\n}\n\n/**\n * Options for configuring the S3ObjectStore.\n */\ninterface S3ObjectStoreOptions {\n /** The S3 bucket name. */\n bucket: string;\n /** Optional existing S3 client instance. */\n client?: S3Client;\n /** Optional configuration for creating a new S3 client. */\n clientConfig?: ConstructorParameters<typeof S3Client>[0];\n}\n\n/**\n * S3-backed implementation of the ObjectStore interface.\n * Provides methods for storing, retrieving, streaming, and deleting objects in S3.\n *\n * Encryption is activated per-operation when a `compliance` context is provided.\n * Without it, object bodies are stored and read as plaintext.\n */\nexport class S3ObjectStore implements ObjectStore<S3Client> {\n private s3: S3Client;\n private bucket: string;\n private initialized: boolean;\n private encryptor?: FieldEncryptor;\n\n constructor(\n private openTelemetryCollector: OpenTelemetryCollector<MetricsDefinition>,\n options: S3ObjectStoreOptions,\n private telemetryOptions: TelemetryOptions,\n encryption: S3EncryptionOptions\n ) {\n this.s3 = options.client || new S3Client(options.clientConfig || {});\n this.bucket = options.bucket;\n this.initialized = false;\n this.encryptor = encryption.encryptor;\n }\n\n // ---------------------------------------------------------------------------\n // Encryption helpers — only active when compliance context is provided\n // ---------------------------------------------------------------------------\n\n private encryptBody(body: string, compliance?: ComplianceContext): string {\n if (!compliance || !this.encryptor) return body;\n return this.encryptor.encrypt(body, compliance.tenantId) ?? body;\n }\n\n private decryptBody(body: string, compliance?: ComplianceContext): string {\n if (!compliance || !this.encryptor) return body;\n if (!isEncrypted(body)) return body;\n try {\n return this.encryptor.decrypt(body, compliance.tenantId) ?? body;\n } catch {\n return body;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private async ensureBucketExists() {\n try {\n await this.s3.send(new HeadBucketCommand({ Bucket: this.bucket }));\n } catch {\n await this.s3.send(new CreateBucketCommand({ Bucket: this.bucket }));\n }\n\n this.initialized = true;\n }\n\n async putObject<T>(\n object: T & { key: string },\n compliance?: ComplianceContext\n ): Promise<void> {\n if (!this.initialized) {\n await this.ensureBucketExists();\n }\n\n const { key, ...rest } = object;\n const body = this.encryptBody(JSON.stringify(rest), compliance);\n const params: PutObjectCommandInput = {\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: 'application/json'\n };\n await this.s3.send(new PutObjectCommand(params));\n }\n\n async putBatchObjects<T>(\n objects: (T & { key: string })[],\n compliance?: ComplianceContext\n ): Promise<void> {\n await Promise.all(objects.map((obj) => this.putObject(obj, compliance)));\n }\n\n async streamUploadObject<T>(\n object: T & { key: string },\n compliance?: ComplianceContext\n ): Promise<void> {\n await this.putObject(object, compliance);\n }\n\n async streamUploadBatchObjects<T>(\n objects: (T & { key: string })[],\n compliance?: ComplianceContext\n ): Promise<void> {\n await this.putBatchObjects(objects, compliance);\n }\n\n async deleteObject(objectKey: string): Promise<void> {\n await this.s3.send(\n new DeleteObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n }\n\n async deleteBatchObjects(objectKeys: string[]): Promise<void> {\n const params: DeleteObjectsCommandInput = {\n Bucket: this.bucket,\n Delete: {\n Objects: objectKeys.map((Key) => ({ Key }))\n }\n };\n await this.s3.send(new DeleteObjectsCommand(params));\n }\n\n async readObject<T>(\n objectKey: string,\n compliance?: ComplianceContext\n ): Promise<T> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n\n if (!resp.Body) {\n throw new Error('S3 did not return a body');\n }\n\n const raw = await resp.Body.transformToString();\n return JSON.parse(this.decryptBody(raw, compliance)) as T;\n }\n\n async readBatchObjects<T>(\n objectKeys: string[],\n compliance?: ComplianceContext\n ): Promise<T[]> {\n return Promise.all(\n objectKeys.map((key) => this.readObject<T>(key, compliance))\n );\n }\n\n async streamDownloadObject(objectKey: string): Promise<Readable> {\n const resp = await this.s3.send(\n new GetObjectCommand({ Bucket: this.bucket, Key: objectKey })\n );\n const webStream = resp.Body?.transformToWebStream();\n if (!webStream) {\n throw new Error('S3 did not return a stream');\n }\n\n return Readable.fromWeb(\n webStream as Parameters<typeof Readable.fromWeb>[0]\n );\n }\n\n async streamDownloadBatchObjects(objectKeys: string[]): Promise<Readable[]> {\n return Promise.all(objectKeys.map((key) => this.streamDownloadObject(key)));\n }\n\n getClient(): S3Client {\n return this.s3;\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forklaunch/infrastructure-s3",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "S3 infrastructure for ForkLaunch components.",
|
|
5
5
|
"homepage": "https://github.com/forklaunch/forklaunch-js#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -28,18 +28,18 @@
|
|
|
28
28
|
"lib/**"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@aws-sdk/client-s3": "^3.
|
|
32
|
-
"@forklaunch/common": "1.2.
|
|
33
|
-
"@forklaunch/core": "1.
|
|
31
|
+
"@aws-sdk/client-s3": "^3.1025.0",
|
|
32
|
+
"@forklaunch/common": "1.2.15",
|
|
33
|
+
"@forklaunch/core": "1.4.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@eslint/js": "^10.0.1",
|
|
37
37
|
"@types/jest": "^30.0.0",
|
|
38
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
38
|
+
"@typescript/native-preview": "7.0.0-dev.20260407.1",
|
|
39
39
|
"globals": "^17.4.0",
|
|
40
40
|
"jest": "^30.3.0",
|
|
41
41
|
"prettier": "^3.8.1",
|
|
42
|
-
"ts-jest": "^29.4.
|
|
42
|
+
"ts-jest": "^29.4.9",
|
|
43
43
|
"ts-node": "^10.9.2",
|
|
44
44
|
"tsup": "^8.5.1",
|
|
45
45
|
"typedoc": "^0.28.18",
|