@geekmidas/storage 10.0.0-alpha.78 → 10.0.0-alpha.80

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/dist/aws.cjs CHANGED
@@ -3,6 +3,37 @@ const require_s3Url = require("./s3Url.cjs");
3
3
  let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
4
4
  let _aws_sdk_s3_presigned_post = require("@aws-sdk/s3-presigned-post");
5
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
+ }
36
+ //#endregion
6
37
  //#region src/AmazonStorageClient.ts
7
38
  var AmazonStorageClient = class AmazonStorageClient {
8
39
  client;
@@ -29,6 +60,14 @@ var AmazonStorageClient = class AmazonStorageClient {
29
60
  this.acl = acl;
30
61
  this.cache = cache;
31
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
+ }
32
71
  createGetObjectCommand(file, overrides) {
33
72
  const disposition = file.disposition ?? (file.name ? "attachment" : void 0);
34
73
  const ResponseContentDisposition = disposition ? file.name ? `${disposition}; filename=${encodeURIComponent(file.name)}` : disposition : void 0;
@@ -42,14 +81,14 @@ var AmazonStorageClient = class AmazonStorageClient {
42
81
  }
43
82
  getVersionDownloadURL(file, versionId) {
44
83
  const command = this.createGetObjectCommand(file, { VersionId: versionId });
45
- return (0, _aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn: 86400 });
84
+ return this.traced("presign", () => (0, _aws_sdk_s3_request_presigner.getSignedUrl)(this.client, command, { expiresIn: 86400 }), { "storage.presign.method": "GET" });
46
85
  }
47
86
  async getVersions(key) {
48
87
  const command = new _aws_sdk_client_s3.ListObjectVersionsCommand({
49
88
  Bucket: this.bucket,
50
89
  Prefix: key
51
90
  });
52
- const { Versions = [] } = await this.client.send(command);
91
+ const { Versions = [] } = await this.traced("list", () => this.client.send(command));
53
92
  return Versions.map((version) => ({
54
93
  id: version.VersionId || "",
55
94
  createdAt: version.LastModified || /* @__PURE__ */ new Date()
@@ -60,7 +99,7 @@ var AmazonStorageClient = class AmazonStorageClient {
60
99
  const cachedURL = await this.cache?.get(cacheKey);
61
100
  if (cachedURL) return cachedURL;
62
101
  const command = this.createGetObjectCommand(file);
63
- 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" });
64
103
  const cacheExpiration = Math.max(expiresIn - 60, 0);
65
104
  if (cacheExpiration) await this.cache?.set(cacheKey, url, cacheExpiration);
66
105
  return url;
@@ -72,17 +111,17 @@ var AmazonStorageClient = class AmazonStorageClient {
72
111
  ContentType: params.contentType,
73
112
  ContentLength: params.contentLength
74
113
  });
75
- 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" });
76
115
  }
77
116
  async getUpload(params, expiresIn = 5) {
78
117
  const { path } = params;
79
- 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, {
80
119
  Expires: expiresIn * 60,
81
120
  Bucket: this.bucket,
82
121
  Fields: { acl: this.acl },
83
122
  Conditions: [],
84
123
  Key: path
85
- });
124
+ }), { "storage.presign.method": "POST" });
86
125
  return {
87
126
  url,
88
127
  fields: Object.keys(values).map((key) => ({
@@ -100,14 +139,17 @@ var AmazonStorageClient = class AmazonStorageClient {
100
139
  ContentType: contentType
101
140
  };
102
141
  const command = new _aws_sdk_client_s3.PutObjectCommand(params);
103
- await this.client.send(command);
142
+ await this.traced("put", () => this.client.send(command), {
143
+ "storage.object.size": Body.length,
144
+ "storage.object.content_type": contentType
145
+ });
104
146
  }
105
147
  async delete(key) {
106
148
  const command = new _aws_sdk_client_s3.DeleteObjectCommand({
107
149
  Bucket: this.bucket,
108
150
  Key: key
109
151
  });
110
- await this.client.send(command);
152
+ await this.traced("delete", () => this.client.send(command));
111
153
  }
112
154
  };
113
155
  let AmazonCannedAccessControlList = /* @__PURE__ */ function(AmazonCannedAccessControlList) {
package/dist/aws.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"aws.cjs","names":["S3Client","GetObjectCommand","getSignedUrl","ListObjectVersionsCommand","PutObjectCommand","createPresignedPost","DeleteObjectCommand","parse"],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"sourcesContent":["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\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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"}
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
@@ -11,6 +11,8 @@ export declare class AmazonStorageClient implements StorageClient {
11
11
  readonly provider = StorageProvider.AWSS3;
12
12
  static create(options: AmazonStorageClientCreateOptions): AmazonStorageClient;
13
13
  constructor(client: S3Client, bucket: string, acl?: AmazonCannedAccessControlList, cache?: Cache);
14
+ /** A storage span on this bucket. */
15
+ private traced;
14
16
  private createGetObjectCommand;
15
17
  getVersionDownloadURL(file: File, versionId: string): Promise<string>;
16
18
  getVersions(key: string): Promise<DocumentVersion[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"aws.d.cts","names":[],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"mappings":";;;;;qBAoBa,+BAA+B;mBA+BzB;mBACA;mBACA;WACR,QAAQ;WAjCT,WAAQ,gBAAA;SAEV,OACN,SAAS,mCACP;EAyBH,YACkB,QAAQ,UACR,gBACA,MAAG,+BACX,QAAQ;UAGV;EAqBR,sBAAsB,MAAM,MAAM,oBAAoB;EAKhD,YAAY,cAAc,QAAQ;EAclC,eAAe,MAAM,MAAM,qBAAsB;EAmBjD,aACL,QAAQ,iBACR,qBACE;EAWG,UACL,QAAQ,iBACR,qBACE,QAAQ;EAwBL,OACL,aACA,eAAe,QACf,sBACE;EAeG,OAAO,cAAc;;oBAUhB;EACX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGS;EACT;EACA;EACA,MAAM;EACN;EACA;EACA;EACA;EACA,QAAQ;;;;qBClMI,UAAU"}
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
@@ -11,6 +11,8 @@ export declare class AmazonStorageClient implements StorageClient {
11
11
  readonly provider = StorageProvider.AWSS3;
12
12
  static create(options: AmazonStorageClientCreateOptions): AmazonStorageClient;
13
13
  constructor(client: S3Client, bucket: string, acl?: AmazonCannedAccessControlList, cache?: Cache);
14
+ /** A storage span on this bucket. */
15
+ private traced;
14
16
  private createGetObjectCommand;
15
17
  getVersionDownloadURL(file: File, versionId: string): Promise<string>;
16
18
  getVersions(key: string): Promise<DocumentVersion[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"aws.d.mts","names":[],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"mappings":";;;;;qBAoBa,+BAA+B;mBA+BzB;mBACA;mBACA;WACR,QAAQ;WAjCT,WAAQ,gBAAA;SAEV,OACN,SAAS,mCACP;EAyBH,YACkB,QAAQ,UACR,gBACA,MAAG,+BACX,QAAQ;UAGV;EAqBR,sBAAsB,MAAM,MAAM,oBAAoB;EAKhD,YAAY,cAAc,QAAQ;EAclC,eAAe,MAAM,MAAM,qBAAsB;EAmBjD,aACL,QAAQ,iBACR,qBACE;EAWG,UACL,QAAQ,iBACR,qBACE,QAAQ;EAwBL,OACL,aACA,eAAe,QACf,sBACE;EAeG,OAAO,cAAc;;oBAUhB;EACX;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGS;EACT;EACA;EACA,MAAM;EACN;EACA;EACA;EACA;EACA,QAAQ;;;;qBClMI,UAAU"}
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"}
package/dist/aws.mjs CHANGED
@@ -2,6 +2,37 @@ import { parse, t as s3Url_exports } from "./s3Url.mjs";
2
2
  import { DeleteObjectCommand, GetObjectCommand, ListObjectVersionsCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
3
3
  import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
4
4
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
5
+ import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
6
+ //#region src/telemetry.ts
7
+ /**
8
+ * A span per storage call, through the global OpenTelemetry tracer — a no-op
9
+ * without a registered provider.
10
+ *
11
+ * `storage.presign`, `storage.put`, `storage.delete` and `storage.list`, with
12
+ * the backend and the bucket. Never the object's key: keys often name a user.
13
+ *
14
+ * @module
15
+ */
16
+ /** Run `fn` inside a `storage.<operation>` span. */
17
+ async function traceStorage(operation, attributes, fn) {
18
+ const span = trace.getTracer("@geekmidas/storage").startSpan(`storage.${operation}`, {
19
+ kind: SpanKind.CLIENT,
20
+ attributes: {
21
+ "storage.operation": operation,
22
+ ...attributes
23
+ }
24
+ });
25
+ try {
26
+ return await fn();
27
+ } catch (error) {
28
+ if (error instanceof Error) span.recordException(error);
29
+ span.setStatus({ code: SpanStatusCode.ERROR });
30
+ throw error;
31
+ } finally {
32
+ span.end();
33
+ }
34
+ }
35
+ //#endregion
5
36
  //#region src/AmazonStorageClient.ts
6
37
  var AmazonStorageClient = class AmazonStorageClient {
7
38
  client;
@@ -28,6 +59,14 @@ var AmazonStorageClient = class AmazonStorageClient {
28
59
  this.acl = acl;
29
60
  this.cache = cache;
30
61
  }
62
+ /** A storage span on this bucket. */
63
+ traced(operation, fn, attributes = {}) {
64
+ return traceStorage(operation, {
65
+ "storage.system": "s3",
66
+ "storage.bucket": this.bucket,
67
+ ...attributes
68
+ }, fn);
69
+ }
31
70
  createGetObjectCommand(file, overrides) {
32
71
  const disposition = file.disposition ?? (file.name ? "attachment" : void 0);
33
72
  const ResponseContentDisposition = disposition ? file.name ? `${disposition}; filename=${encodeURIComponent(file.name)}` : disposition : void 0;
@@ -41,14 +80,14 @@ var AmazonStorageClient = class AmazonStorageClient {
41
80
  }
42
81
  getVersionDownloadURL(file, versionId) {
43
82
  const command = this.createGetObjectCommand(file, { VersionId: versionId });
44
- return getSignedUrl(this.client, command, { expiresIn: 86400 });
83
+ return this.traced("presign", () => getSignedUrl(this.client, command, { expiresIn: 86400 }), { "storage.presign.method": "GET" });
45
84
  }
46
85
  async getVersions(key) {
47
86
  const command = new ListObjectVersionsCommand({
48
87
  Bucket: this.bucket,
49
88
  Prefix: key
50
89
  });
51
- const { Versions = [] } = await this.client.send(command);
90
+ const { Versions = [] } = await this.traced("list", () => this.client.send(command));
52
91
  return Versions.map((version) => ({
53
92
  id: version.VersionId || "",
54
93
  createdAt: version.LastModified || /* @__PURE__ */ new Date()
@@ -59,7 +98,7 @@ var AmazonStorageClient = class AmazonStorageClient {
59
98
  const cachedURL = await this.cache?.get(cacheKey);
60
99
  if (cachedURL) return cachedURL;
61
100
  const command = this.createGetObjectCommand(file);
62
- const url = await getSignedUrl(this.client, command, { expiresIn });
101
+ const url = await this.traced("presign", () => getSignedUrl(this.client, command, { expiresIn }), { "storage.presign.method": "GET" });
63
102
  const cacheExpiration = Math.max(expiresIn - 60, 0);
64
103
  if (cacheExpiration) await this.cache?.set(cacheKey, url, cacheExpiration);
65
104
  return url;
@@ -71,17 +110,17 @@ var AmazonStorageClient = class AmazonStorageClient {
71
110
  ContentType: params.contentType,
72
111
  ContentLength: params.contentLength
73
112
  });
74
- return getSignedUrl(this.client, command, { expiresIn });
113
+ return this.traced("presign", () => getSignedUrl(this.client, command, { expiresIn }), { "storage.presign.method": "PUT" });
75
114
  }
76
115
  async getUpload(params, expiresIn = 5) {
77
116
  const { path } = params;
78
- const { fields: values, url } = await createPresignedPost(this.client, {
117
+ const { fields: values, url } = await this.traced("presign", () => createPresignedPost(this.client, {
79
118
  Expires: expiresIn * 60,
80
119
  Bucket: this.bucket,
81
120
  Fields: { acl: this.acl },
82
121
  Conditions: [],
83
122
  Key: path
84
- });
123
+ }), { "storage.presign.method": "POST" });
85
124
  return {
86
125
  url,
87
126
  fields: Object.keys(values).map((key) => ({
@@ -99,14 +138,17 @@ var AmazonStorageClient = class AmazonStorageClient {
99
138
  ContentType: contentType
100
139
  };
101
140
  const command = new PutObjectCommand(params);
102
- await this.client.send(command);
141
+ await this.traced("put", () => this.client.send(command), {
142
+ "storage.object.size": Body.length,
143
+ "storage.object.content_type": contentType
144
+ });
103
145
  }
104
146
  async delete(key) {
105
147
  const command = new DeleteObjectCommand({
106
148
  Bucket: this.bucket,
107
149
  Key: key
108
150
  });
109
- await this.client.send(command);
151
+ await this.traced("delete", () => this.client.send(command));
110
152
  }
111
153
  };
112
154
  let AmazonCannedAccessControlList = /* @__PURE__ */ function(AmazonCannedAccessControlList) {
package/dist/aws.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"aws.mjs","names":[],"sources":["../src/AmazonStorageClient.ts","../src/s3Driver.ts"],"sourcesContent":["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\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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"}
1
+ {"version":3,"file":"aws.mjs","names":[],"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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/storage",
3
- "version": "10.0.0-alpha.78",
3
+ "version": "10.0.0-alpha.80",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,13 +46,15 @@
46
46
  "files": [
47
47
  "dist"
48
48
  ],
49
- "dependencies": {},
49
+ "dependencies": {
50
+ "@opentelemetry/api": "~1.9.1"
51
+ },
50
52
  "peerDependencies": {
51
53
  "zod": ">=4.6.5",
52
54
  "@aws-sdk/client-s3": ">=3.1136.0",
53
55
  "@aws-sdk/s3-presigned-post": ">=3.1136.0",
54
56
  "@aws-sdk/s3-request-presigner": ">=3.1136.0",
55
- "@geekmidas/cache": "^10.0.0-alpha.78"
57
+ "@geekmidas/cache": "^10.0.0-alpha.80"
56
58
  },
57
59
  "peerDependenciesMeta": {
58
60
  "@geekmidas/cache": {
@@ -72,7 +74,8 @@
72
74
  }
73
75
  },
74
76
  "devDependencies": {
75
- "@geekmidas/cache": "^10.0.0-alpha.78"
77
+ "@opentelemetry/sdk-trace-node": "~2.11.0",
78
+ "@geekmidas/cache": "^10.0.0-alpha.80"
76
79
  },
77
80
  "scripts": {
78
81
  "build": "tsdown --config ../../tsdown.config.ts --filter ."