@geekmidas/storage 10.0.0-alpha.9 → 10.0.0-alpha.90

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 (48) hide show
  1. package/README.md +49 -13
  2. package/dist/aws.cjs +102 -63
  3. package/dist/aws.cjs.map +1 -1
  4. package/dist/aws.d.cts +9 -10
  5. package/dist/aws.d.cts.map +1 -1
  6. package/dist/aws.d.mts +9 -10
  7. package/dist/aws.d.mts.map +1 -1
  8. package/dist/aws.mjs +87 -49
  9. package/dist/aws.mjs.map +1 -1
  10. package/dist/{errors-D5UnAd-X.mjs → errors-BB8jdHif.cjs} +83 -5
  11. package/dist/errors-BB8jdHif.cjs.map +1 -0
  12. package/dist/{errors-Bce0CJ11.cjs → errors-COXs1iNg.mjs} +42 -35
  13. package/dist/errors-COXs1iNg.mjs.map +1 -0
  14. package/dist/index.cjs +19 -3
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +32 -11
  17. package/dist/index.d.cts.map +1 -1
  18. package/dist/index.d.mts +32 -11
  19. package/dist/index.d.mts.map +1 -1
  20. package/dist/index.mjs +17 -4
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{registry-DBsAUTT3.d.cts → registry-ClEvHGpM.d.cts} +3 -6
  23. package/dist/registry-ClEvHGpM.d.cts.map +1 -0
  24. package/dist/{registry-DEqSHjx-.d.mts → registry-ClEvHGpM.d.mts} +3 -6
  25. package/dist/registry-ClEvHGpM.d.mts.map +1 -0
  26. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  27. package/dist/s3Url.cjs +108 -4
  28. package/dist/s3Url.cjs.map +1 -0
  29. package/dist/s3Url.d.cts +52 -2
  30. package/dist/s3Url.d.cts.map +1 -0
  31. package/dist/s3Url.d.mts +52 -2
  32. package/dist/s3Url.d.mts.map +1 -0
  33. package/dist/s3Url.mjs +89 -3
  34. package/dist/s3Url.mjs.map +1 -0
  35. package/package.json +10 -5
  36. package/dist/chunk-B9dir_RE.mjs +0 -11
  37. package/dist/errors-Bce0CJ11.cjs.map +0 -1
  38. package/dist/errors-D5UnAd-X.mjs.map +0 -1
  39. package/dist/registry-DBsAUTT3.d.cts.map +0 -1
  40. package/dist/registry-DEqSHjx-.d.mts.map +0 -1
  41. package/dist/s3Url-BfztIltX.d.cts +0 -39
  42. package/dist/s3Url-BfztIltX.d.cts.map +0 -1
  43. package/dist/s3Url-Bo32UTTA.cjs +0 -103
  44. package/dist/s3Url-Bo32UTTA.cjs.map +0 -1
  45. package/dist/s3Url-C8FGPNrG.mjs +0 -52
  46. package/dist/s3Url-C8FGPNrG.mjs.map +0 -1
  47. package/dist/s3Url-COpMIgF9.d.mts +0 -42
  48. package/dist/s3Url-COpMIgF9.d.mts.map +0 -1
package/README.md CHANGED
@@ -85,6 +85,49 @@ const storage = AmazonStorageClient.create({
85
85
  });
86
86
  ```
87
87
 
88
+ ## Storage URLs and credentials
89
+
90
+ A declared bucket reaches an app as one URL, `<ID>_URL`, and
91
+ `createStorageClient(url)` picks the driver by its scheme. An `s3://` URL
92
+ names the bucket as its host and the rest as query parameters:
93
+
94
+ ```
95
+ s3://uploads?region=eu-west-1
96
+ s3://uploads?region=auto&endpoint=https://<account>.r2.cloudflarestorage.com
97
+ ```
98
+
99
+ Credentials are optional, and come from one of two places:
100
+
101
+ - **The URL's userinfo** — `s3://KEY:SECRET@uploads?region=eu-west-1`. A key
102
+ here signs for this bucket only and wins over anything in the environment.
103
+ Percent-encode both halves: AWS secrets often contain `/` (`%2F`) and `+`
104
+ (`%2B`). `s3Url.build` does this for you, and `s3Url.parse` decodes them.
105
+ - **The SDK's default chain** — for a URL with no userinfo: the shared
106
+ `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, a profile, or an execution role.
107
+
108
+ ```typescript
109
+ import { s3Url } from '@geekmidas/storage/aws';
110
+
111
+ s3Url.build({
112
+ bucket: 'uploads',
113
+ region: 'eu-west-1',
114
+ accessKeyId: 'AKIA…',
115
+ secretAccessKey: 'wJal/rXUt+nFEMI…',
116
+ });
117
+ // 's3://AKIA…:wJal%2FrXUt%2BnFEMI…@uploads?region=eu-west-1'
118
+ ```
119
+
120
+ Prefer a key per bucket, scoped to that bucket, over one shared pair: an app
121
+ that reads two buckets then holds two narrow keys rather than one that opens
122
+ both. A URL with only one half of a pair (`s3://KEY@uploads`) is refused with
123
+ `IncompleteStorageCredentials` — it is never completed from the environment.
124
+
125
+ Every storage error that carries a URL (`MalformedStorageUrl`,
126
+ `UnexpectedStorageScheme`, `MissingStorageBucket`,
127
+ `UnregisteredStorageScheme`, `IncompleteStorageCredentials`) holds it with the
128
+ userinfo replaced by `REDACTED`, and its message never includes the URL.
129
+ `redactStorageUrl(url)` does the same for your own log lines.
130
+
88
131
  ## API Reference
89
132
 
90
133
  ### StorageClient Interface
@@ -400,20 +443,13 @@ npm run test:once
400
443
 
401
444
  ### Local Development with MinIO
402
445
 
403
- 1. Start MinIO using Docker Compose:
404
- ```bash
405
- docker-compose up -d minio
406
- ```
407
-
408
- 2. MinIO will be available at:
409
- - API: http://localhost:9000
410
- - Console: http://localhost:9001
411
- - Credentials: minioadmin/minioadmin
446
+ The suite starts the repo compose stack's MinIO itself (`geekmidas` /
447
+ `geekmidas`, with a `geekmidas` bucket). It publishes on port 9000; when
448
+ another project holds that port, move it:
412
449
 
413
- 3. Run integration tests:
414
- ```bash
415
- npm run test:integration
416
- ```
450
+ ```bash
451
+ MINIO_API_HOST_PORT=29000 MINIO_CONSOLE_HOST_PORT=29001 pnpm vitest run --project storage
452
+ ```
417
453
 
418
454
  ### Project Structure
419
455
 
