@tstdl/base 0.93.117 → 0.93.119
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/api/server/gateway.js +2 -2
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/internal.d.ts +1 -0
- package/internal.js +1 -0
- package/notification/api/notification.api.d.ts +22 -10
- package/notification/api/notification.api.js +9 -3
- package/notification/client/notification-client.d.ts +2 -0
- package/notification/client/notification-client.js +7 -0
- package/notification/server/api/notification.api-controller.d.ts +3 -0
- package/notification/server/api/notification.api-controller.js +5 -0
- package/notification/server/services/notification-type.service.d.ts +1 -0
- package/notification/server/services/notification-type.service.js +5 -0
- package/notification/server/services/notification.service.d.ts +2 -0
- package/notification/server/services/notification.service.js +9 -2
- package/notification/tests/notification-api.test.js +8 -0
- package/notification/tests/notification-sse.service.test.js +9 -0
- package/notification/tests/unit/notification-client.test.d.ts +1 -0
- package/notification/tests/unit/notification-client.test.js +112 -0
- package/object-storage/object-storage.d.ts +10 -0
- package/object-storage/s3/s3.object-storage-provider.d.ts +11 -4
- package/object-storage/s3/s3.object-storage-provider.js +29 -26
- package/object-storage/s3/s3.object-storage.d.ts +8 -4
- package/object-storage/s3/s3.object-storage.js +148 -60
- package/object-storage/s3/s3.object.d.ts +6 -0
- package/object-storage/s3/s3.object.js +1 -1
- package/object-storage/s3/tests/s3.object-storage.integration.test.d.ts +1 -0
- package/object-storage/s3/tests/s3.object-storage.integration.test.js +334 -0
- package/package.json +3 -2
- package/rpc/adapters/readable-stream.adapter.js +27 -22
- package/rpc/endpoints/message-port.rpc-endpoint.d.ts +4 -0
- package/rpc/endpoints/message-port.rpc-endpoint.js +4 -0
- package/rpc/model.d.ts +11 -1
- package/rpc/rpc.d.ts +17 -1
- package/rpc/rpc.endpoint.js +4 -3
- package/rpc/rpc.error.d.ts +5 -1
- package/rpc/rpc.error.js +16 -3
- package/rpc/rpc.js +89 -15
- package/rpc/tests/rpc.integration.test.d.ts +1 -0
- package/rpc/tests/rpc.integration.test.js +619 -0
- package/unit-test/integration-setup.d.ts +1 -0
- package/unit-test/integration-setup.js +12 -0
|
@@ -9,20 +9,20 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
var S3ObjectStorage_1;
|
|
11
11
|
import { Readable } from 'node:stream';
|
|
12
|
-
import {
|
|
12
|
+
import { CopyObjectCommand, CreateBucketCommand, DeleteBucketLifecycleCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLifecycleConfigurationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, PutBucketLifecycleConfigurationCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
13
|
+
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
13
14
|
import { match, P } from 'ts-pattern';
|
|
15
|
+
import { BadRequestError } from '../../errors/bad-request.error.js';
|
|
14
16
|
import { Singleton } from '../../injector/decorators.js';
|
|
15
17
|
import { registerAfterResolve } from '../../injector/resolution.js';
|
|
16
18
|
import { ObjectStorage } from '../../object-storage/index.js';
|
|
17
|
-
import { toArray } from '../../utils/array/array.js';
|
|
18
|
-
import { mapAsync } from '../../utils/async-iterable-helpers/map.js';
|
|
19
19
|
import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
|
|
20
20
|
import { now } from '../../utils/date-time.js';
|
|
21
21
|
import { mapObjectKeys } from '../../utils/object/object.js';
|
|
22
22
|
import { readableStreamFromPromise } from '../../utils/stream/index.js';
|
|
23
23
|
import { readBinaryStream } from '../../utils/stream/stream-reader.js';
|
|
24
24
|
import { _throw } from '../../utils/throw.js';
|
|
25
|
-
import { assertDefinedPass, isDefined, isObject, isString, isUint8Array, isUndefined } from '../../utils/type-guards.js';
|
|
25
|
+
import { assertDefinedPass, isBlob, isDate, isDefined, isObject, isReadableStream, isString, isUint8Array, isUndefined } from '../../utils/type-guards.js';
|
|
26
26
|
import { secondsPerDay } from '../../utils/units.js';
|
|
27
27
|
import { S3ObjectStorageProvider } from './s3.object-storage-provider.js';
|
|
28
28
|
import { S3Object } from './s3.object.js';
|
|
@@ -42,27 +42,40 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
async ensureBucketExists(region, options) {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
try {
|
|
46
|
+
await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
catch (error) {
|
|
50
|
+
// ignore error if bucket already exists (e.g. 403 Forbidden when we have no head permission but it might still exist)
|
|
51
|
+
if (this.isNotFoundError(error)) {
|
|
52
|
+
await this.client.send(new CreateBucketCommand({
|
|
53
|
+
Bucket: this.bucket,
|
|
54
|
+
CreateBucketConfiguration: isDefined(region) ? { LocationConstraint: region } : undefined,
|
|
55
|
+
ObjectLockEnabledForBucket: options?.objectLocking,
|
|
56
|
+
}));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (this.isBadRequestError(error)) {
|
|
60
|
+
throw new BadRequestError(`S3 request failed with 400 Bad Request. This often indicates an invalid bucket name ("${this.bucket}") or missing "forcePathStyle: true" for local S3 providers.`);
|
|
61
|
+
}
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
50
64
|
}
|
|
51
65
|
async configureBucket(configuration) {
|
|
52
66
|
if (isUndefined(configuration.lifecycle)) {
|
|
53
67
|
return;
|
|
54
68
|
}
|
|
55
|
-
let
|
|
69
|
+
let currentLifecycleRules;
|
|
56
70
|
try {
|
|
57
|
-
|
|
71
|
+
const result = await this.client.send(new GetBucketLifecycleConfigurationCommand({ Bucket: this.bucket }));
|
|
72
|
+
currentLifecycleRules = result.Rules;
|
|
58
73
|
}
|
|
59
74
|
catch (error) {
|
|
60
|
-
|
|
61
|
-
if (!isObject(error) || (error.code != 'NoSuchLifecycleConfiguration')) {
|
|
75
|
+
if (!this.isError(error, 'NoSuchLifecycleConfiguration')) {
|
|
62
76
|
throw error;
|
|
63
77
|
}
|
|
64
78
|
}
|
|
65
|
-
const currentLifecycleRules = isDefined(currentLifecycle?.Rule) ? toArray(currentLifecycle.Rule) : undefined; // https://github.com/minio/minio-js/issues/1407
|
|
66
79
|
const tstdlRule = currentLifecycleRules?.find((rule) => rule.ID == 'TstdlExpireObjects');
|
|
67
80
|
const tstdlRuleExpiration = tstdlRule?.Expiration?.Days;
|
|
68
81
|
const targetExpirationDays = configuration.lifecycle?.expiration?.after;
|
|
@@ -73,35 +86,43 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
73
86
|
const nonTstdlRules = currentLifecycleRules?.filter((rule) => rule.ID != 'TstdlExpireObjects') ?? [];
|
|
74
87
|
if (isUndefined(targetExpiration)) {
|
|
75
88
|
if (nonTstdlRules.length == 0) {
|
|
76
|
-
await this.client.
|
|
89
|
+
await this.client.send(new DeleteBucketLifecycleCommand({
|
|
90
|
+
Bucket: this.bucket,
|
|
91
|
+
}));
|
|
77
92
|
}
|
|
78
93
|
else {
|
|
79
|
-
await this.client.
|
|
94
|
+
await this.client.send(new PutBucketLifecycleConfigurationCommand({
|
|
95
|
+
Bucket: this.bucket,
|
|
96
|
+
LifecycleConfiguration: { Rules: nonTstdlRules },
|
|
97
|
+
}));
|
|
80
98
|
}
|
|
81
99
|
}
|
|
82
100
|
else {
|
|
83
|
-
await this.client.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
await this.client.send(new PutBucketLifecycleConfigurationCommand({
|
|
102
|
+
Bucket: this.bucket,
|
|
103
|
+
LifecycleConfiguration: {
|
|
104
|
+
Rules: [
|
|
105
|
+
...nonTstdlRules,
|
|
106
|
+
{
|
|
107
|
+
ID: 'TstdlExpireObjects',
|
|
108
|
+
Status: 'Enabled',
|
|
109
|
+
Expiration: {
|
|
110
|
+
Days: targetExpiration,
|
|
111
|
+
},
|
|
91
112
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
});
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
}));
|
|
95
116
|
}
|
|
96
117
|
}
|
|
97
118
|
async exists(key) {
|
|
98
119
|
const bucketKey = this.getBucketKey(key);
|
|
99
120
|
try {
|
|
100
|
-
await this.client.
|
|
121
|
+
await this.client.send(new HeadObjectCommand({ Bucket: this.bucket, Key: bucketKey }));
|
|
101
122
|
return true;
|
|
102
123
|
}
|
|
103
124
|
catch (error) {
|
|
104
|
-
if (
|
|
125
|
+
if (this.isNotFoundError(error)) {
|
|
105
126
|
return false;
|
|
106
127
|
}
|
|
107
128
|
throw error;
|
|
@@ -109,21 +130,28 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
109
130
|
}
|
|
110
131
|
async statObject(key) {
|
|
111
132
|
const bucketKey = this.getBucketKey(key);
|
|
112
|
-
|
|
133
|
+
const result = await this.client.send(new HeadObjectCommand({ Bucket: this.bucket, Key: bucketKey }));
|
|
134
|
+
return {
|
|
135
|
+
size: result.ContentLength ?? 0,
|
|
136
|
+
etag: result.ETag ?? '',
|
|
137
|
+
lastModified: result.LastModified?.getTime(),
|
|
138
|
+
metadata: result.Metadata ?? {},
|
|
139
|
+
};
|
|
113
140
|
}
|
|
114
141
|
async uploadObject(key, content, options) {
|
|
115
142
|
const bucketKey = this.getBucketKey(key);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
const body = isUint8Array(content) ? content : Readable.fromWeb(content);
|
|
144
|
+
const metadata = isDefined(options?.metadata) ? mapObjectKeys(options.metadata, (key) => key.toLowerCase()) : undefined;
|
|
145
|
+
await this.client.send(new PutObjectCommand({
|
|
146
|
+
Bucket: this.bucket,
|
|
147
|
+
Key: bucketKey,
|
|
148
|
+
Body: body,
|
|
149
|
+
ContentLength: options?.contentLength,
|
|
150
|
+
ContentType: options?.contentType,
|
|
151
|
+
ContentDisposition: options?.contentDisposition,
|
|
152
|
+
CacheControl: options?.cacheControl,
|
|
153
|
+
Metadata: metadata,
|
|
154
|
+
}));
|
|
127
155
|
return await this.getObject(key);
|
|
128
156
|
}
|
|
129
157
|
async copyObject(source, destination, options) {
|
|
@@ -137,11 +165,16 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
137
165
|
.with([P.instanceOf(S3ObjectStorage_1), P.string], async ([storage, key]) => await storage.getObject(key))
|
|
138
166
|
.otherwise(() => _throw(new Error('Destination must be a S3Object, string key, or [S3ObjectStorage, string] tuple.')));
|
|
139
167
|
const sourceMetadata = await sourceObject.getMetadata();
|
|
140
|
-
|
|
168
|
+
const metadata = mapObjectKeys({ ...sourceMetadata, ...options?.metadata }, (key) => key.toLowerCase());
|
|
169
|
+
await this.client.send(new CopyObjectCommand({
|
|
170
|
+
CopySource: `${this.bucket}/${sourceObject.storage.getBucketKey(sourceObject.key)}`,
|
|
141
171
|
Bucket: destinationObject.storage.bucket,
|
|
142
|
-
|
|
172
|
+
Key: destinationObject.storage.getBucketKey(destinationObject.key),
|
|
143
173
|
MetadataDirective: 'REPLACE',
|
|
144
|
-
|
|
174
|
+
ContentType: options?.contentType,
|
|
175
|
+
ContentDisposition: options?.contentDisposition,
|
|
176
|
+
CacheControl: options?.cacheControl,
|
|
177
|
+
Metadata: metadata,
|
|
145
178
|
}));
|
|
146
179
|
return destinationObject;
|
|
147
180
|
}
|
|
@@ -153,22 +186,45 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
153
186
|
}
|
|
154
187
|
async getContent(key) {
|
|
155
188
|
const bucketKey = this.getBucketKey(key);
|
|
156
|
-
const result = await this.client.
|
|
157
|
-
|
|
189
|
+
const result = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: bucketKey }));
|
|
190
|
+
if (isUint8Array(result.Body)) {
|
|
191
|
+
return result.Body;
|
|
192
|
+
}
|
|
193
|
+
return await readBinaryStream(result.Body);
|
|
158
194
|
}
|
|
159
195
|
getContentStream(key) {
|
|
160
196
|
const bucketKey = this.getBucketKey(key);
|
|
161
197
|
return readableStreamFromPromise(async () => {
|
|
162
|
-
const
|
|
163
|
-
|
|
198
|
+
const result = await this.client.send(new GetObjectCommand({ Bucket: this.bucket, Key: bucketKey }));
|
|
199
|
+
if (isReadableStream(result.Body)) {
|
|
200
|
+
return result.Body;
|
|
201
|
+
}
|
|
202
|
+
if (isBlob(result.Body)) {
|
|
203
|
+
return result.Body.stream();
|
|
204
|
+
}
|
|
205
|
+
return Readable.toWeb(result.Body);
|
|
164
206
|
});
|
|
165
207
|
}
|
|
166
208
|
async getObjects() {
|
|
167
209
|
return await toArrayAsync(this.getObjectsCursor());
|
|
168
210
|
}
|
|
169
|
-
getObjectsCursor() {
|
|
170
|
-
|
|
171
|
-
|
|
211
|
+
async *getObjectsCursor() {
|
|
212
|
+
let continuationToken;
|
|
213
|
+
do {
|
|
214
|
+
const result = await this.client.send(new ListObjectsV2Command({
|
|
215
|
+
Bucket: this.bucket,
|
|
216
|
+
Prefix: this.prefix,
|
|
217
|
+
ContinuationToken: continuationToken,
|
|
218
|
+
}));
|
|
219
|
+
if (isDefined(result.Contents)) {
|
|
220
|
+
for (const item of result.Contents) {
|
|
221
|
+
if (isDefined(item.Key)) {
|
|
222
|
+
yield new S3Object(this.module, this.getKey(item.Key), `s3://${this.bucket}/${item.Key}`, item.Size, this);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
continuationToken = result.NextContinuationToken;
|
|
227
|
+
} while (isDefined(continuationToken));
|
|
172
228
|
}
|
|
173
229
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
174
230
|
async getObject(key) {
|
|
@@ -180,22 +236,42 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
180
236
|
}
|
|
181
237
|
async getDownloadUrl(key, expirationTimestamp, responseHeaders) {
|
|
182
238
|
const bucketKey = this.getBucketKey(key);
|
|
183
|
-
const
|
|
184
|
-
|
|
239
|
+
const expiration = getExpiration(expirationTimestamp);
|
|
240
|
+
const expiresHeader = responseHeaders?.['Expires'];
|
|
241
|
+
const mappedExpiresHeader = isDefined(expiresHeader) ? (isDate(expiresHeader) ? expiresHeader : new Date(expiresHeader)) : undefined;
|
|
242
|
+
return await getSignedUrl(this.client, new GetObjectCommand({
|
|
243
|
+
Bucket: this.bucket,
|
|
244
|
+
Key: bucketKey,
|
|
245
|
+
ResponseContentType: responseHeaders?.['Content-Type'],
|
|
246
|
+
ResponseContentDisposition: responseHeaders?.['Content-Disposition'],
|
|
247
|
+
ResponseCacheControl: responseHeaders?.['Cache-Control'],
|
|
248
|
+
ResponseContentLanguage: responseHeaders?.['Content-Language'],
|
|
249
|
+
ResponseContentEncoding: responseHeaders?.['Content-Encoding'],
|
|
250
|
+
ResponseExpires: mappedExpiresHeader,
|
|
251
|
+
}), { expiresIn: expiration });
|
|
185
252
|
}
|
|
186
253
|
async getUploadUrl(key, expirationTimestamp, options) {
|
|
187
254
|
const bucketKey = this.getBucketKey(key);
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
255
|
+
const expiration = getExpiration(expirationTimestamp);
|
|
256
|
+
return await getSignedUrl(this.client, new PutObjectCommand({
|
|
257
|
+
Bucket: this.bucket,
|
|
258
|
+
Key: bucketKey,
|
|
259
|
+
Metadata: options?.metadata,
|
|
260
|
+
ContentType: options?.contentType,
|
|
261
|
+
ContentDisposition: options?.contentDisposition,
|
|
262
|
+
CacheControl: options?.cacheControl,
|
|
263
|
+
}), { expiresIn: expiration });
|
|
191
264
|
}
|
|
192
265
|
async deleteObject(key) {
|
|
193
266
|
const bucketKey = this.getBucketKey(key);
|
|
194
|
-
await this.client.
|
|
267
|
+
await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: bucketKey }));
|
|
195
268
|
}
|
|
196
269
|
async deleteObjects(keys) {
|
|
197
|
-
const bucketKeys = keys.map((key) => this.getBucketKey(key));
|
|
198
|
-
await this.client.
|
|
270
|
+
const bucketKeys = keys.map((key) => ({ Key: this.getBucketKey(key) }));
|
|
271
|
+
await this.client.send(new DeleteObjectsCommand({
|
|
272
|
+
Bucket: this.bucket,
|
|
273
|
+
Delete: { Objects: bucketKeys },
|
|
274
|
+
}));
|
|
199
275
|
}
|
|
200
276
|
getResourceUriSync(key) {
|
|
201
277
|
const bucketKey = this.getBucketKey(key);
|
|
@@ -207,6 +283,18 @@ let S3ObjectStorage = S3ObjectStorage_1 = class S3ObjectStorage extends ObjectSt
|
|
|
207
283
|
getKey(bucketKey) {
|
|
208
284
|
return bucketKey.slice(this.prefix.length);
|
|
209
285
|
}
|
|
286
|
+
isNotFoundError(error) {
|
|
287
|
+
return this.isError(error, 'NotFound', 'NoSuchKey', 'NoSuchBucket') || ((isObject(error) && isObject(error.$metadata) && error.$metadata.httpStatusCode == 404));
|
|
288
|
+
}
|
|
289
|
+
isForbiddenError(error) {
|
|
290
|
+
return this.isError(error, 'Forbidden', 'AccessDenied', 'InvalidAccessKeyId', 'SignatureDoesNotMatch') || ((isObject(error) && isObject(error.$metadata) && error.$metadata.httpStatusCode == 403));
|
|
291
|
+
}
|
|
292
|
+
isBadRequestError(error) {
|
|
293
|
+
return (isObject(error) && isObject(error.$metadata) && error.$metadata.httpStatusCode == 400);
|
|
294
|
+
}
|
|
295
|
+
isError(error, ...names) {
|
|
296
|
+
return isObject(error) && names.includes(error.name);
|
|
297
|
+
}
|
|
210
298
|
};
|
|
211
299
|
S3ObjectStorage = S3ObjectStorage_1 = __decorate([
|
|
212
300
|
Singleton({
|
|
@@ -218,11 +306,11 @@ S3ObjectStorage = S3ObjectStorage_1 = __decorate([
|
|
|
218
306
|
},
|
|
219
307
|
argumentIdentityProvider: JSON.stringify,
|
|
220
308
|
}),
|
|
221
|
-
__metadata("design:paramtypes", [
|
|
309
|
+
__metadata("design:paramtypes", [S3Client, String, String, String])
|
|
222
310
|
], S3ObjectStorage);
|
|
223
311
|
export { S3ObjectStorage };
|
|
224
|
-
function
|
|
312
|
+
function getExpiration(expirationTimestamp) {
|
|
225
313
|
const date = now();
|
|
226
314
|
const diffSeconds = Math.floor((expirationTimestamp - date.getTime()) / 1000);
|
|
227
|
-
return
|
|
315
|
+
return diffSeconds;
|
|
228
316
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { ObjectMetadata } from '../../object-storage/object.js';
|
|
2
2
|
import { ObjectStorageObject } from '../../object-storage/object.js';
|
|
3
3
|
import type { S3ObjectStorage } from './s3.object-storage.js';
|
|
4
|
+
export type S3BucketItemStat = {
|
|
5
|
+
size: number;
|
|
6
|
+
etag?: string;
|
|
7
|
+
lastModified?: number;
|
|
8
|
+
metadata: ObjectMetadata;
|
|
9
|
+
};
|
|
4
10
|
export declare class S3Object extends ObjectStorageObject {
|
|
5
11
|
private readonly resourceUri;
|
|
6
12
|
private contentLength;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|