@geekmidas/storage 10.0.0-alpha.11 → 10.0.0-alpha.111
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/README.md +49 -13
- package/dist/aws.cjs +102 -63
- package/dist/aws.cjs.map +1 -1
- package/dist/aws.d.cts +9 -10
- package/dist/aws.d.cts.map +1 -1
- package/dist/aws.d.mts +9 -10
- package/dist/aws.d.mts.map +1 -1
- package/dist/aws.mjs +87 -49
- package/dist/aws.mjs.map +1 -1
- package/dist/{errors-D5UnAd-X.mjs → errors-BB8jdHif.cjs} +83 -5
- package/dist/errors-BB8jdHif.cjs.map +1 -0
- package/dist/{errors-Bce0CJ11.cjs → errors-COXs1iNg.mjs} +42 -35
- package/dist/errors-COXs1iNg.mjs.map +1 -0
- package/dist/index.cjs +19 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -11
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +32 -11
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +17 -4
- package/dist/index.mjs.map +1 -1
- package/dist/{registry-DBsAUTT3.d.cts → registry-ClEvHGpM.d.cts} +3 -6
- package/dist/registry-ClEvHGpM.d.cts.map +1 -0
- package/dist/{registry-DEqSHjx-.d.mts → registry-ClEvHGpM.d.mts} +3 -6
- package/dist/registry-ClEvHGpM.d.mts.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/s3Url.cjs +108 -4
- package/dist/s3Url.cjs.map +1 -0
- package/dist/s3Url.d.cts +52 -2
- package/dist/s3Url.d.cts.map +1 -0
- package/dist/s3Url.d.mts +52 -2
- package/dist/s3Url.d.mts.map +1 -0
- package/dist/s3Url.mjs +89 -3
- package/dist/s3Url.mjs.map +1 -0
- package/package.json +10 -5
- package/dist/chunk-B9dir_RE.mjs +0 -11
- package/dist/errors-Bce0CJ11.cjs.map +0 -1
- package/dist/errors-D5UnAd-X.mjs.map +0 -1
- package/dist/registry-DBsAUTT3.d.cts.map +0 -1
- package/dist/registry-DEqSHjx-.d.mts.map +0 -1
- package/dist/s3Url-BfztIltX.d.cts +0 -39
- package/dist/s3Url-BfztIltX.d.cts.map +0 -1
- package/dist/s3Url-Bo32UTTA.cjs +0 -103
- package/dist/s3Url-Bo32UTTA.cjs.map +0 -1
- package/dist/s3Url-C8FGPNrG.mjs +0 -52
- package/dist/s3Url-C8FGPNrG.mjs.map +0 -1
- package/dist/s3Url-COpMIgF9.d.mts +0 -42
- package/dist/s3Url-COpMIgF9.d.mts.map +0 -1
package/dist/aws.mjs
CHANGED
|
@@ -1,42 +1,72 @@
|
|
|
1
|
-
import "./
|
|
2
|
-
import { parse, s3Url_exports } from "./s3Url-C8FGPNrG.mjs";
|
|
1
|
+
import { parse, t as s3Url_exports } from "./s3Url.mjs";
|
|
3
2
|
import { DeleteObjectCommand, GetObjectCommand, ListObjectVersionsCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
4
3
|
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
|
|
5
4
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
6
|
-
|
|
7
|
-
//#region src/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
}
|
|
15
35
|
//#endregion
|
|
16
36
|
//#region src/AmazonStorageClient.ts
|
|
17
37
|
var AmazonStorageClient = class AmazonStorageClient {
|
|
18
|
-
|
|
38
|
+
client;
|
|
39
|
+
bucket;
|
|
40
|
+
acl;
|
|
41
|
+
cache;
|
|
42
|
+
provider = "geekimdas.toolbox.storage.aws.s3";
|
|
19
43
|
static create(options) {
|
|
20
44
|
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
45
|
const client = new S3Client({
|
|
27
46
|
region,
|
|
28
|
-
credentials
|
|
47
|
+
credentials: accessKeyId && secretAccessKey ? {
|
|
48
|
+
accessKeyId,
|
|
49
|
+
secretAccessKey
|
|
50
|
+
} : void 0,
|
|
29
51
|
endpoint,
|
|
30
52
|
forcePathStyle
|
|
31
53
|
});
|
|
32
54
|
return new AmazonStorageClient(client, bucket, acl, cache);
|
|
33
55
|
}
|
|
34
|
-
constructor(client, bucket, acl =
|
|
56
|
+
constructor(client, bucket, acl = "authenticated-read", cache) {
|
|
35
57
|
this.client = client;
|
|
36
58
|
this.bucket = bucket;
|
|
37
59
|
this.acl = acl;
|
|
38
60
|
this.cache = cache;
|
|
39
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
|
+
}
|
|
40
70
|
createGetObjectCommand(file, overrides) {
|
|
41
71
|
const disposition = file.disposition ?? (file.name ? "attachment" : void 0);
|
|
42
72
|
const ResponseContentDisposition = disposition ? file.name ? `${disposition}; filename=${encodeURIComponent(file.name)}` : disposition : void 0;
|
|
@@ -50,55 +80,53 @@ var AmazonStorageClient = class AmazonStorageClient {
|
|
|
50
80
|
}
|
|
51
81
|
getVersionDownloadURL(file, versionId) {
|
|
52
82
|
const command = this.createGetObjectCommand(file, { VersionId: versionId });
|
|
53
|
-
return getSignedUrl(this.client, command, { expiresIn:
|
|
83
|
+
return this.traced("presign", () => getSignedUrl(this.client, command, { expiresIn: 86400 }), { "storage.presign.method": "GET" });
|
|
54
84
|
}
|
|
55
85
|
async getVersions(key) {
|
|
56
86
|
const command = new ListObjectVersionsCommand({
|
|
57
87
|
Bucket: this.bucket,
|
|
58
88
|
Prefix: key
|
|
59
89
|
});
|
|
60
|
-
const { Versions = [] } = await this.client.send(command);
|
|
90
|
+
const { Versions = [] } = await this.traced("list", () => this.client.send(command));
|
|
61
91
|
return Versions.map((version) => ({
|
|
62
92
|
id: version.VersionId || "",
|
|
63
93
|
createdAt: version.LastModified || /* @__PURE__ */ new Date()
|
|
64
94
|
}));
|
|
65
95
|
}
|
|
66
|
-
async getDownloadURL(file, expiresIn =
|
|
96
|
+
async getDownloadURL(file, expiresIn = 3600) {
|
|
67
97
|
const cacheKey = `download-url:${file.path}`;
|
|
68
98
|
const cachedURL = await this.cache?.get(cacheKey);
|
|
69
99
|
if (cachedURL) return cachedURL;
|
|
70
100
|
const command = this.createGetObjectCommand(file);
|
|
71
|
-
const url = await getSignedUrl(this.client, command, { expiresIn });
|
|
101
|
+
const url = await this.traced("presign", () => getSignedUrl(this.client, command, { expiresIn }), { "storage.presign.method": "GET" });
|
|
72
102
|
const cacheExpiration = Math.max(expiresIn - 60, 0);
|
|
73
103
|
if (cacheExpiration) await this.cache?.set(cacheKey, url, cacheExpiration);
|
|
74
104
|
return url;
|
|
75
105
|
}
|
|
76
|
-
async getUploadURL(params, expiresIn =
|
|
106
|
+
async getUploadURL(params, expiresIn = 3600) {
|
|
77
107
|
const command = new PutObjectCommand({
|
|
78
108
|
Bucket: this.bucket,
|
|
79
109
|
Key: params.path,
|
|
80
110
|
ContentType: params.contentType,
|
|
81
111
|
ContentLength: params.contentLength
|
|
82
112
|
});
|
|
83
|
-
return getSignedUrl(this.client, command, { expiresIn });
|
|
113
|
+
return this.traced("presign", () => getSignedUrl(this.client, command, { expiresIn }), { "storage.presign.method": "PUT" });
|
|
84
114
|
}
|
|
85
115
|
async getUpload(params, expiresIn = 5) {
|
|
86
116
|
const { path } = params;
|
|
87
|
-
const { fields: values, url } = await createPresignedPost(this.client, {
|
|
117
|
+
const { fields: values, url } = await this.traced("presign", () => createPresignedPost(this.client, {
|
|
88
118
|
Expires: expiresIn * 60,
|
|
89
119
|
Bucket: this.bucket,
|
|
90
120
|
Fields: { acl: this.acl },
|
|
91
121
|
Conditions: [],
|
|
92
122
|
Key: path
|
|
93
|
-
});
|
|
94
|
-
const keys = Object.keys(values);
|
|
95
|
-
const fields = keys.map((key) => ({
|
|
96
|
-
key,
|
|
97
|
-
value: values[key] || ""
|
|
98
|
-
}));
|
|
123
|
+
}), { "storage.presign.method": "POST" });
|
|
99
124
|
return {
|
|
100
125
|
url,
|
|
101
|
-
fields
|
|
126
|
+
fields: Object.keys(values).map((key) => ({
|
|
127
|
+
key,
|
|
128
|
+
value: values[key] || ""
|
|
129
|
+
}))
|
|
102
130
|
};
|
|
103
131
|
}
|
|
104
132
|
async upload(key, data, contentType) {
|
|
@@ -110,43 +138,53 @@ var AmazonStorageClient = class AmazonStorageClient {
|
|
|
110
138
|
ContentType: contentType
|
|
111
139
|
};
|
|
112
140
|
const command = new PutObjectCommand(params);
|
|
113
|
-
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
|
+
});
|
|
114
145
|
}
|
|
115
146
|
async delete(key) {
|
|
116
147
|
const command = new DeleteObjectCommand({
|
|
117
148
|
Bucket: this.bucket,
|
|
118
149
|
Key: key
|
|
119
150
|
});
|
|
120
|
-
await this.client.send(command);
|
|
151
|
+
await this.traced("delete", () => this.client.send(command));
|
|
121
152
|
}
|
|
122
153
|
};
|
|
123
|
-
let AmazonCannedAccessControlList = /* @__PURE__ */ function(AmazonCannedAccessControlList
|
|
124
|
-
AmazonCannedAccessControlList
|
|
125
|
-
AmazonCannedAccessControlList
|
|
126
|
-
AmazonCannedAccessControlList
|
|
127
|
-
AmazonCannedAccessControlList
|
|
128
|
-
AmazonCannedAccessControlList
|
|
129
|
-
AmazonCannedAccessControlList
|
|
130
|
-
AmazonCannedAccessControlList
|
|
131
|
-
AmazonCannedAccessControlList
|
|
132
|
-
return AmazonCannedAccessControlList
|
|
154
|
+
let AmazonCannedAccessControlList = /* @__PURE__ */ function(AmazonCannedAccessControlList) {
|
|
155
|
+
AmazonCannedAccessControlList["AuthenticatedRead"] = "authenticated-read";
|
|
156
|
+
AmazonCannedAccessControlList["Private"] = "private";
|
|
157
|
+
AmazonCannedAccessControlList["PublicRead"] = "public-read";
|
|
158
|
+
AmazonCannedAccessControlList["PublicReadWrite"] = "public-read-write";
|
|
159
|
+
AmazonCannedAccessControlList["AwsExecRead"] = "aws-exec-read";
|
|
160
|
+
AmazonCannedAccessControlList["BucketOwnerRead"] = "bucket-owner-read";
|
|
161
|
+
AmazonCannedAccessControlList["BucketOwnerFullControl"] = "bucket-owner-full-control";
|
|
162
|
+
AmazonCannedAccessControlList["LogDeliveryWrite"] = "log-delivery-write";
|
|
163
|
+
return AmazonCannedAccessControlList;
|
|
133
164
|
}({});
|
|
134
|
-
|
|
135
165
|
//#endregion
|
|
136
166
|
//#region src/s3Driver.ts
|
|
167
|
+
/**
|
|
168
|
+
* The S3 driver — `s3://` URLs to an {@link AmazonStorageClient}.
|
|
169
|
+
*
|
|
170
|
+
* Registered by whoever assembles the application, so neutral code can build a
|
|
171
|
+
* client from a URL without importing this file or the AWS SDK behind it.
|
|
172
|
+
*/
|
|
137
173
|
const s3Driver = {
|
|
138
174
|
scheme: "s3:",
|
|
139
175
|
create(url) {
|
|
140
|
-
const { bucket, region, endpoint, forcePathStyle } = parse(url);
|
|
176
|
+
const { bucket, region, endpoint, forcePathStyle, accessKeyId, secretAccessKey } = parse(url);
|
|
141
177
|
return AmazonStorageClient.create({
|
|
142
178
|
bucket,
|
|
143
179
|
region,
|
|
144
180
|
endpoint,
|
|
145
|
-
forcePathStyle
|
|
181
|
+
forcePathStyle,
|
|
182
|
+
accessKeyId,
|
|
183
|
+
secretAccessKey
|
|
146
184
|
});
|
|
147
185
|
}
|
|
148
186
|
};
|
|
149
|
-
|
|
150
187
|
//#endregion
|
|
151
188
|
export { AmazonCannedAccessControlList, AmazonStorageClient, s3Driver, s3Url_exports as s3Url };
|
|
189
|
+
|
|
152
190
|
//# sourceMappingURL=aws.mjs.map
|
package/dist/aws.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aws.mjs","names":["options: AmazonStorageClientCreateOptions","client: S3Client","bucket: string","cache?: Cache","file: File","overrides?: { VersionId?: string }","versionId: string","key: string","params: GetUploadParams","data: string | Buffer","contentType: string","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.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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"}
|
|
@@ -13,14 +13,37 @@
|
|
|
13
13
|
*/
|
|
14
14
|
/** Base for anything wrong with a storage URL, so callers may catch broadly. */
|
|
15
15
|
var StorageUrlError = class extends Error {
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The URL that could not be used, with any credentials it carried replaced
|
|
18
|
+
* by `REDACTED` — an error is logged, and a key in its userinfo would be
|
|
19
|
+
* logged with it.
|
|
20
|
+
*/
|
|
17
21
|
url;
|
|
18
22
|
constructor(url, message) {
|
|
19
23
|
super(message);
|
|
20
|
-
this.url = url;
|
|
24
|
+
this.url = redactStorageUrl(url);
|
|
21
25
|
this.name = new.target.name;
|
|
22
26
|
}
|
|
23
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Replace a URL's userinfo with `REDACTED`, so `s3://KEY:SECRET@uploads` can be
|
|
30
|
+
* logged as `s3://REDACTED@uploads`. A URL without userinfo comes back as is.
|
|
31
|
+
*
|
|
32
|
+
* A string the URL parser rejects is still redacted, up to its last `@`: an
|
|
33
|
+
* unencoded `/` in a secret is the commonest way to write a malformed one, and
|
|
34
|
+
* that string is exactly the one that ends up in an error.
|
|
35
|
+
*/
|
|
36
|
+
function redactStorageUrl(url) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = new URL(url);
|
|
39
|
+
if (!parsed.username && !parsed.password) return url;
|
|
40
|
+
parsed.username = "REDACTED";
|
|
41
|
+
parsed.password = "";
|
|
42
|
+
return parsed.toString();
|
|
43
|
+
} catch {
|
|
44
|
+
return url.replace(/^([a-z][a-z0-9+.-]*:\/\/).*@/i, "$1REDACTED@");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
24
47
|
/** The string is not a URL at all. */
|
|
25
48
|
var MalformedStorageUrl = class extends StorageUrlError {
|
|
26
49
|
constructor(url) {
|
|
@@ -56,6 +79,20 @@ var MissingStorageBucket = class extends StorageUrlError {
|
|
|
56
79
|
}
|
|
57
80
|
};
|
|
58
81
|
/**
|
|
82
|
+
* The URL's userinfo carries one half of a key pair: an access key with no
|
|
83
|
+
* secret, or a secret with no access key. Either both are in the URL, or
|
|
84
|
+
* neither is and the provider's default credential chain applies — half a pair
|
|
85
|
+
* is never silently completed from the environment.
|
|
86
|
+
*/
|
|
87
|
+
var IncompleteStorageCredentials = class extends StorageUrlError {
|
|
88
|
+
/** Which half is missing: `'accessKeyId'` or `'secretAccessKey'`. */
|
|
89
|
+
missing;
|
|
90
|
+
constructor(url, missing) {
|
|
91
|
+
super(url, "Storage URL credentials need both halves: write them as KEY:SECRET@bucket, or leave them out to use the default credential chain");
|
|
92
|
+
this.missing = missing;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
59
96
|
* No driver is registered for the URL's scheme.
|
|
60
97
|
*
|
|
61
98
|
* Usually means the build pinned a driver for a different target, or an entry
|
|
@@ -73,7 +110,48 @@ var UnregisteredStorageScheme = class extends StorageUrlError {
|
|
|
73
110
|
this.registered = registered;
|
|
74
111
|
}
|
|
75
112
|
};
|
|
76
|
-
|
|
77
113
|
//#endregion
|
|
78
|
-
|
|
79
|
-
|
|
114
|
+
Object.defineProperty(exports, "IncompleteStorageCredentials", {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function() {
|
|
117
|
+
return IncompleteStorageCredentials;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
Object.defineProperty(exports, "MalformedStorageUrl", {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
get: function() {
|
|
123
|
+
return MalformedStorageUrl;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
Object.defineProperty(exports, "MissingStorageBucket", {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
get: function() {
|
|
129
|
+
return MissingStorageBucket;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
Object.defineProperty(exports, "StorageUrlError", {
|
|
133
|
+
enumerable: true,
|
|
134
|
+
get: function() {
|
|
135
|
+
return StorageUrlError;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
Object.defineProperty(exports, "UnexpectedStorageScheme", {
|
|
139
|
+
enumerable: true,
|
|
140
|
+
get: function() {
|
|
141
|
+
return UnexpectedStorageScheme;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
Object.defineProperty(exports, "UnregisteredStorageScheme", {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
get: function() {
|
|
147
|
+
return UnregisteredStorageScheme;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
Object.defineProperty(exports, "redactStorageUrl", {
|
|
151
|
+
enumerable: true,
|
|
152
|
+
get: function() {
|
|
153
|
+
return redactStorageUrl;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
//# sourceMappingURL=errors-BB8jdHif.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-BB8jdHif.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Storage errors — shared by every implementation, not just S3.\n *\n * A `gs://` or Azure client hits the same three problems as the S3 one: the\n * string is not a URL, it is a URL for a different provider, or it addresses no\n * bucket. Each carries the provider-specific detail as a field, so the type is\n * common while the specifics stay accurate.\n *\n * Messages state the rule, which is constant, and never interpolate the value.\n * An interpolated message cannot be matched on, reads differently every time it\n * is thrown, and carries user input into every log line that touches it.\n */\n\n/** Base for anything wrong with a storage URL, so callers may catch broadly. */\nexport abstract class StorageUrlError extends Error {\n\t/**\n\t * The URL that could not be used, with any credentials it carried replaced\n\t * by `REDACTED` — an error is logged, and a key in its userinfo would be\n\t * logged with it.\n\t */\n\treadonly url: string;\n\n\tconstructor(url: string, message: string) {\n\t\tsuper(message);\n\t\tthis.url = redactStorageUrl(url);\n\t\tthis.name = new.target.name;\n\t}\n}\n\n/**\n * Replace a URL's userinfo with `REDACTED`, so `s3://KEY:SECRET@uploads` can be\n * logged as `s3://REDACTED@uploads`. A URL without userinfo comes back as is.\n *\n * A string the URL parser rejects is still redacted, up to its last `@`: an\n * unencoded `/` in a secret is the commonest way to write a malformed one, and\n * that string is exactly the one that ends up in an error.\n */\nexport function redactStorageUrl(url: string): string {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tif (!parsed.username && !parsed.password) return url;\n\t\tparsed.username = 'REDACTED';\n\t\tparsed.password = '';\n\t\treturn parsed.toString();\n\t} catch {\n\t\treturn url.replace(/^([a-z][a-z0-9+.-]*:\\/\\/).*@/i, '$1REDACTED@');\n\t}\n}\n\n/** The string is not a URL at all. */\nexport class MalformedStorageUrl extends StorageUrlError {\n\tconstructor(url: string) {\n\t\tsuper(url, 'Not a valid URL');\n\t}\n}\n\n/**\n * A URL, but for a different provider — a `gs://` URL reaching the S3 client,\n * or the reverse. The usual cause is a construct resolving against the wrong\n * target, which is worth distinguishing from a malformed string.\n */\nexport class UnexpectedStorageScheme extends StorageUrlError {\n\t/** The scheme this client handles, e.g. `'s3:'`. */\n\treadonly expected: string;\n\t/** The scheme the URL actually carried, e.g. `'gs:'`. */\n\treadonly actual: string;\n\n\tconstructor(url: string, expected: string, actual: string) {\n\t\tsuper(url, 'Storage URL is for a different provider');\n\t\tthis.expected = expected;\n\t\tthis.actual = actual;\n\t}\n}\n\n/**\n * A well-formed URL that addresses no bucket.\n *\n * \"Bucket\" is the term S3 and GCS share; Azure calls the same thing a\n * container. One error covers all three — the word in the message is the\n * common one rather than a new abstraction nobody uses.\n */\nexport class MissingStorageBucket extends StorageUrlError {\n\tconstructor(url: string) {\n\t\tsuper(url, 'Storage URL must address a bucket');\n\t}\n}\n\n/**\n * The URL's userinfo carries one half of a key pair: an access key with no\n * secret, or a secret with no access key. Either both are in the URL, or\n * neither is and the provider's default credential chain applies — half a pair\n * is never silently completed from the environment.\n */\nexport class IncompleteStorageCredentials extends StorageUrlError {\n\t/** Which half is missing: `'accessKeyId'` or `'secretAccessKey'`. */\n\treadonly missing: 'accessKeyId' | 'secretAccessKey';\n\n\tconstructor(url: string, missing: 'accessKeyId' | 'secretAccessKey') {\n\t\tsuper(\n\t\t\turl,\n\t\t\t'Storage URL credentials need both halves: write them as KEY:SECRET@bucket, or leave them out to use the default credential chain',\n\t\t);\n\t\tthis.missing = missing;\n\t}\n}\n\n/**\n * No driver is registered for the URL's scheme.\n *\n * Usually means the build pinned a driver for a different target, or an entry\n * point registered nothing at all — so the registered schemes are carried\n * alongside, since \"what *is* available\" is the actionable half.\n */\nexport class UnregisteredStorageScheme extends StorageUrlError {\n\t/** The scheme that had no driver, e.g. `'gs:'`. */\n\treadonly scheme: string;\n\t/** What is registered, so the caller can see what was pinned instead. */\n\treadonly registered: readonly string[];\n\n\tconstructor(url: string, scheme: string, registered: readonly string[]) {\n\t\tsuper(url, 'No storage driver is registered for this URL scheme');\n\t\tthis.scheme = scheme;\n\t\tthis.registered = registered;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,IAAsB,kBAAtB,cAA8C,MAAM;;;;;;CAMnD;CAEA,YAAY,KAAa,SAAiB;EACzC,MAAM,OAAO;EACb,KAAK,MAAM,iBAAiB,GAAG;EAC/B,KAAK,OAAO,WAAW;CACxB;AACD;;;;;;;;;AAUA,SAAgB,iBAAiB,KAAqB;CACrD,IAAI;EACH,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,IAAI,CAAC,OAAO,YAAY,CAAC,OAAO,UAAU,OAAO;EACjD,OAAO,WAAW;EAClB,OAAO,WAAW;EAClB,OAAO,OAAO,SAAS;CACxB,QAAQ;EACP,OAAO,IAAI,QAAQ,iCAAiC,aAAa;CAClE;AACD;;AAGA,IAAa,sBAAb,cAAyC,gBAAgB;CACxD,YAAY,KAAa;EACxB,MAAM,KAAK,iBAAiB;CAC7B;AACD;;;;;;AAOA,IAAa,0BAAb,cAA6C,gBAAgB;;CAE5D;;CAEA;CAEA,YAAY,KAAa,UAAkB,QAAgB;EAC1D,MAAM,KAAK,yCAAyC;EACpD,KAAK,WAAW;EAChB,KAAK,SAAS;CACf;AACD;;;;;;;;AASA,IAAa,uBAAb,cAA0C,gBAAgB;CACzD,YAAY,KAAa;EACxB,MAAM,KAAK,mCAAmC;CAC/C;AACD;;;;;;;AAQA,IAAa,+BAAb,cAAkD,gBAAgB;;CAEjE;CAEA,YAAY,KAAa,SAA4C;EACpE,MACC,KACA,kIACD;EACA,KAAK,UAAU;CAChB;AACD;;;;;;;;AASA,IAAa,4BAAb,cAA+C,gBAAgB;;CAE9D;;CAEA;CAEA,YAAY,KAAa,QAAgB,YAA+B;EACvE,MAAM,KAAK,qDAAqD;EAChE,KAAK,SAAS;EACd,KAAK,aAAa;CACnB;AACD"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
//#region src/errors.ts
|
|
3
2
|
/**
|
|
4
3
|
* Storage errors — shared by every implementation, not just S3.
|
|
@@ -14,14 +13,37 @@
|
|
|
14
13
|
*/
|
|
15
14
|
/** Base for anything wrong with a storage URL, so callers may catch broadly. */
|
|
16
15
|
var StorageUrlError = class extends Error {
|
|
17
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The URL that could not be used, with any credentials it carried replaced
|
|
18
|
+
* by `REDACTED` — an error is logged, and a key in its userinfo would be
|
|
19
|
+
* logged with it.
|
|
20
|
+
*/
|
|
18
21
|
url;
|
|
19
22
|
constructor(url, message) {
|
|
20
23
|
super(message);
|
|
21
|
-
this.url = url;
|
|
24
|
+
this.url = redactStorageUrl(url);
|
|
22
25
|
this.name = new.target.name;
|
|
23
26
|
}
|
|
24
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* Replace a URL's userinfo with `REDACTED`, so `s3://KEY:SECRET@uploads` can be
|
|
30
|
+
* logged as `s3://REDACTED@uploads`. A URL without userinfo comes back as is.
|
|
31
|
+
*
|
|
32
|
+
* A string the URL parser rejects is still redacted, up to its last `@`: an
|
|
33
|
+
* unencoded `/` in a secret is the commonest way to write a malformed one, and
|
|
34
|
+
* that string is exactly the one that ends up in an error.
|
|
35
|
+
*/
|
|
36
|
+
function redactStorageUrl(url) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = new URL(url);
|
|
39
|
+
if (!parsed.username && !parsed.password) return url;
|
|
40
|
+
parsed.username = "REDACTED";
|
|
41
|
+
parsed.password = "";
|
|
42
|
+
return parsed.toString();
|
|
43
|
+
} catch {
|
|
44
|
+
return url.replace(/^([a-z][a-z0-9+.-]*:\/\/).*@/i, "$1REDACTED@");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
25
47
|
/** The string is not a URL at all. */
|
|
26
48
|
var MalformedStorageUrl = class extends StorageUrlError {
|
|
27
49
|
constructor(url) {
|
|
@@ -57,6 +79,20 @@ var MissingStorageBucket = class extends StorageUrlError {
|
|
|
57
79
|
}
|
|
58
80
|
};
|
|
59
81
|
/**
|
|
82
|
+
* The URL's userinfo carries one half of a key pair: an access key with no
|
|
83
|
+
* secret, or a secret with no access key. Either both are in the URL, or
|
|
84
|
+
* neither is and the provider's default credential chain applies — half a pair
|
|
85
|
+
* is never silently completed from the environment.
|
|
86
|
+
*/
|
|
87
|
+
var IncompleteStorageCredentials = class extends StorageUrlError {
|
|
88
|
+
/** Which half is missing: `'accessKeyId'` or `'secretAccessKey'`. */
|
|
89
|
+
missing;
|
|
90
|
+
constructor(url, missing) {
|
|
91
|
+
super(url, "Storage URL credentials need both halves: write them as KEY:SECRET@bucket, or leave them out to use the default credential chain");
|
|
92
|
+
this.missing = missing;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
60
96
|
* No driver is registered for the URL's scheme.
|
|
61
97
|
*
|
|
62
98
|
* Usually means the build pinned a driver for a different target, or an entry
|
|
@@ -74,36 +110,7 @@ var UnregisteredStorageScheme = class extends StorageUrlError {
|
|
|
74
110
|
this.registered = registered;
|
|
75
111
|
}
|
|
76
112
|
};
|
|
77
|
-
|
|
78
113
|
//#endregion
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return MalformedStorageUrl;
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
Object.defineProperty(exports, 'MissingStorageBucket', {
|
|
86
|
-
enumerable: true,
|
|
87
|
-
get: function () {
|
|
88
|
-
return MissingStorageBucket;
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
Object.defineProperty(exports, 'StorageUrlError', {
|
|
92
|
-
enumerable: true,
|
|
93
|
-
get: function () {
|
|
94
|
-
return StorageUrlError;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
Object.defineProperty(exports, 'UnexpectedStorageScheme', {
|
|
98
|
-
enumerable: true,
|
|
99
|
-
get: function () {
|
|
100
|
-
return UnexpectedStorageScheme;
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
Object.defineProperty(exports, 'UnregisteredStorageScheme', {
|
|
104
|
-
enumerable: true,
|
|
105
|
-
get: function () {
|
|
106
|
-
return UnregisteredStorageScheme;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
//# sourceMappingURL=errors-Bce0CJ11.cjs.map
|
|
114
|
+
export { UnexpectedStorageScheme as a, StorageUrlError as i, MalformedStorageUrl as n, UnregisteredStorageScheme as o, MissingStorageBucket as r, redactStorageUrl as s, IncompleteStorageCredentials as t };
|
|
115
|
+
|
|
116
|
+
//# sourceMappingURL=errors-COXs1iNg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-COXs1iNg.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Storage errors — shared by every implementation, not just S3.\n *\n * A `gs://` or Azure client hits the same three problems as the S3 one: the\n * string is not a URL, it is a URL for a different provider, or it addresses no\n * bucket. Each carries the provider-specific detail as a field, so the type is\n * common while the specifics stay accurate.\n *\n * Messages state the rule, which is constant, and never interpolate the value.\n * An interpolated message cannot be matched on, reads differently every time it\n * is thrown, and carries user input into every log line that touches it.\n */\n\n/** Base for anything wrong with a storage URL, so callers may catch broadly. */\nexport abstract class StorageUrlError extends Error {\n\t/**\n\t * The URL that could not be used, with any credentials it carried replaced\n\t * by `REDACTED` — an error is logged, and a key in its userinfo would be\n\t * logged with it.\n\t */\n\treadonly url: string;\n\n\tconstructor(url: string, message: string) {\n\t\tsuper(message);\n\t\tthis.url = redactStorageUrl(url);\n\t\tthis.name = new.target.name;\n\t}\n}\n\n/**\n * Replace a URL's userinfo with `REDACTED`, so `s3://KEY:SECRET@uploads` can be\n * logged as `s3://REDACTED@uploads`. A URL without userinfo comes back as is.\n *\n * A string the URL parser rejects is still redacted, up to its last `@`: an\n * unencoded `/` in a secret is the commonest way to write a malformed one, and\n * that string is exactly the one that ends up in an error.\n */\nexport function redactStorageUrl(url: string): string {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tif (!parsed.username && !parsed.password) return url;\n\t\tparsed.username = 'REDACTED';\n\t\tparsed.password = '';\n\t\treturn parsed.toString();\n\t} catch {\n\t\treturn url.replace(/^([a-z][a-z0-9+.-]*:\\/\\/).*@/i, '$1REDACTED@');\n\t}\n}\n\n/** The string is not a URL at all. */\nexport class MalformedStorageUrl extends StorageUrlError {\n\tconstructor(url: string) {\n\t\tsuper(url, 'Not a valid URL');\n\t}\n}\n\n/**\n * A URL, but for a different provider — a `gs://` URL reaching the S3 client,\n * or the reverse. The usual cause is a construct resolving against the wrong\n * target, which is worth distinguishing from a malformed string.\n */\nexport class UnexpectedStorageScheme extends StorageUrlError {\n\t/** The scheme this client handles, e.g. `'s3:'`. */\n\treadonly expected: string;\n\t/** The scheme the URL actually carried, e.g. `'gs:'`. */\n\treadonly actual: string;\n\n\tconstructor(url: string, expected: string, actual: string) {\n\t\tsuper(url, 'Storage URL is for a different provider');\n\t\tthis.expected = expected;\n\t\tthis.actual = actual;\n\t}\n}\n\n/**\n * A well-formed URL that addresses no bucket.\n *\n * \"Bucket\" is the term S3 and GCS share; Azure calls the same thing a\n * container. One error covers all three — the word in the message is the\n * common one rather than a new abstraction nobody uses.\n */\nexport class MissingStorageBucket extends StorageUrlError {\n\tconstructor(url: string) {\n\t\tsuper(url, 'Storage URL must address a bucket');\n\t}\n}\n\n/**\n * The URL's userinfo carries one half of a key pair: an access key with no\n * secret, or a secret with no access key. Either both are in the URL, or\n * neither is and the provider's default credential chain applies — half a pair\n * is never silently completed from the environment.\n */\nexport class IncompleteStorageCredentials extends StorageUrlError {\n\t/** Which half is missing: `'accessKeyId'` or `'secretAccessKey'`. */\n\treadonly missing: 'accessKeyId' | 'secretAccessKey';\n\n\tconstructor(url: string, missing: 'accessKeyId' | 'secretAccessKey') {\n\t\tsuper(\n\t\t\turl,\n\t\t\t'Storage URL credentials need both halves: write them as KEY:SECRET@bucket, or leave them out to use the default credential chain',\n\t\t);\n\t\tthis.missing = missing;\n\t}\n}\n\n/**\n * No driver is registered for the URL's scheme.\n *\n * Usually means the build pinned a driver for a different target, or an entry\n * point registered nothing at all — so the registered schemes are carried\n * alongside, since \"what *is* available\" is the actionable half.\n */\nexport class UnregisteredStorageScheme extends StorageUrlError {\n\t/** The scheme that had no driver, e.g. `'gs:'`. */\n\treadonly scheme: string;\n\t/** What is registered, so the caller can see what was pinned instead. */\n\treadonly registered: readonly string[];\n\n\tconstructor(url: string, scheme: string, registered: readonly string[]) {\n\t\tsuper(url, 'No storage driver is registered for this URL scheme');\n\t\tthis.scheme = scheme;\n\t\tthis.registered = registered;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,IAAsB,kBAAtB,cAA8C,MAAM;;;;;;CAMnD;CAEA,YAAY,KAAa,SAAiB;EACzC,MAAM,OAAO;EACb,KAAK,MAAM,iBAAiB,GAAG;EAC/B,KAAK,OAAO,WAAW;CACxB;AACD;;;;;;;;;AAUA,SAAgB,iBAAiB,KAAqB;CACrD,IAAI;EACH,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,IAAI,CAAC,OAAO,YAAY,CAAC,OAAO,UAAU,OAAO;EACjD,OAAO,WAAW;EAClB,OAAO,WAAW;EAClB,OAAO,OAAO,SAAS;CACxB,QAAQ;EACP,OAAO,IAAI,QAAQ,iCAAiC,aAAa;CAClE;AACD;;AAGA,IAAa,sBAAb,cAAyC,gBAAgB;CACxD,YAAY,KAAa;EACxB,MAAM,KAAK,iBAAiB;CAC7B;AACD;;;;;;AAOA,IAAa,0BAAb,cAA6C,gBAAgB;;CAE5D;;CAEA;CAEA,YAAY,KAAa,UAAkB,QAAgB;EAC1D,MAAM,KAAK,yCAAyC;EACpD,KAAK,WAAW;EAChB,KAAK,SAAS;CACf;AACD;;;;;;;;AASA,IAAa,uBAAb,cAA0C,gBAAgB;CACzD,YAAY,KAAa;EACxB,MAAM,KAAK,mCAAmC;CAC/C;AACD;;;;;;;AAQA,IAAa,+BAAb,cAAkD,gBAAgB;;CAEjE;CAEA,YAAY,KAAa,SAA4C;EACpE,MACC,KACA,kIACD;EACA,KAAK,UAAU;CAChB;AACD;;;;;;;;AASA,IAAa,4BAAb,cAA+C,gBAAgB;;CAE9D;;CAEA;CAEA,YAAY,KAAa,QAAgB,YAA+B;EACvE,MAAM,KAAK,qDAAqD;EAChE,KAAK,SAAS;EACd,KAAK,aAAa;CACnB;AACD"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_errors = require("./errors-BB8jdHif.cjs");
|
|
3
3
|
//#region src/registry.ts
|
|
4
|
+
/**
|
|
5
|
+
* Driver registry — how a storage URL becomes a client without the caller
|
|
6
|
+
* naming a cloud.
|
|
7
|
+
*
|
|
8
|
+
* A construct declares that it needs object storage and receives one URL. It
|
|
9
|
+
* must not import `AmazonStorageClient` to use it: that would name a provider in
|
|
10
|
+
* the neutral layer and drag the AWS SDK into an app that only ever talks to
|
|
11
|
+
* GCS. So the protocol selects the driver, and whoever assembles the
|
|
12
|
+
* application decides which drivers exist — a generated handler registering the
|
|
13
|
+
* one its target needs, or a dev entry registering several.
|
|
14
|
+
*
|
|
15
|
+
* Registration is explicit rather than an import side effect, because a
|
|
16
|
+
* side-effecting module is exactly what a bundler is entitled to drop.
|
|
17
|
+
*/
|
|
4
18
|
const drivers = /* @__PURE__ */ new Map();
|
|
5
19
|
/** Make a driver available to {@link createStorageClient}. Idempotent. */
|
|
6
20
|
function registerStorageDriver(driver) {
|
|
@@ -26,14 +40,16 @@ function schemeOf(url) {
|
|
|
26
40
|
const separator = url.indexOf(":");
|
|
27
41
|
return separator === -1 ? "" : url.slice(0, separator + 1);
|
|
28
42
|
}
|
|
29
|
-
|
|
30
43
|
//#endregion
|
|
44
|
+
exports.IncompleteStorageCredentials = require_errors.IncompleteStorageCredentials;
|
|
31
45
|
exports.MalformedStorageUrl = require_errors.MalformedStorageUrl;
|
|
32
46
|
exports.MissingStorageBucket = require_errors.MissingStorageBucket;
|
|
33
47
|
exports.StorageUrlError = require_errors.StorageUrlError;
|
|
34
48
|
exports.UnexpectedStorageScheme = require_errors.UnexpectedStorageScheme;
|
|
35
49
|
exports.UnregisteredStorageScheme = require_errors.UnregisteredStorageScheme;
|
|
36
50
|
exports.createStorageClient = createStorageClient;
|
|
51
|
+
exports.redactStorageUrl = require_errors.redactStorageUrl;
|
|
37
52
|
exports.registerStorageDriver = registerStorageDriver;
|
|
38
53
|
exports.registeredStorageSchemes = registeredStorageSchemes;
|
|
54
|
+
|
|
39
55
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["UnregisteredStorageScheme"],"sources":["../src/registry.ts"],"sourcesContent":["/**\n * Driver registry — how a storage URL becomes a client without the caller\n * naming a cloud.\n *\n * A construct declares that it needs object storage and receives one URL. It\n * must not import `AmazonStorageClient` to use it: that would name a provider in\n * the neutral layer and drag the AWS SDK into an app that only ever talks to\n * GCS. So the protocol selects the driver, and whoever assembles the\n * application decides which drivers exist — a generated handler registering the\n * one its target needs, or a dev entry registering several.\n *\n * Registration is explicit rather than an import side effect, because a\n * side-effecting module is exactly what a bundler is entitled to drop.\n */\n\nimport { UnregisteredStorageScheme } from './errors';\nimport type { StorageClient } from './StorageClient';\n\n/**\n * Builds a client from a URL it recognises.\n *\n * The driver owns parsing: the shape of an `s3://` URL is S3's business, and a\n * `gs://` driver shares nothing with it beyond this signature.\n */\nexport interface StorageDriver {\n\t/** The URL scheme this driver handles, including the colon — e.g. `'s3:'`. */\n\treadonly scheme: string;\n\tcreate(url: string): StorageClient;\n}\n\nconst drivers = new Map<string, StorageDriver>();\n\n/** Make a driver available to {@link createStorageClient}. Idempotent. */\nexport function registerStorageDriver(driver: StorageDriver): void {\n\tdrivers.set(driver.scheme, driver);\n}\n\n/** Which schemes are currently registered — the useful half of a failure. */\nexport function registeredStorageSchemes(): string[] {\n\treturn [...drivers.keys()].sort();\n}\n\n/**\n * Build a client for a URL.\n *\n * @throws {UnregisteredStorageScheme} when no driver handles the scheme, which\n * in practice means the build pinned a different one, or nothing registered.\n */\nexport function createStorageClient(url: string): StorageClient {\n\tconst scheme = schemeOf(url);\n\tconst driver = drivers.get(scheme);\n\n\tif (!driver) {\n\t\tthrow new UnregisteredStorageScheme(\n\t\t\turl,\n\t\t\tscheme,\n\t\t\tregisteredStorageSchemes(),\n\t\t);\n\t}\n\n\treturn driver.create(url);\n}\n\nfunction schemeOf(url: string): string {\n\tconst separator = url.indexOf(':');\n\treturn separator === -1 ? '' : url.slice(0, separator + 1);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,MAAM,0BAAU,IAAI,IAA2B;;AAG/C,SAAgB,sBAAsB,QAA6B;CAClE,QAAQ,IAAI,OAAO,QAAQ,MAAM;AAClC;;AAGA,SAAgB,2BAAqC;CACpD,OAAO,CAAC,GAAG,QAAQ,KAAK,CAAC,CAAC,CAAC,KAAK;AACjC;;;;;;;AAQA,SAAgB,oBAAoB,KAA4B;CAC/D,MAAM,SAAS,SAAS,GAAG;CAC3B,MAAM,SAAS,QAAQ,IAAI,MAAM;CAEjC,IAAI,CAAC,QACJ,MAAM,IAAIA,eAAAA,0BACT,KACA,QACA,yBAAyB,CAC1B;CAGD,OAAO,OAAO,OAAO,GAAG;AACzB;AAEA,SAAS,SAAS,KAAqB;CACtC,MAAM,YAAY,IAAI,QAAQ,GAAG;CACjC,OAAO,cAAc,KAAK,KAAK,IAAI,MAAM,GAAG,YAAY,CAAC;AAC1D"}
|