@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.
Files changed (42) hide show
  1. package/api/server/gateway.js +2 -2
  2. package/index.d.ts +1 -0
  3. package/index.js +1 -0
  4. package/internal.d.ts +1 -0
  5. package/internal.js +1 -0
  6. package/notification/api/notification.api.d.ts +22 -10
  7. package/notification/api/notification.api.js +9 -3
  8. package/notification/client/notification-client.d.ts +2 -0
  9. package/notification/client/notification-client.js +7 -0
  10. package/notification/server/api/notification.api-controller.d.ts +3 -0
  11. package/notification/server/api/notification.api-controller.js +5 -0
  12. package/notification/server/services/notification-type.service.d.ts +1 -0
  13. package/notification/server/services/notification-type.service.js +5 -0
  14. package/notification/server/services/notification.service.d.ts +2 -0
  15. package/notification/server/services/notification.service.js +9 -2
  16. package/notification/tests/notification-api.test.js +8 -0
  17. package/notification/tests/notification-sse.service.test.js +9 -0
  18. package/notification/tests/unit/notification-client.test.d.ts +1 -0
  19. package/notification/tests/unit/notification-client.test.js +112 -0
  20. package/object-storage/object-storage.d.ts +10 -0
  21. package/object-storage/s3/s3.object-storage-provider.d.ts +11 -4
  22. package/object-storage/s3/s3.object-storage-provider.js +29 -26
  23. package/object-storage/s3/s3.object-storage.d.ts +8 -4
  24. package/object-storage/s3/s3.object-storage.js +148 -60
  25. package/object-storage/s3/s3.object.d.ts +6 -0
  26. package/object-storage/s3/s3.object.js +1 -1
  27. package/object-storage/s3/tests/s3.object-storage.integration.test.d.ts +1 -0
  28. package/object-storage/s3/tests/s3.object-storage.integration.test.js +334 -0
  29. package/package.json +3 -2
  30. package/rpc/adapters/readable-stream.adapter.js +27 -22
  31. package/rpc/endpoints/message-port.rpc-endpoint.d.ts +4 -0
  32. package/rpc/endpoints/message-port.rpc-endpoint.js +4 -0
  33. package/rpc/model.d.ts +11 -1
  34. package/rpc/rpc.d.ts +17 -1
  35. package/rpc/rpc.endpoint.js +4 -3
  36. package/rpc/rpc.error.d.ts +5 -1
  37. package/rpc/rpc.error.js +16 -3
  38. package/rpc/rpc.js +89 -15
  39. package/rpc/tests/rpc.integration.test.d.ts +1 -0
  40. package/rpc/tests/rpc.integration.test.js +619 -0
  41. package/unit-test/integration-setup.d.ts +1 -0
  42. 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 { CopyDestinationOptions, CopySourceOptions } from 'minio';
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
- const exists = await this.client.bucketExists(this.bucket);
46
- if (exists) {
45
+ try {
46
+ await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));
47
47
  return;
48
48
  }
49
- await this.client.makeBucket(this.bucket, region ?? '', { ObjectLocking: options?.objectLocking ?? false });
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 currentLifecycle = null;
69
+ let currentLifecycleRules;
56
70
  try {
57
- currentLifecycle = await this.client.getBucketLifecycle(this.bucket);
71
+ const result = await this.client.send(new GetBucketLifecycleConfigurationCommand({ Bucket: this.bucket }));
72
+ currentLifecycleRules = result.Rules;
58
73
  }
59
74
  catch (error) {
60
- // ignore error if lifecycle configuration is not set
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.removeBucketLifecycle(this.bucket);
89
+ await this.client.send(new DeleteBucketLifecycleCommand({
90
+ Bucket: this.bucket,
91
+ }));
77
92
  }
78
93
  else {
79
- await this.client.setBucketLifecycle(this.bucket, { Rule: nonTstdlRules });
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.setBucketLifecycle(this.bucket, {
84
- Rule: [
85
- ...nonTstdlRules,
86
- {
87
- ID: 'TstdlExpireObjects',
88
- Status: 'Enabled',
89
- Expiration: {
90
- Days: targetExpiration,
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.statObject(this.bucket, bucketKey);
121
+ await this.client.send(new HeadObjectCommand({ Bucket: this.bucket, Key: bucketKey }));
101
122
  return true;
102
123
  }
103
124
  catch (error) {
104
- if (isObject(error) && error.code == 'NotFound') {
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
- return await this.client.statObject(this.bucket, bucketKey);
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
- if (isUint8Array(content)) {
117
- await this.client.putObject(this.bucket, bucketKey, Buffer.from(content), options?.contentLength, options?.metadata);
118
- }
119
- else {
120
- const readable = Readable.fromWeb(content);
121
- const errorPromise = new Promise((_, reject) => readable.on('error', reject));
122
- await Promise.race([
123
- this.client.putObject(this.bucket, bucketKey, readable, options?.contentLength, options?.metadata),
124
- errorPromise,
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
- await this.client.copyObject(new CopySourceOptions({ Bucket: this.bucket, Object: sourceObject.storage.getBucketKey(sourceObject.key) }), new CopyDestinationOptions({
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
- Object: destinationObject.storage.getBucketKey(destinationObject.key),
172
+ Key: destinationObject.storage.getBucketKey(destinationObject.key),
143
173
  MetadataDirective: 'REPLACE',
144
- UserMetadata: { ...sourceMetadata, ...options?.metadata },
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.getObject(this.bucket, bucketKey);
157
- return await readBinaryStream(result);
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 readable = await this.client.getObject(this.bucket, bucketKey);
163
- return Readable.toWeb(readable);
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
- const stream = this.client.listObjectsV2(this.bucket, this.prefix, true);
171
- return mapAsync(stream, (item) => new S3Object(this.module, this.getKey(item.name), `s3://${this.bucket}/${item.name}`, item.size, this));
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 { date, expiration } = getDateAndExpiration(expirationTimestamp);
184
- return await this.client.presignedGetObject(this.bucket, bucketKey, expiration, responseHeaders ?? {}, date);
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 { date, expiration } = getDateAndExpiration(expirationTimestamp);
189
- const query = mapObjectKeys(options?.metadata ?? {}, (key) => `X-Amz-Meta-${key}`);
190
- return await this.client.presignedUrl('PUT', this.bucket, bucketKey, expiration, query, date);
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.removeObject(this.bucket, bucketKey);
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.removeObjects(this.bucket, bucketKeys);
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", [Function, String, String, String])
309
+ __metadata("design:paramtypes", [S3Client, String, String, String])
222
310
  ], S3ObjectStorage);
223
311
  export { S3ObjectStorage };
224
- function getDateAndExpiration(expirationTimestamp) {
312
+ function getExpiration(expirationTimestamp) {
225
313
  const date = now();
226
314
  const diffSeconds = Math.floor((expirationTimestamp - date.getTime()) / 1000);
227
- return { date, expiration: diffSeconds };
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;
@@ -24,7 +24,7 @@ export class S3Object extends ObjectStorageObject {
24
24
  }
25
25
  async getMetadata() {
26
26
  const stat = await this.stat();
27
- return stat.metaData;
27
+ return stat.metadata;
28
28
  }
29
29
  async getContent() {
30
30
  return await this.storage.getContent(this.key);