package/dist/aws.cjs CHANGED
@@ -1,46 +1,77 @@
1
- const require_s3Url = require('./s3Url-Bo32UTTA.cjs');
2
- require('./errors-Bce0CJ11.cjs');
3
- const __aws_sdk_client_s3 = require_s3Url.__toESM(require("@aws-sdk/client-s3"));
4
- const __aws_sdk_s3_presigned_post = require_s3Url.__toESM(require("@aws-sdk/s3-presigned-post"));
5
- const __aws_sdk_s3_request_presigner = require_s3Url.__toESM(require("@aws-sdk/s3-request-presigner"));
6
-
7
- //#region src/StorageClient.ts
8
- let StorageProvider = /* @__PURE__ */ function(StorageProvider$1) {
9
- StorageProvider$1["AWSS3"] = "geekimdas.toolbox.storage.aws.s3";
10
- StorageProvider$1["GCP"] = "geekimdas.toolbox.storage.gcp";
11
- StorageProvider$1["AZURE"] = "geekimdas.toolbox.storage.azure";
12
- return StorageProvider$1;
13
- }({});
14
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_s3Url = require("./s3Url.cjs");
3
+ let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
4
+ let _aws_sdk_s3_presigned_post = require("@aws-sdk/s3-presigned-post");
5
+ let _aws_sdk_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
6
+ let _opentelemetry_api = require("@opentelemetry/api");
7
+ //#region src/telemetry.ts
8
+ /**
9
+ * A span per storage call, through the global OpenTelemetry tracer — a no-op
10
+ * without a registered provider.
11
+ *
12
+ * `storage.presign`, `storage.put`, `storage.delete` and `storage.list`, with
13
+ * the backend and the bucket. Never the object's key: keys often name a user.
14
+ *
15
+ * @module
16
+ */
17
+ /** Run `fn` inside a `storage.<operation>` span. */
18
+ async function traceStorage(operation, attributes, fn) {
19
+ const span = _opentelemetry_api.trace.getTracer("@geekmidas/storage").startSpan(`storage.${operation}`, {
20
+ kind: _opentelemetry_api.SpanKind.CLIENT,
21
+ attributes: {
22
+ "storage.operation": operation,
23
+ ...attributes
24
+ }
25
+ });
26
+ try {
27
+ return await fn();
28
+ } catch (error) {
29
+ if (error instanceof Error) span.recordException(error);
30
+ span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
31
+ throw error;
32
+ } finally {
33
+ span.end();
34
+ }
35
+ }
15
36
  //#endregion
16
37
  //#region src/AmazonStorageClient.ts
