@tstdl/base 0.93.117 → 0.93.118

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