17
38
  var AmazonStorageClient = class AmazonStorageClient {
18
- provider = StorageProvider.AWSS3;
39
+ client;
40
+ bucket;
41
+ acl;
42
+ cache;
43
+ provider = "geekimdas.toolbox.storage.aws.s3";
19
44
  static create(options) {
20
45
  const { bucket, region, accessKeyId, acl, endpoint, secretAccessKey, forcePathStyle = false, cache } = options;
21
- const hasCredentials = accessKeyId && secretAccessKey;
22
- const credentials = hasCredentials ? {
23
- accessKeyId,
24
- secretAccessKey
25
- } : void 0;
26
- const client = new __aws_sdk_client_s3.S3Client({
46
+ const client = new _aws_sdk_client_s3.S3Client({
27
47
  region,
28
- credentials,
48
+ credentials: accessKeyId && secretAccessKey ? {
49
+ accessKeyId,
50
+ secretAccessKey
51
+ } : void 0,
29
52
  endpoint,
30
53
  forcePathStyle
31
54
  });
32
55
  return new AmazonStorageClient(client, bucket, acl, cache);
33
56
  }
34
- constructor(client, bucket, acl = AmazonCannedAccessControlList.AuthenticatedRead, cache) {
57
+ constructor(client, bucket, acl = "authenticated-read", cache) {
35
58
  this.client = client;
36
59
  this.bucket = bucket;
37
60
  this.acl = acl;
38
61
  this.cache = cache;
39
62
  }
63
+ /** A storage span on this bucket. */
64
+ traced(operation, fn, attributes = {}) {
65
+ return traceStorage(operation, {
66
+ "storage.system": "s3",
67
+ "storage.bucket": this.bucket,
68
+ ...attributes
69
+ }, fn);
70
+ }
40
71
  createGetObjectCommand(file, overrides) {
41
72
  const disposition = file.disposition ?? (file.name ? "attachment" : void 0);
42
73
  const ResponseContentDisposition = disposition ? file.name ? `${disposition}; filename=${encodeURIComponent(file.name)}` : disposition : void 0;
43
- return new __aws_sdk_client_s3.GetObjectCommand({
74
+ return new _aws_sdk_client_s3.GetObjectCommand({
44
75
  Bucket: this.bucket,
45
76
  Key: file.path,
46
77
  ResponseContentDisposition,
@@ -50,55 +81,53 @@ var AmazonStorageClient = class AmazonStorageClient {
50
81
  }
51
82
  getVersionDownloadURL(file, versionId) {
52
83
  const command = this.createGetObjectCommand(file, { VersionId: versionId });
53
- return (0, __aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn: 60 * 60 * 24 });
84
+ return this.traced("presign", () => (0, _aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn: 86400 }), { "storage.presign.method": "GET" });
54
85
  }
55
86
  async getVersions(key) {
56
- const command = new __aws_sdk_client_s3.ListObjectVersionsCommand({
87
+ const command = new _aws_sdk_client_s3.ListObjectVersionsCommand({
57
88
  Bucket: this.bucket,
58
89
  Prefix: key
59
90
  });
60
- const { Versions = [] } = await this.client.send(command);
91
+ const { Versions = [] } = await this.traced("list", () => this.client.send(command));
61
92
  return Versions.map((version) => ({
62
93
  id: version.VersionId || "",
63
94
  createdAt: version.LastModified || /* @__PURE__ */ new Date()
64
95
  }));
65
96
  }
66
- async getDownloadURL(file, expiresIn = 60 * 60) {
97
+ async getDownloadURL(file, expiresIn = 3600) {
67
98
  const cacheKey = `download-url:${file.path}`;
68
99
  const cachedURL = await this.cache?.get(cacheKey);
69
100
  if (cachedURL) return cachedURL;
70
101
  const command = this.createGetObjectCommand(file);
71
- const url = await (0, __aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn });
102
+ const url = await this.traced("presign", () => (0, _aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn }), { "storage.presign.method": "GET" });
72
103
  const cacheExpiration = Math.max(expiresIn - 60, 0);
73
104
  if (cacheExpiration) await this.cache?.set(cacheKey, url, cacheExpiration);
74
105
  return url;
75
106
  }
76
- async getUploadURL(params, expiresIn = 60 * 60) {
77
- const command = new __aws_sdk_client_s3.PutObjectCommand({
107
+ async getUploadURL(params, expiresIn = 3600) {
108
+ const command = new _aws_sdk_client_s3.PutObjectCommand({
78
109
  Bucket: this.bucket,
79
110
  Key: params.path,
80
111
  ContentType: params.contentType,
81
112
  ContentLength: params.contentLength
82
113
  });
83
- return (0, __aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn });
114
+ return this.traced("presign", () => (0, _aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn }), { "storage.presign.method": "PUT" });
84
115
  }
85
116
  async getUpload(params, expiresIn = 5) {
86
117
  const { path } = params;
87
- const { fields: values, url } = await (0, __aws_sdk_s3_presigned_post.createPresignedPost)(this.client, {
118
+ const { fields: values, url } = await this.traced("presign", () => (0, _aws_sdk_s3_presigned_post.createPresignedPost)(this.client, {
88
119
  Expires: expiresIn * 60,
89
120
  Bucket: this.bucket,
90
121
  Fields: { acl: this.acl },
91
122
  Conditions: [],
92
123
  Key: path
93
- });
94
- const keys = Object.keys(values);
95
- const fields = keys.map((key) => ({
96
- key,
97
- value: values[key] || ""
98
- }));
124
+ }), { "storage.presign.method": "POST" });
99
125
  return {
100
126
  url,
101
- fields
127
+ fields: Object.keys(values).map((key) => ({
128
+ key,
129
+ value: values[key] || ""
130
+ }))
102
131
  };
103
132
  }
104
133
  async upload(key, data, contentType) {
@@ -109,52 +138,62 @@ var AmazonStorageClient = class AmazonStorageClient {
109
138
  Body,
110
139
  ContentType: contentType
111
140
  };
112
- const command = new __aws_sdk_client_s3.PutObjectCommand(params);
113
- await this.client.send(command);
141
+ const command = new _aws_sdk_client_s3.PutObjectCommand(params);
142
+ await this.traced("put", () => this.client.send(command), {
143
+ "storage.object.size": Body.length,
144
+ "storage.object.content_type": contentType
145
+ });
114
146
  }
115
147
  async delete(key) {
116
- const command = new __aws_sdk_client_s3.DeleteObjectCommand({
148
+ const command = new _aws_sdk_client_s3.DeleteObjectCommand({
117
149
  Bucket: this.bucket,
118
150
  Key: key
119
151
  });
120
- await this.client.send(command);
152
+ await this.traced("delete", () => this.client.send(command));
121
153
  }
122
154
  };
123
- let AmazonCannedAccessControlList = /* @__PURE__ */ function(AmazonCannedAccessControlList$1) {
124
- AmazonCannedAccessControlList$1["AuthenticatedRead"] = "authenticated-read";
125
- AmazonCannedAccessControlList$1["Private"] = "private";
126
- AmazonCannedAccessControlList$1["PublicRead"] = "public-read";
127
- AmazonCannedAccessControlList$1["PublicReadWrite"] = "public-read-write";
128
- AmazonCannedAccessControlList$1["AwsExecRead"] = "aws-exec-read";
129
- AmazonCannedAccessControlList$1["BucketOwnerRead"] = "bucket-owner-read";
130
- AmazonCannedAccessControlList$1["BucketOwnerFullControl"] = "bucket-owner-full-control";
131
- AmazonCannedAccessControlList$1["LogDeliveryWrite"] = "log-delivery-write";
132
- return AmazonCannedAccessControlList$1;
155
+ let AmazonCannedAccessControlList = /* @__PURE__ */ function(AmazonCannedAccessControlList) {
156
+ AmazonCannedAccessControlList["AuthenticatedRead"] = "authenticated-read";
157
+ AmazonCannedAccessControlList["Private"] = "private";
158
+ AmazonCannedAccessControlList["PublicRead"] = "public-read";
159
+ AmazonCannedAccessControlList["PublicReadWrite"] = "public-read-write";
160
+ AmazonCannedAccessControlList["AwsExecRead"] = "aws-exec-read";
161
+ AmazonCannedAccessControlList["BucketOwnerRead"] = "bucket-owner-read";
162
+ AmazonCannedAccessControlList["BucketOwnerFullControl"] = "bucket-owner-full-control";
163
+ AmazonCannedAccessControlList["LogDeliveryWrite"] = "log-delivery-write";
164
+ return AmazonCannedAccessControlList;
133
165
  }({});
134
-
135
166
  //#endregion
136
167
  //#region src/s3Driver.ts
168
+ /**
169
+ * The S3 driver — `s3://` URLs to an {@link AmazonStorageClient}.
170
+ *
171
+ * Registered by whoever assembles the application, so neutral code can build a
172
+ * client from a URL without importing this file or the AWS SDK behind it.
173
+ */
137
174
  const s3Driver = {
138
175
  scheme: "s3:",
139
176
  create(url) {
140
- const { bucket, region, endpoint, forcePathStyle } = require_s3Url.parse(url);
177
+ const { bucket, region, endpoint, forcePathStyle, accessKeyId, secretAccessKey } = require_s3Url.parse(url);
141
178
  return AmazonStorageClient.create({
142
179
  bucket,
143
180
  region,
144
181
  endpoint,
145
- forcePathStyle
182
+ forcePathStyle,
183
+ accessKeyId,
184
+ secretAccessKey
146
185
  });
147
186
  }
148
187
  };
149
-
150
188
  //#endregion
151
189
  exports.AmazonCannedAccessControlList = AmazonCannedAccessControlList;
152
190
  exports.AmazonStorageClient = AmazonStorageClient;
153
191
  exports.s3Driver = s3Driver;
154
- Object.defineProperty(exports, 's3Url', {
155
- enumerable: true,
156
- get: function () {
157
- return require_s3Url.s3Url_exports;
158
- }
192
+ Object.defineProperty(exports, "s3Url", {
193
+ enumerable: true,
194
+ get: function() {
195
+ return require_s3Url.s3Url_exports;
196
+ }
159
197
  });
198
+
160
199
  //# sourceMappingURL=aws.cjs.map
package/dist/aws.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"aws.cjs","names":["options: AmazonStorageClientCreateOptions","S3Client","client: S3Client","bucket: string","cache?: Cache","file: File","overrides?: { VersionId?: string }","GetObjectCommand","versionId: string","key: string","ListObjectVersionsCommand","params: GetUploadParams","PutObjectCommand","data: string | Buffer","contentType: string","DeleteObjectCommand","s3Driver: StorageDriver"],"sources":["../src/StorageClient.ts","../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"sourcesContent":["import type { Cache } from '@geekmidas/cache';\nexport interface DocumentVersion {\n\tid: string;\n\tcreatedAt: Date;\n}\n\nexport enum StorageProvider {\n\tAWSS3 = 'geekimdas.toolbox.storage.aws.s3',\n\tGCP = 'geekimdas.toolbox.storage.gcp',\n\tAZURE = 'geekimdas.toolbox.storage.azure',\n}\n\nexport interface StorageClient {\n\treadonly provider: StorageProvider;\n\treadonly cache?: Cache;\n\t/**\n\t * Get a URL to upload a file to.\n\t *\n\t * @param params - The parameters to get the upload URL for.\n\t * @param expiresIn - The number of minutes the URL should be valid for.\n\t * Defaults to 60 * 60 (1 hour).\n\t * @returns A promise that resolves to the upload URL.\n\t */\n\tgetUploadURL(params: GetUploadParams, expiresIn?: number): Promise<string>;\n\n\t/**\n\t * Get a URL to download a file from.\n\t *\n\t * @param file - The file to get the download URL for.\n\t * @param expiresIn - The number of seconds the URL should be valid for.\n\t * Defaults to 60 * 60 (1 hour).\n\t * @returns A promise that resolves to the download URL.\n\t */\n\tgetDownloadURL(file: File, expiresIn?: number): Promise<string>;\n\t/**\n\t * Get the versions for a key.\n\t *\n\t * @param key - The key to get the versions for.\n\t */\n\tgetVersions(key: string): Promise<DocumentVersion[]>;\n\n\t/**\n\t * Get a URL to download a version of a file from.\n\t *\n\t * @param file - The file to get the download URL for.\n\t * @param versionId - The version ID to get the download URL for.\n\t */\n\tgetVersionDownloadURL(file: File, versionId: string): Promise<string>;\n\t/**\n\t *\n\t * @param key - The key to upload the file to.\n\t * @param data - The data to upload.\n\t * @param contentType - The content type of the data.\n\t */\n\tupload(\n\t\tkey: string,\n\t\tdata: string | Buffer,\n\t\tcontentType: string,\n\t): Promise<void>;\n\t/**\n\t * Get a URL to upload a file to.\n\t *\n\t * @param params - The parameters to get the upload URL for.\n\t * @param expiresIn - The number of minutes the URL should be valid for.\n\t */\n\tgetUpload(\n\t\tparams: GetUploadParams,\n\t\texpiresIn?: number,\n\t): Promise<GetUploadResponse>;\n\t/**\n\t * Delete a file from storage.\n\t *\n\t * @param key - The key (path) of the file to delete.\n\t */\n\tdelete(key: string): Promise<void>;\n}\n\nexport interface GetUploadParams {\n\tpath: string;\n\tcontentType: string;\n\tcontentLength: number;\n}\n\nexport type UploadField = {\n\tkey: string;\n\tvalue: string;\n};\n\nexport type GetUploadResponse = {\n\turl: string;\n\tfields: UploadField[];\n};\n\nexport interface File {\n\tname?: string;\n\tpath: string;\n\t/**\n\t * Override the Content-Type of the response.\n\t * Useful when the stored object's content type differs from what the browser should receive.\n\t */\n\tresponseContentType?: string;\n\t/**\n\t * Control how the browser handles the file.\n\t * - `'attachment'` — prompts download (default when `name` is set)\n\t * - `'inline'` — displays in the browser\n\t */\n\tdisposition?: 'inline' | 'attachment';\n}\n","import {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tListObjectVersionsCommand,\n\tPutObjectCommand,\n\tS3Client,\n} from '@aws-sdk/client-s3';\nimport { createPresignedPost } from '@aws-sdk/s3-presigned-post';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nimport type { Cache } from '@geekmidas/cache';\nimport {\n\ttype DocumentVersion,\n\ttype File,\n\ttype GetUploadParams,\n\ttype GetUploadResponse,\n\ttype StorageClient,\n\tStorageProvider,\n} from './StorageClient';\n\nexport class AmazonStorageClient implements StorageClient {\n\treadonly provider = StorageProvider.AWSS3;\n\n\tstatic create(\n\t\toptions: AmazonStorageClientCreateOptions,\n\t): AmazonStorageClient {\n\t\tconst {\n\t\t\tbucket,\n\t\t\tregion,\n\t\t\taccessKeyId,\n\t\t\tacl,\n\t\t\tendpoint,\n\t\t\tsecretAccessKey,\n\t\t\tforcePathStyle = false,\n\t\t\tcache,\n\t\t} = options;\n\t\tconst hasCredentials = accessKeyId && secretAccessKey;\n\t\tconst credentials = hasCredentials\n\t\t\t? { accessKeyId, secretAccessKey }\n\t\t\t: undefined;\n\n\t\tconst client = new S3Client({\n\t\t\tregion,\n\t\t\tcredentials,\n\t\t\tendpoint,\n\t\t\tforcePathStyle,\n\t\t});\n\n\t\treturn new AmazonStorageClient(client, bucket, acl, cache);\n\t}\n\tconstructor(\n\t\tprivate readonly client: S3Client,\n\t\tprivate readonly bucket: string,\n\t\tprivate readonly acl = AmazonCannedAccessControlList.AuthenticatedRead,\n\t\treadonly cache?: Cache,\n\t) {}\n\n\tprivate createGetObjectCommand(\n\t\tfile: File,\n\t\toverrides?: { VersionId?: string },\n\t): GetObjectCommand {\n\t\tconst disposition =\n\t\t\tfile.disposition ?? (file.name ? 'attachment' : undefined);\n\t\tconst ResponseContentDisposition = disposition\n\t\t\t? file.name\n\t\t\t\t? `${disposition}; filename=${encodeURIComponent(file.name)}`\n\t\t\t\t: disposition\n\t\t\t: undefined;\n\n\t\treturn new GetObjectCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tKey: file.path,\n\t\t\tResponseContentDisposition,\n\t\t\tResponseContentType: file.responseContentType,\n\t\t\t...overrides,\n\t\t});\n\t}\n\n\tgetVersionDownloadURL(file: File, versionId: string): Promise<string> {\n\t\tconst command = this.createGetObjectCommand(file, { VersionId: versionId });\n\t\treturn getSignedUrl(this.client, command, { expiresIn: 60 * 60 * 24 });\n\t}\n\n\tasync getVersions(key: string): Promise<DocumentVersion[]> {\n\t\tconst command = new ListObjectVersionsCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tPrefix: key,\n\t\t});\n\n\t\tconst { Versions = [] } = await this.client.send(command);\n\n\t\treturn Versions.map((version) => ({\n\t\t\tid: version.VersionId || '',\n\t\t\tcreatedAt: version.LastModified || new Date(),\n\t\t}));\n\t}\n\n\tasync getDownloadURL(file: File, expiresIn = 60 * 60): Promise<string> {\n\t\tconst cacheKey = `download-url:${file.path}`;\n\t\tconst cachedURL = await this.cache?.get<string>(cacheKey);\n\n\t\tif (cachedURL) {\n\t\t\treturn cachedURL;\n\t\t}\n\n\t\tconst command = this.createGetObjectCommand(file);\n\t\tconst url = await getSignedUrl(this.client, command, { expiresIn });\n\t\tconst cacheExpiration = Math.max(expiresIn - 60, 0);\n\n\t\tif (cacheExpiration) {\n\t\t\tawait this.cache?.set(cacheKey, url, cacheExpiration);\n\t\t}\n\n\t\treturn url;\n\t}\n\n\tasync getUploadURL(\n\t\tparams: GetUploadParams,\n\t\texpiresIn = 60 * 60,\n\t): Promise<string> {\n\t\tconst command = new PutObjectCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tKey: params.path,\n\t\t\tContentType: params.contentType,\n\t\t\tContentLength: params.contentLength,\n\t\t});\n\n\t\treturn getSignedUrl(this.client, command, { expiresIn });\n\t}\n\n\tasync getUpload(\n\t\tparams: GetUploadParams,\n\t\texpiresIn = 5,\n\t): Promise<GetUploadResponse> {\n\t\tconst { path } = params;\n\t\tconst { fields: values, url } = await createPresignedPost(this.client, {\n\t\t\tExpires: expiresIn * 60,\n\t\t\tBucket: this.bucket,\n\t\t\tFields: {\n\t\t\t\tacl: this.acl,\n\t\t\t},\n\t\t\tConditions: [\n\t\t\t\t// content length restrictions: 0-1MB]\n\t\t\t\t// ['content-length-range', 0, contentLength],\n\t\t\t\t// specify content-type to be more generic- images only\n\t\t\t\t// ['starts-with', '$Content-Type', 'image/'],\n\t\t\t\t// ['starts-with', '$Content-Type', contentType],\n\t\t\t],\n\t\t\tKey: path,\n\t\t});\n\n\t\tconst keys = Object.keys(values);\n\t\tconst fields = keys.map((key) => ({ key, value: values[key] || '' }));\n\n\t\treturn { url, fields };\n\t}\n\n\tasync upload(\n\t\tkey: string,\n\t\tdata: string | Buffer,\n\t\tcontentType: string,\n\t): Promise<void> {\n\t\tconst Body = typeof data === 'string' ? Buffer.from(data, 'base64') : data;\n\n\t\tconst params = {\n\t\t\tBucket: this.bucket,\n\t\t\tKey: key,\n\t\t\tBody,\n\t\t\tContentType: contentType,\n\t\t};\n\n\t\tconst command = new PutObjectCommand(params);\n\n\t\tawait this.client.send(command);\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tconst command = new DeleteObjectCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tKey: key,\n\t\t});\n\n\t\tawait this.client.send(command);\n\t}\n}\n\nexport enum AmazonCannedAccessControlList {\n\tAuthenticatedRead = 'authenticated-read',\n\tPrivate = 'private',\n\tPublicRead = 'public-read',\n\tPublicReadWrite = 'public-read-write',\n\tAwsExecRead = 'aws-exec-read',\n\tBucketOwnerRead = 'bucket-owner-read',\n\tBucketOwnerFullControl = 'bucket-owner-full-control',\n\tLogDeliveryWrite = 'log-delivery-write',\n}\n\ninterface AmazonStorageClientCreateOptions {\n\tbucket: string;\n\tregion?: string;\n\tacl?: AmazonCannedAccessControlList;\n\taccessKeyId?: string;\n\tsecretAccessKey?: string;\n\tendpoint?: string;\n\tforcePathStyle?: boolean;\n\tcache?: Cache;\n}\n","/**\n * The S3 driver — `s3://` URLs to an {@link AmazonStorageClient}.\n *\n * Registered by whoever assembles the application, so neutral code can build a\n * client from a URL without importing this file or the AWS SDK behind it.\n */\n\nimport { AmazonStorageClient } from './AmazonStorageClient';\nimport type { StorageDriver } from './registry';\nimport { parse } from './s3Url';\n\nexport const s3Driver: StorageDriver = {\n\tscheme: 's3:',\n\tcreate(url) {\n\t\tconst { bucket, region, endpoint, forcePathStyle } = parse(url);\n\t\treturn AmazonStorageClient.create({\n\t\t\tbucket,\n\t\t\tregion,\n\t\t\tendpoint,\n\t\t\tforcePathStyle,\n\t\t});\n\t},\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"aws.cjs","names":["trace","SpanKind","SpanStatusCode","S3Client","GetObjectCommand","getSignedUrl","ListObjectVersionsCommand","PutObjectCommand","createPresignedPost","DeleteObjectCommand","parse"],"sources":["../src/telemetry.ts","../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"sourcesContent":["/**\n * A span per storage call, through the global OpenTelemetry tracer — a no-op\n * without a registered provider.\n *\n * `storage.presign`, `storage.put`, `storage.delete` and `storage.list`, with\n * the backend and the bucket. Never the object's key: keys often name a user.\n *\n * @module\n */\nimport {\n\ttype Attributes,\n\tSpanKind,\n\tSpanStatusCode,\n\ttrace,\n} from '@opentelemetry/api';\n\nexport type StorageOperation = 'presign' | 'put' | 'delete' | 'list';\n\n/** Run `fn` inside a `storage.<operation>` span. */\nexport async function traceStorage<T>(\n\toperation: StorageOperation,\n\tattributes: Attributes,\n\tfn: () => Promise<T>,\n): Promise<T> {\n\tconst span = trace\n\t\t.getTracer('@geekmidas/storage')\n\t\t.startSpan(`storage.${operation}`, {\n\t\t\tkind: SpanKind.CLIENT,\n\t\t\tattributes: { 'storage.operation': operation, ...attributes },\n\t\t});\n\ttry {\n\t\treturn await fn();\n\t} catch (error) {\n\t\tif (error instanceof Error) span.recordException(error);\n\t\tspan.setStatus({ code: SpanStatusCode.ERROR });\n\t\tthrow error;\n\t} finally {\n\t\tspan.end();\n\t}\n}\n","import {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tListObjectVersionsCommand,\n\tPutObjectCommand,\n\tS3Client,\n} from '@aws-sdk/client-s3';\nimport { createPresignedPost } from '@aws-sdk/s3-presigned-post';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\nimport type { Cache } from '@geekmidas/cache';\nimport type { Attributes } from '@opentelemetry/api';\nimport {\n\ttype DocumentVersion,\n\ttype File,\n\ttype GetUploadParams,\n\ttype GetUploadResponse,\n\ttype StorageClient,\n\tStorageProvider,\n} from './StorageClient';\nimport { type StorageOperation, traceStorage } from './telemetry';\n\nexport class AmazonStorageClient implements StorageClient {\n\treadonly provider = StorageProvider.AWSS3;\n\n\tstatic create(\n\t\toptions: AmazonStorageClientCreateOptions,\n\t): AmazonStorageClient {\n\t\tconst {\n\t\t\tbucket,\n\t\t\tregion,\n\t\t\taccessKeyId,\n\t\t\tacl,\n\t\t\tendpoint,\n\t\t\tsecretAccessKey,\n\t\t\tforcePathStyle = false,\n\t\t\tcache,\n\t\t} = options;\n\t\tconst hasCredentials = accessKeyId && secretAccessKey;\n\t\tconst credentials = hasCredentials\n\t\t\t? { accessKeyId, secretAccessKey }\n\t\t\t: undefined;\n\n\t\tconst client = new S3Client({\n\t\t\tregion,\n\t\t\tcredentials,\n\t\t\tendpoint,\n\t\t\tforcePathStyle,\n\t\t});\n\n\t\treturn new AmazonStorageClient(client, bucket, acl, cache);\n\t}\n\tconstructor(\n\t\tprivate readonly client: S3Client,\n\t\tprivate readonly bucket: string,\n\t\tprivate readonly acl = AmazonCannedAccessControlList.AuthenticatedRead,\n\t\treadonly cache?: Cache,\n\t) {}\n\n\t/** A storage span on this bucket. */\n\tprivate traced<T>(\n\t\toperation: StorageOperation,\n\t\tfn: () => Promise<T>,\n\t\tattributes: Attributes = {},\n\t): Promise<T> {\n\t\treturn traceStorage(\n\t\t\toperation,\n\t\t\t{ 'storage.system': 's3', 'storage.bucket': this.bucket, ...attributes },\n\t\t\tfn,\n\t\t);\n\t}\n\n\tprivate createGetObjectCommand(\n\t\tfile: File,\n\t\toverrides?: { VersionId?: string },\n\t): GetObjectCommand {\n\t\tconst disposition =\n\t\t\tfile.disposition ?? (file.name ? 'attachment' : undefined);\n\t\tconst ResponseContentDisposition = disposition\n\t\t\t? file.name\n\t\t\t\t? `${disposition}; filename=${encodeURIComponent(file.name)}`\n\t\t\t\t: disposition\n\t\t\t: undefined;\n\n\t\treturn new GetObjectCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tKey: file.path,\n\t\t\tResponseContentDisposition,\n\t\t\tResponseContentType: file.responseContentType,\n\t\t\t...overrides,\n\t\t});\n\t}\n\n\tgetVersionDownloadURL(file: File, versionId: string): Promise<string> {\n\t\tconst command = this.createGetObjectCommand(file, { VersionId: versionId });\n\t\treturn this.traced(\n\t\t\t'presign',\n\t\t\t() => getSignedUrl(this.client, command, { expiresIn: 60 * 60 * 24 }),\n\t\t\t{ 'storage.presign.method': 'GET' },\n\t\t);\n\t}\n\n\tasync getVersions(key: string): Promise<DocumentVersion[]> {\n\t\tconst command = new ListObjectVersionsCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tPrefix: key,\n\t\t});\n\n\t\tconst { Versions = [] } = await this.traced('list', () =>\n\t\t\tthis.client.send(command),\n\t\t);\n\n\t\treturn Versions.map((version) => ({\n\t\t\tid: version.VersionId || '',\n\t\t\tcreatedAt: version.LastModified || new Date(),\n\t\t}));\n\t}\n\n\tasync getDownloadURL(file: File, expiresIn = 60 * 60): Promise<string> {\n\t\tconst cacheKey = `download-url:${file.path}`;\n\t\tconst cachedURL = await this.cache?.get<string>(cacheKey);\n\n\t\tif (cachedURL) {\n\t\t\treturn cachedURL;\n\t\t}\n\n\t\tconst command = this.createGetObjectCommand(file);\n\t\tconst url = await this.traced(\n\t\t\t'presign',\n\t\t\t() => getSignedUrl(this.client, command, { expiresIn }),\n\t\t\t{ 'storage.presign.method': 'GET' },\n\t\t);\n\t\tconst cacheExpiration = Math.max(expiresIn - 60, 0);\n\n\t\tif (cacheExpiration) {\n\t\t\tawait this.cache?.set(cacheKey, url, cacheExpiration);\n\t\t}\n\n\t\treturn url;\n\t}\n\n\tasync getUploadURL(\n\t\tparams: GetUploadParams,\n\t\texpiresIn = 60 * 60,\n\t): Promise<string> {\n\t\tconst command = new PutObjectCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tKey: params.path,\n\t\t\tContentType: params.contentType,\n\t\t\tContentLength: params.contentLength,\n\t\t});\n\n\t\treturn this.traced(\n\t\t\t'presign',\n\t\t\t() => getSignedUrl(this.client, command, { expiresIn }),\n\t\t\t{ 'storage.presign.method': 'PUT' },\n\t\t);\n\t}\n\n\tasync getUpload(\n\t\tparams: GetUploadParams,\n\t\texpiresIn = 5,\n\t): Promise<GetUploadResponse> {\n\t\tconst { path } = params;\n\t\tconst { fields: values, url } = await this.traced(\n\t\t\t'presign',\n\t\t\t() =>\n\t\t\t\tcreatePresignedPost(this.client, {\n\t\t\t\t\tExpires: expiresIn * 60,\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tFields: {\n\t\t\t\t\t\tacl: this.acl,\n\t\t\t\t\t},\n\t\t\t\t\tConditions: [\n\t\t\t\t\t\t// content length restrictions: 0-1MB]\n\t\t\t\t\t\t// ['content-length-range', 0, contentLength],\n\t\t\t\t\t\t// specify content-type to be more generic- images only\n\t\t\t\t\t\t// ['starts-with', '$Content-Type', 'image/'],\n\t\t\t\t\t\t// ['starts-with', '$Content-Type', contentType],\n\t\t\t\t\t],\n\t\t\t\t\tKey: path,\n\t\t\t\t}),\n\t\t\t{ 'storage.presign.method': 'POST' },\n\t\t);\n\n\t\tconst keys = Object.keys(values);\n\t\tconst fields = keys.map((key) => ({ key, value: values[key] || '' }));\n\n\t\treturn { url, fields };\n\t}\n\n\tasync upload(\n\t\tkey: string,\n\t\tdata: string | Buffer,\n\t\tcontentType: string,\n\t): Promise<void> {\n\t\tconst Body = typeof data === 'string' ? Buffer.from(data, 'base64') : data;\n\n\t\tconst params = {\n\t\t\tBucket: this.bucket,\n\t\t\tKey: key,\n\t\t\tBody,\n\t\t\tContentType: contentType,\n\t\t};\n\n\t\tconst command = new PutObjectCommand(params);\n\n\t\tawait this.traced('put', () => this.client.send(command), {\n\t\t\t'storage.object.size': Body.length,\n\t\t\t'storage.object.content_type': contentType,\n\t\t});\n\t}\n\n\tasync delete(key: string): Promise<void> {\n\t\tconst command = new DeleteObjectCommand({\n\t\t\tBucket: this.bucket,\n\t\t\tKey: key,\n\t\t});\n\n\t\tawait this.traced('delete', () => this.client.send(command));\n\t}\n}\n\nexport enum AmazonCannedAccessControlList {\n\tAuthenticatedRead = 'authenticated-read',\n\tPrivate = 'private',\n\tPublicRead = 'public-read',\n\tPublicReadWrite = 'public-read-write',\n\tAwsExecRead = 'aws-exec-read',\n\tBucketOwnerRead = 'bucket-owner-read',\n\tBucketOwnerFullControl = 'bucket-owner-full-control',\n\tLogDeliveryWrite = 'log-delivery-write',\n}\n\ninterface AmazonStorageClientCreateOptions {\n\tbucket: string;\n\tregion?: string;\n\tacl?: AmazonCannedAccessControlList;\n\taccessKeyId?: string;\n\tsecretAccessKey?: string;\n\tendpoint?: string;\n\tforcePathStyle?: boolean;\n\tcache?: Cache;\n}\n","/**\n * The S3 driver — `s3://` URLs to an {@link AmazonStorageClient}.\n *\n * Registered by whoever assembles the application, so neutral code can build a\n * client from a URL without importing this file or the AWS SDK behind it.\n */\n\nimport { AmazonStorageClient } from './AmazonStorageClient';\nimport type { StorageDriver } from './registry';\nimport { parse } from './s3Url';\n\nexport const s3Driver: StorageDriver = {\n\tscheme: 's3:',\n\tcreate(url) {\n\t\t// The URL's own key pair, when it has one, wins over the SDK's default\n\t\t// chain; without one the client resolves credentials as it always has.\n\t\tconst {\n\t\t\tbucket,\n\t\t\tregion,\n\t\t\tendpoint,\n\t\t\tforcePathStyle,\n\t\t\taccessKeyId,\n\t\t\tsecretAccessKey,\n\t\t} = parse(url);\n\t\treturn AmazonStorageClient.create({\n\t\t\tbucket,\n\t\t\tregion,\n\t\t\tendpoint,\n\t\t\tforcePathStyle,\n\t\t\taccessKeyId,\n\t\t\tsecretAccessKey,\n\t\t});\n\t},\n};\n"],"mappings":"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"}
package/dist/aws.d.cts CHANGED
@@ -1,10 +1,9 @@
1
- import { S3Address, s3Url_d_exports } from "./s3Url-BfztIltX.cjs";
2
- import { DocumentVersion, File, GetUploadParams, GetUploadResponse, StorageClient, StorageDriver, StorageProvider } from "./registry-DBsAUTT3.cjs";
1
+ import { a as DocumentVersion, c as GetUploadResponse, l as StorageClient, o as File, s as GetUploadParams, t as StorageDriver, u as StorageProvider } from "./registry-ClEvHGpM.cjs";
2
+ import { S3Address, t as s3Url_d_exports } from "./s3Url.cjs";
3
3
  import { S3Client } from "@aws-sdk/client-s3";
4
4
  import { Cache } from "@geekmidas/cache";
5
-
6
5
  //#region src/AmazonStorageClient.d.ts
7
- declare class AmazonStorageClient implements StorageClient {
6
+ export declare class AmazonStorageClient implements StorageClient {
8
7
  private readonly client;
9
8
  private readonly bucket;
10
9
  private readonly acl;
@@ -12,6 +11,8 @@ declare class AmazonStorageClient implements StorageClient {
12
11
  readonly provider = StorageProvider.AWSS3;
13
12
  static create(options: AmazonStorageClientCreateOptions): AmazonStorageClient;
14
13
  constructor(client: S3Client, bucket: string, acl?: AmazonCannedAccessControlList, cache?: Cache);
14
+ /** A storage span on this bucket. */
15
+ private traced;
15
16
  private createGetObjectCommand;
16
17
  getVersionDownloadURL(file: File, versionId: string): Promise<string>;
17
18
  getVersions(key: string): Promise<DocumentVersion[]>;
@@ -21,7 +22,7 @@ declare class AmazonStorageClient implements StorageClient {
21
22
  upload(key: string, data: string | Buffer, contentType: string): Promise<void>;
22
23
  delete(key: string): Promise<void>;
23
24
  }
24
- declare enum AmazonCannedAccessControlList {
25
+ export declare enum AmazonCannedAccessControlList {
25
26
  AuthenticatedRead = "authenticated-read",
26
27
  Private = "private",
27
28
  PublicRead = "public-read",
@@ -29,7 +30,7 @@ declare enum AmazonCannedAccessControlList {
29
30
  AwsExecRead = "aws-exec-read",
30
31
  BucketOwnerRead = "bucket-owner-read",
31
32
  BucketOwnerFullControl = "bucket-owner-full-control",
32
- LogDeliveryWrite = "log-delivery-write",
33
+ LogDeliveryWrite = "log-delivery-write"
33
34
  }
34
35
  interface AmazonStorageClientCreateOptions {
35
36
  bucket: string;
@@ -43,9 +44,7 @@ interface AmazonStorageClientCreateOptions {
43
44
  }
44
45
  //#endregion
45
46
  //#region src/s3Driver.d.ts
46
- declare const s3Driver: StorageDriver;
47
- //# sourceMappingURL=s3Driver.d.ts.map
48
-
47
+ export declare const s3Driver: StorageDriver;
49
48
  //#endregion
50
- export { AmazonCannedAccessControlList, AmazonStorageClient, DocumentVersion, File, GetUploadParams, GetUploadResponse, S3Address, StorageClient, s3Driver, s3Url_d_exports as s3Url };
49
+ export { type DocumentVersion, type File, type GetUploadParams, type GetUploadResponse, type S3Address, type StorageClient, s3Url_d_exports as s3Url };
51
50
  //# sourceMappingURL=aws.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"aws.d.cts","names":[],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"sourcesContent":[],"mappings":";;;;;;cAoBa,mBAAA,YAA+B;;;EAA/B,iBAAA,GAAA;EAAoB,SAAA,KAAA,CAAA,EAkCd,KAlCc;EAAA,SAkCd,QAAA,GAjCD,eAAA,CAAA,KAAA;EAiCM,OAjCN,MAAA,CAAA,OAAA,EAGP,gCAHO,CAAA,EAId,mBAJc;EAAA,WAGP,CAAA,MAAA,EA2BgB,QA3BhB,EAAA,MAAA,EAAA,MAAA,EAAA,GAAA,CAAA,EA6BW,6BA7BX,EAAA,KAAA,CAAA,EA8BQ,KA9BR;EAAgC,QACvC,sBAAA;EAAmB,qBA0BI,CAAA,IAAA,EA2BE,IA3BF,EAAA,SAAA,EAAA,MAAA,CAAA,EA2B4B,OA3B5B,CAAA,MAAA,CAAA;EAAQ,WAEb,CAAA,GAAA,EAAA,MAAA,CAAA,EA8BW,OA9BX,CA8BmB,eA9BnB,EAAA,CAAA;EAAkD,cACrD,CAAA,IAAA,EA2CS,IA3CT,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EA2CqC,OA3CrC,CAAA,MAAA,CAAA;EAAK,YAwBK,CAAA,MAAA,EAuCnB,eAvCmB,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAyCzB,OAzCyB,CAAA,MAAA,CAAA;EAAI,SAAsB,CAAA,MAAA,EAqD7C,eArD6C,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAuDnD,OAvDmD,CAuD3C,iBAvD2C,CAAA;EAAO,MAKrB,CAAA,GAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,GA4ExB,MA5EwB,EAAA,WAAA,EAAA,MAAA,CAAA,EA8ErC,OA9EqC,CAAA,IAAA,CAAA;EAAe,MAAvB,CAAA,GAAA,EAAA,MAAA,CAAA,EA6FL,OA7FK,CAAA,IAAA,CAAA;;AAcuB,aAyF5C,6BAAA;EAzFmD,iBAoBrD,GAAA,oBAAA;EAAe,OAErB,GAAA,SAAA;EAAO,UAYD,GAAA,aAAA;EAAe,eAEb,GAAA,mBAAA;EAAiB,WAAzB,GAAA,eAAA;EAAO,eA0BM,GAAA,mBAAA;EAAM,sBAEnB,GAAA,2BAAA;EAAO,gBAeiB,GAAA,oBAAA;;AA5J6B,UAiL/C,gCAAA,CAjL+C;EAsK7C,MAAA,EAAA,MAAA;EAWF,MAAA,CAAA,EAAA,MAAA;EAAgC,GAAA,CAAA,EAGnC,6BAHmC;EAAA,WAGnC,CAAA,EAAA,MAAA;EAA6B,eAK3B,CAAA,EAAA,MAAA;EAAK,QAAA,CAAA,EAAA,MAAA;;UAAL;;;;AAzLwB,cCTpB,QDSoB,ECTV,aDSU"}
1
+ {"version":3,"file":"aws.d.cts","names":[],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"mappings":";;;;;qBAsBa,+BAA+B;mBA+BzB;mBACA;mBACA;WACR,QAAQ;WAjCT,WAAQ,gBAAA;SAEV,OACN,SAAS,mCACP;EAyBH,YACkB,QAAQ,UACR,gBACA,MAAG,+BACX,QAAQ;;UAIV;UAYA;EAqBR,sBAAsB,MAAM,MAAM,oBAAoB;EAShD,YAAY,cAAc,QAAQ;EAgBlC,eAAe,MAAM,MAAM,qBAAsB;EAuBjD,aACL,QAAQ,iBACR,qBACE;EAeG,UACL,QAAQ,iBACR,qBACE,QAAQ;EA6BL,OACL,aACA,eAAe,QACf,sBACE;EAkBG,OAAO,cAAc;;oBAUhB;EACX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGS;EACT;EACA;EACA,MAAM;EACN;EACA;EACA;EACA;EACA,QAAQ;;;;qBCvOI,UAAU"}
package/dist/aws.d.mts CHANGED
@@ -1,10 +1,9 @@
1
- import { DocumentVersion, File, GetUploadParams, GetUploadResponse, StorageClient, StorageDriver, StorageProvider } from "./registry-DEqSHjx-.mjs";
2
- import { S3Address, s3Url_d_exports } from "./s3Url-COpMIgF9.mjs";
1
+ import { a as DocumentVersion, c as GetUploadResponse, l as StorageClient, o as File, s as GetUploadParams, t as StorageDriver, u as StorageProvider } from "./registry-ClEvHGpM.mjs";
2
+ import { S3Address, t as s3Url_d_exports } from "./s3Url.mjs";
3
3
  import { S3Client } from "@aws-sdk/client-s3";
4
4
  import { Cache } from "@geekmidas/cache";
5
-
6
5
  //#region src/AmazonStorageClient.d.ts
7
- declare class AmazonStorageClient implements StorageClient {
6
+ export declare class AmazonStorageClient implements StorageClient {
8
7
  private readonly client;
9
8
  private readonly bucket;
10
9
  private readonly acl;
@@ -12,6 +11,8 @@ declare class AmazonStorageClient implements StorageClient {
12
11
  readonly provider = StorageProvider.AWSS3;
13
12
  static create(options: AmazonStorageClientCreateOptions): AmazonStorageClient;
14
13
  constructor(client: S3Client, bucket: string, acl?: AmazonCannedAccessControlList, cache?: Cache);
14
+ /** A storage span on this bucket. */
15
+ private traced;
15
16
  private createGetObjectCommand;
16
17
  getVersionDownloadURL(file: File, versionId: string): Promise<string>;
17
18
  getVersions(key: string): Promise<DocumentVersion[]>;
@@ -21,7 +22,7 @@ declare class AmazonStorageClient implements StorageClient {
21
22
  upload(key: string, data: string | Buffer, contentType: string): Promise<void>;
22
23
  delete(key: string): Promise<void>;
23
24
  }
24
- declare enum AmazonCannedAccessControlList {
25
+ export declare enum AmazonCannedAccessControlList {
25
26
  AuthenticatedRead = "authenticated-read",
26
27
  Private = "private",
27
28
  PublicRead = "public-read",
@@ -29,7 +30,7 @@ declare enum AmazonCannedAccessControlList {
29
30
  AwsExecRead = "aws-exec-read",
30
31
  BucketOwnerRead = "bucket-owner-read",
31
32
  BucketOwnerFullControl = "bucket-owner-full-control",
32
- LogDeliveryWrite = "log-delivery-write",
33
+ LogDeliveryWrite = "log-delivery-write"
33
34
  }
34
35
  interface AmazonStorageClientCreateOptions {
35
36
  bucket: string;
@@ -43,9 +44,7 @@ interface AmazonStorageClientCreateOptions {
43
44
  }
44
45
  //#endregion
45
46
  //#region src/s3Driver.d.ts
46
- declare const s3Driver: StorageDriver;
47
- //# sourceMappingURL=s3Driver.d.ts.map
48
-
47
+ export declare const s3Driver: StorageDriver;
49
48
  //#endregion
50
- export { AmazonCannedAccessControlList, AmazonStorageClient, DocumentVersion, File, GetUploadParams, GetUploadResponse, S3Address, StorageClient, s3Driver, s3Url_d_exports as s3Url };
49
+ export { type DocumentVersion, type File, type GetUploadParams, type GetUploadResponse, type S3Address, type StorageClient, s3Url_d_exports as s3Url };
51
50
  //# sourceMappingURL=aws.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"aws.d.mts","names":[],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"sourcesContent":[],"mappings":";;;;;;cAoBa,mBAAA,YAA+B;;;EAA/B,iBAAA,GAAA;EAAoB,SAAA,KAAA,CAAA,EAkCd,KAlCc;EAAA,SAkCd,QAAA,GAjCD,eAAA,CAAA,KAAA;EAiCM,OAjCN,MAAA,CAAA,OAAA,EAGP,gCAHO,CAAA,EAId,mBAJc;EAAA,WAGP,CAAA,MAAA,EA2BgB,QA3BhB,EAAA,MAAA,EAAA,MAAA,EAAA,GAAA,CAAA,EA6BW,6BA7BX,EAAA,KAAA,CAAA,EA8BQ,KA9BR;EAAgC,QACvC,sBAAA;EAAmB,qBA0BI,CAAA,IAAA,EA2BE,IA3BF,EAAA,SAAA,EAAA,MAAA,CAAA,EA2B4B,OA3B5B,CAAA,MAAA,CAAA;EAAQ,WAEb,CAAA,GAAA,EAAA,MAAA,CAAA,EA8BW,OA9BX,CA8BmB,eA9BnB,EAAA,CAAA;EAAkD,cACrD,CAAA,IAAA,EA2CS,IA3CT,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EA2CqC,OA3CrC,CAAA,MAAA,CAAA;EAAK,YAwBK,CAAA,MAAA,EAuCnB,eAvCmB,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAyCzB,OAzCyB,CAAA,MAAA,CAAA;EAAI,SAAsB,CAAA,MAAA,EAqD7C,eArD6C,EAAA,SAAA,CAAA,EAAA,MAAA,CAAA,EAuDnD,OAvDmD,CAuD3C,iBAvD2C,CAAA;EAAO,MAKrB,CAAA,GAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,GA4ExB,MA5EwB,EAAA,WAAA,EAAA,MAAA,CAAA,EA8ErC,OA9EqC,CAAA,IAAA,CAAA;EAAe,MAAvB,CAAA,GAAA,EAAA,MAAA,CAAA,EA6FL,OA7FK,CAAA,IAAA,CAAA;;AAcuB,aAyF5C,6BAAA;EAzFmD,iBAoBrD,GAAA,oBAAA;EAAe,OAErB,GAAA,SAAA;EAAO,UAYD,GAAA,aAAA;EAAe,eAEb,GAAA,mBAAA;EAAiB,WAAzB,GAAA,eAAA;EAAO,eA0BM,GAAA,mBAAA;EAAM,sBAEnB,GAAA,2BAAA;EAAO,gBAeiB,GAAA,oBAAA;;AA5J6B,UAiL/C,gCAAA,CAjL+C;EAsK7C,MAAA,EAAA,MAAA;EAWF,MAAA,CAAA,EAAA,MAAA;EAAgC,GAAA,CAAA,EAGnC,6BAHmC;EAAA,WAGnC,CAAA,EAAA,MAAA;EAA6B,eAK3B,CAAA,EAAA,MAAA;EAAK,QAAA,CAAA,EAAA,MAAA;;UAAL;;;;AAzLwB,cCTpB,QDSoB,ECTV,aDSU"}
1
+ {"version":3,"file":"aws.d.mts","names":[],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"mappings":";;;;;qBAsBa,+BAA+B;mBA+BzB;mBACA;mBACA;WACR,QAAQ;WAjCT,WAAQ,gBAAA;SAEV,OACN,SAAS,mCACP;EAyBH,YACkB,QAAQ,UACR,gBACA,MAAG,+BACX,QAAQ;;UAIV;UAYA;EAqBR,sBAAsB,MAAM,MAAM,oBAAoB;EAShD,YAAY,cAAc,QAAQ;EAgBlC,eAAe,MAAM,MAAM,qBAAsB;EAuBjD,aACL,QAAQ,iBACR,qBACE;EAeG,UACL,QAAQ,iBACR,qBACE,QAAQ;EA6BL,OACL,aACA,eAAe,QACf,sBACE;EAkBG,OAAO,cAAc;;oBAUhB;EACX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGS;EACT;EACA;EACA,MAAM;EACN;EACA;EACA;EACA;EACA,QAAQ;;;;qBCvOI,UAAU"}