@hanzo/s3 0.6.3

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/lib/index.js ADDED
@@ -0,0 +1,450 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+ var __decorateClass = (decorators, target, key, kind) => {
31
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
32
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
33
+ if (decorator = decorators[i])
34
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
35
+ if (kind && result) __defProp(target, key, result);
36
+ return result;
37
+ };
38
+ var index_exports = {};
39
+ __export(index_exports, {
40
+ CONFIG_KIND: () => CONFIG_KIND,
41
+ S3Service: () => S3Service,
42
+ processConfigFromEnv: () => processConfigFromEnv
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+ var import_client_s3 = require("@aws-sdk/client-s3");
46
+ var import_lib_storage = require("@aws-sdk/lib-storage");
47
+ var import_node_http_handler = require("@smithy/node-http-handler");
48
+ var import_http = require("http");
49
+ var import_https = require("https");
50
+ var import_core = __toESM(require("@hanzo/core"));
51
+ var import_platform = require("@hanzo/platform");
52
+ var import_server_core = __toESM(require("@hanzo/server-core"));
53
+ var import_stream = require("stream");
54
+ var import_storage = require("@hanzo/storage");
55
+ const CONFIG_KIND = "s3";
56
+ class S3Service {
57
+ constructor(opt) {
58
+ this.opt = opt;
59
+ this.client = new import_client_s3.S3({
60
+ endpoint: opt.endpoint,
61
+ credentials: {
62
+ accessKeyId: opt.accessKey,
63
+ secretAccessKey: opt.secretKey
64
+ },
65
+ region: opt.region ?? "auto",
66
+ requestHandler: new import_node_http_handler.NodeHttpHandler({
67
+ connectionTimeout: 5e3,
68
+ socketTimeout: 12e4,
69
+ httpAgent: new import_http.Agent({ maxSockets: 500, keepAlive: true }),
70
+ httpsAgent: new import_https.Agent({ maxSockets: 500, keepAlive: true })
71
+ })
72
+ });
73
+ this.expireTime = parseInt(this.opt.expireTime ?? "168") * 3600;
74
+ }
75
+ static {
76
+ __name(this, "S3Service");
77
+ }
78
+ expireTime;
79
+ client;
80
+ async initialize(ctx, wsIds) {
81
+ }
82
+ /**
83
+ * @public
84
+ */
85
+ getBucketId(wsIds) {
86
+ return this.opt.rootBucket ?? (this.opt.bucketPrefix ?? "") + (0, import_server_core.getDataId)(wsIds);
87
+ }
88
+ getBucketFolder(wsIds) {
89
+ return (0, import_server_core.getDataId)(wsIds);
90
+ }
91
+ async close() {
92
+ }
93
+ async exists(ctx, wsIds) {
94
+ try {
95
+ const result = await this.client.headBucket({
96
+ Bucket: this.getBucketId(wsIds)
97
+ });
98
+ return result.$metadata.httpStatusCode === 200;
99
+ } catch (err) {
100
+ if (err.name === "400") {
101
+ return false;
102
+ }
103
+ }
104
+ return false;
105
+ }
106
+ async make(ctx, wsIds) {
107
+ try {
108
+ await this.client.createBucket({
109
+ Bucket: this.getBucketId(wsIds)
110
+ });
111
+ } catch (err) {
112
+ if (err.Code === "BucketAlreadyOwnedByYou") {
113
+ return;
114
+ }
115
+ ctx.error("error during create bucket", { err });
116
+ }
117
+ }
118
+ async listBuckets(ctx) {
119
+ try {
120
+ if (this.opt.rootBucket !== void 0) {
121
+ const info = /* @__PURE__ */ new Map();
122
+ let token;
123
+ while (true) {
124
+ const res = await this.client.listObjectsV2({
125
+ Bucket: this.opt.rootBucket,
126
+ Prefix: "",
127
+ Delimiter: "/",
128
+ ContinuationToken: token
129
+ });
130
+ for (const data of res.CommonPrefixes ?? []) {
131
+ const wsDataId = data.Prefix?.split("/")?.[0];
132
+ const wsIds = {
133
+ uuid: wsDataId,
134
+ dataId: wsDataId,
135
+ url: ""
136
+ };
137
+ if (wsDataId !== void 0 && !info.has(wsDataId)) {
138
+ info.set(wsDataId, {
139
+ name: wsDataId,
140
+ delete: /* @__PURE__ */ __name(async () => {
141
+ await this.delete(ctx, wsIds);
142
+ }, "delete"),
143
+ list: /* @__PURE__ */ __name(async () => await this.listStream(ctx, wsIds), "list")
144
+ });
145
+ }
146
+ }
147
+ if (res.IsTruncated === true) {
148
+ token = res.NextContinuationToken;
149
+ } else {
150
+ break;
151
+ }
152
+ }
153
+ return Array.from(info.values());
154
+ } else {
155
+ const productPostfix = this.getBucketFolder({
156
+ uuid: "",
157
+ dataId: "",
158
+ url: ""
159
+ });
160
+ const buckets = await this.client.listBuckets();
161
+ return (buckets.Buckets ?? []).filter((it) => it.Name !== void 0 && it.Name.endsWith(productPostfix)).map((it) => {
162
+ let name = it.Name ?? "";
163
+ name = name.slice(0, name.length - productPostfix.length);
164
+ const wsIds = {
165
+ uuid: name,
166
+ dataId: name,
167
+ url: ""
168
+ };
169
+ return {
170
+ name,
171
+ delete: /* @__PURE__ */ __name(async () => {
172
+ await this.delete(ctx, wsIds);
173
+ }, "delete"),
174
+ list: /* @__PURE__ */ __name(async () => await this.listStream(ctx, wsIds), "list")
175
+ };
176
+ });
177
+ }
178
+ } catch (err) {
179
+ if (err.Code === "NoSuchBucket") {
180
+ return [];
181
+ }
182
+ ctx.error("failed to list buckets", { rootBucket: this.opt.rootBucket });
183
+ console.error(err);
184
+ return [];
185
+ }
186
+ }
187
+ getDocumentKey(wsIds, name) {
188
+ return this.opt.rootBucket === void 0 ? name : `${this.getBucketFolder(wsIds)}/${name}`;
189
+ }
190
+ async remove(ctx, wsIds, objectNames) {
191
+ await this.client.deleteObjects({
192
+ Bucket: this.getBucketId(wsIds),
193
+ Delete: {
194
+ Objects: objectNames.map((it) => ({ Key: this.getDocumentKey(wsIds, it) }))
195
+ }
196
+ });
197
+ }
198
+ async delete(ctx, wsIds) {
199
+ try {
200
+ await (0, import_storage.removeAllObjects)(ctx, this, wsIds);
201
+ } catch (err) {
202
+ ctx.error("failed t oclean all objecrs", { error: err });
203
+ }
204
+ if (this.opt.rootBucket === void 0) {
205
+ await this.client.deleteBucket({
206
+ Bucket: this.getBucketId(wsIds)
207
+ });
208
+ }
209
+ }
210
+ stripPrefix(prefix, key) {
211
+ if (prefix !== void 0 && key.startsWith(prefix)) {
212
+ return key.slice(prefix.length);
213
+ }
214
+ return key;
215
+ }
216
+ rootPrefix(wsIds) {
217
+ return this.opt.rootBucket !== void 0 ? this.getBucketFolder(wsIds) + "/" : void 0;
218
+ }
219
+ async copy(sourceId, targetId, objectName) {
220
+ const copyOp = new import_client_s3.CopyObjectCommand({
221
+ Bucket: this.getBucketId(targetId),
222
+ Key: this.getDocumentKey(targetId, objectName),
223
+ CopySource: `${this.getBucketId(sourceId)}/${this.getDocumentKey(sourceId, objectName)}`
224
+ });
225
+ await this.client.send(copyOp);
226
+ }
227
+ async listStream(ctx, wsIds) {
228
+ let hasMore = true;
229
+ const buffer = [];
230
+ let token;
231
+ const rootPrefix = this.rootPrefix(wsIds);
232
+ return {
233
+ next: /* @__PURE__ */ __name(async () => {
234
+ try {
235
+ while (hasMore && buffer.length < 50) {
236
+ const res = await this.client.listObjectsV2({
237
+ Bucket: this.getBucketId(wsIds),
238
+ Prefix: rootPrefix ?? "",
239
+ ContinuationToken: token
240
+ });
241
+ if (res.IsTruncated === true) {
242
+ token = res.NextContinuationToken;
243
+ } else {
244
+ hasMore = false;
245
+ }
246
+ for (const data of res.Contents ?? []) {
247
+ const _id = this.stripPrefix(rootPrefix, data.Key ?? "");
248
+ buffer.push({
249
+ _id,
250
+ _class: import_core.default.class.Blob,
251
+ etag: data.ETag ?? "",
252
+ size: data.Size ?? 0,
253
+ provider: this.opt.name,
254
+ space: import_core.default.space.Configuration,
255
+ modifiedBy: import_core.default.account.ConfigUser,
256
+ modifiedOn: data.LastModified?.getTime() ?? 0
257
+ });
258
+ }
259
+ }
260
+ } catch (err) {
261
+ ctx.error("Failed to get list", { error: err, wsIds });
262
+ }
263
+ return buffer.splice(0, 50);
264
+ }, "next"),
265
+ close: /* @__PURE__ */ __name(async () => {
266
+ }, "close")
267
+ };
268
+ }
269
+ async stat(ctx, wsIds, objectName) {
270
+ try {
271
+ const result = await this.client.headObject({
272
+ Bucket: this.getBucketId(wsIds),
273
+ Key: this.getDocumentKey(wsIds, objectName)
274
+ });
275
+ const rootPrefix = this.rootPrefix(wsIds);
276
+ return {
277
+ provider: "",
278
+ _class: import_core.default.class.Blob,
279
+ _id: this.stripPrefix(rootPrefix, objectName),
280
+ contentType: result.ContentType ?? "",
281
+ size: result.ContentLength ?? 0,
282
+ etag: result.ETag ?? "",
283
+ space: import_core.default.space.Configuration,
284
+ modifiedBy: import_core.default.account.System,
285
+ modifiedOn: result.LastModified?.getTime() ?? 0,
286
+ version: result.VersionId ?? null
287
+ };
288
+ } catch (err) {
289
+ if (err?.$metadata?.httpStatusCode !== 404) {
290
+ ctx.warn("no object found", { error: err, objectName, wsIds });
291
+ }
292
+ }
293
+ }
294
+ async get(ctx, wsIds, objectName) {
295
+ return await this.doGet(ctx, wsIds, objectName);
296
+ }
297
+ async doGet(ctx, wsIds, objectName, range) {
298
+ try {
299
+ const res = await this.client.getObject({
300
+ Bucket: this.getBucketId(wsIds),
301
+ Key: this.getDocumentKey(wsIds, objectName),
302
+ Range: range
303
+ });
304
+ const stream = res.Body?.transformToWebStream();
305
+ if (stream !== void 0) {
306
+ return import_stream.Readable.fromWeb(stream);
307
+ } else {
308
+ const readable = new import_stream.Readable();
309
+ readable._read = () => {
310
+ };
311
+ readable.push(null);
312
+ return readable;
313
+ }
314
+ } catch (err) {
315
+ throw new import_server_core.NoSuchKeyError(`uuid=${wsIds.uuid} dataId=${wsIds.dataId} missing ${objectName}`, err);
316
+ }
317
+ }
318
+ put(ctx, wsIds, objectName, stream, contentType, size) {
319
+ if (size !== void 0 && size < 1024 * 1024 * 5) {
320
+ return ctx.with(
321
+ "simple-put",
322
+ {},
323
+ async () => {
324
+ const cmd = new import_client_s3.PutObjectCommand({
325
+ Bucket: this.getBucketId(wsIds),
326
+ Key: this.getDocumentKey(wsIds, objectName),
327
+ ContentType: contentType,
328
+ ContentLength: size,
329
+ Body: stream
330
+ });
331
+ const response = await this.client.send(cmd);
332
+ return {
333
+ etag: response.ETag ?? "",
334
+ versionId: response.VersionId ?? null
335
+ };
336
+ },
337
+ { size, objectName, wsIds }
338
+ );
339
+ } else {
340
+ return ctx.with(
341
+ "multipart-upload",
342
+ {},
343
+ async () => {
344
+ const uploadTask = new import_lib_storage.Upload({
345
+ client: this.client,
346
+ params: {
347
+ Bucket: this.getBucketId(wsIds),
348
+ Key: this.getDocumentKey(wsIds, objectName),
349
+ ContentType: contentType,
350
+ Body: stream
351
+ },
352
+ // (optional) concurrency configuration
353
+ // queueSize: 1,
354
+ // (optional) size of each part, in bytes, at least 5MB
355
+ partSize: 1024 * 1024 * 5,
356
+ leavePartsOnError: false
357
+ });
358
+ const output = await uploadTask.done();
359
+ return {
360
+ etag: output.ETag ?? "",
361
+ versionId: output.VersionId ?? null
362
+ };
363
+ },
364
+ { size, objectName, wsIds }
365
+ );
366
+ }
367
+ }
368
+ async read(ctx, wsIds, name) {
369
+ const data = await this.doGet(ctx, wsIds, name);
370
+ const chunks = [];
371
+ await new Promise((resolve, reject) => {
372
+ data.on("data", (chunk) => {
373
+ chunks.push(chunk);
374
+ });
375
+ data.on("end", () => {
376
+ data.destroy();
377
+ resolve(null);
378
+ });
379
+ data.on("error", (err) => {
380
+ data.destroy();
381
+ reject(err);
382
+ });
383
+ });
384
+ return chunks;
385
+ }
386
+ async partial(ctx, wsIds, objectName, offset, length) {
387
+ const range = length !== void 0 ? `bytes=${offset}-${offset + length}` : `bytes=${offset}-`;
388
+ return await this.doGet(ctx, wsIds, objectName, range);
389
+ }
390
+ async getUrl(ctx, wsIds, objectName) {
391
+ const filesUrl = (0, import_platform.getMetadata)(import_server_core.default.metadata.FilesUrl) ?? "";
392
+ return filesUrl.replaceAll(":workspace", (0, import_server_core.getDataId)(wsIds)).replaceAll(":blobId", objectName);
393
+ }
394
+ }
395
+ __decorateClass([
396
+ (0, import_core.withContext)("make")
397
+ ], S3Service.prototype, "make", 1);
398
+ __decorateClass([
399
+ (0, import_core.withContext)("remove")
400
+ ], S3Service.prototype, "remove", 1);
401
+ __decorateClass([
402
+ (0, import_core.withContext)("delete")
403
+ ], S3Service.prototype, "delete", 1);
404
+ __decorateClass([
405
+ (0, import_core.withContext)("listStream")
406
+ ], S3Service.prototype, "listStream", 1);
407
+ __decorateClass([
408
+ (0, import_core.withContext)("stat")
409
+ ], S3Service.prototype, "stat", 1);
410
+ __decorateClass([
411
+ (0, import_core.withContext)("get")
412
+ ], S3Service.prototype, "get", 1);
413
+ __decorateClass([
414
+ (0, import_core.withContext)("put")
415
+ ], S3Service.prototype, "put", 1);
416
+ __decorateClass([
417
+ (0, import_core.withContext)("read")
418
+ ], S3Service.prototype, "read", 1);
419
+ __decorateClass([
420
+ (0, import_core.withContext)("partial")
421
+ ], S3Service.prototype, "partial", 1);
422
+ __decorateClass([
423
+ (0, import_core.withContext)("getUrl")
424
+ ], S3Service.prototype, "getUrl", 1);
425
+ function processConfigFromEnv(storageConfig) {
426
+ const endpoint = process.env.S3_ENDPOINT;
427
+ if (endpoint === void 0) {
428
+ return "S3_ENDPOINT";
429
+ }
430
+ const accessKey = process.env.S3_ACCESS_KEY;
431
+ if (accessKey === void 0) {
432
+ return "S3_ACCESS_KEY";
433
+ }
434
+ const secretKey = process.env.S3_SECRET_KEY;
435
+ if (secretKey === void 0) {
436
+ return "S3_SECRET_KEY";
437
+ }
438
+ const config = {
439
+ kind: "s3",
440
+ name: "s3",
441
+ region: "auto",
442
+ endpoint,
443
+ accessKey,
444
+ secretKey
445
+ };
446
+ storageConfig.storages.push(config);
447
+ storageConfig.default = "s3";
448
+ }
449
+ __name(processConfigFromEnv, "processConfigFromEnv");
450
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["//\n// Copyright \u00A9 2024 Hardcore Engineering Inc.\n//\n// Licensed under the Eclipse Public License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License. You may\n// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\nimport { CopyObjectCommand, PutObjectCommand, S3 } from '@aws-sdk/client-s3'\nimport { Upload } from '@aws-sdk/lib-storage'\nimport { NodeHttpHandler } from '@smithy/node-http-handler'\nimport { Agent as HttpAgent } from 'http'\nimport { Agent as HttpsAgent } from 'https'\n\nimport core, {\n withContext,\n type WorkspaceIds,\n type Blob,\n type MeasureContext,\n type Ref,\n type WorkspaceDataId,\n type WorkspaceUuid\n} from '@hanzo/core'\nimport { getMetadata } from '@hanzo/platform'\nimport serverCore, {\n NoSuchKeyError,\n getDataId,\n type BlobStorageIterator,\n type ListBlobResult,\n type StorageAdapter,\n type StorageConfig,\n type StorageConfiguration,\n type UploadedObjectInfo\n} from '@hanzo/server-core'\nimport { Readable } from 'stream'\n\nimport { removeAllObjects, type BucketInfo } from '@hanzo/storage'\nimport type { ReadableStream } from 'stream/web'\n\nexport interface S3Config extends StorageConfig {\n kind: 's3'\n accessKey: string\n secretKey: string\n region?: string\n\n // If defined, all resources will be inside selected root bucket.\n rootBucket?: string\n\n // A prefix string to be added to a bucketId in case rootBucket not used\n bucketPrefix?: string\n\n // If not specified will be enabled\n allowPresign?: string\n // Expire time for presigned URIs\n expireTime?: string\n}\n\nexport const CONFIG_KIND = 's3'\n\n/**\n * @public\n */\nexport class S3Service implements StorageAdapter {\n expireTime: number\n client: S3\n constructor (readonly opt: S3Config) {\n this.client = new S3({\n endpoint: opt.endpoint,\n credentials: {\n accessKeyId: opt.accessKey,\n secretAccessKey: opt.secretKey\n },\n region: opt.region ?? 'auto',\n requestHandler: new NodeHttpHandler({\n connectionTimeout: 5000,\n socketTimeout: 120000,\n httpAgent: new HttpAgent({ maxSockets: 500, keepAlive: true }),\n httpsAgent: new HttpsAgent({ maxSockets: 500, keepAlive: true })\n })\n })\n\n this.expireTime = parseInt(this.opt.expireTime ?? '168') * 3600 // use 7 * 24 - hours as default value for expireF\n }\n\n async initialize (ctx: MeasureContext, wsIds: WorkspaceIds): Promise<void> {}\n\n /**\n * @public\n */\n getBucketId (wsIds: WorkspaceIds): string {\n return this.opt.rootBucket ?? (this.opt.bucketPrefix ?? '') + getDataId(wsIds)\n }\n\n getBucketFolder (wsIds: WorkspaceIds): string {\n return getDataId(wsIds)\n }\n\n async close (): Promise<void> {}\n\n async exists (ctx: MeasureContext, wsIds: WorkspaceIds): Promise<boolean> {\n try {\n const result = await this.client.headBucket({\n Bucket: this.getBucketId(wsIds)\n })\n return result.$metadata.httpStatusCode === 200\n } catch (err: any) {\n if (err.name === '400') {\n // No bucket exisrs\n return false\n }\n }\n // No API to check is bucket exists or not, so we need to call make and check if it already exists.\n return false\n }\n\n @withContext('make')\n async make (ctx: MeasureContext, wsIds: WorkspaceIds): Promise<void> {\n try {\n await this.client.createBucket({\n Bucket: this.getBucketId(wsIds)\n })\n } catch (err: any) {\n if (err.Code === 'BucketAlreadyOwnedByYou') {\n return\n }\n ctx.error('error during create bucket', { err })\n }\n }\n\n async listBuckets (ctx: MeasureContext): Promise<BucketInfo[]> {\n try {\n if (this.opt.rootBucket !== undefined) {\n const info = new Map<string, BucketInfo>()\n let token: string | undefined\n\n while (true) {\n const res = await this.client.listObjectsV2({\n Bucket: this.opt.rootBucket,\n Prefix: '',\n Delimiter: '/',\n ContinuationToken: token\n })\n for (const data of res.CommonPrefixes ?? []) {\n const wsDataId = data.Prefix?.split('/')?.[0] as WorkspaceDataId\n const wsIds = {\n uuid: wsDataId as unknown as WorkspaceUuid,\n dataId: wsDataId,\n url: ''\n }\n if (wsDataId !== undefined && !info.has(wsDataId)) {\n info.set(wsDataId, {\n name: wsDataId,\n delete: async () => {\n await this.delete(ctx, wsIds)\n },\n list: async () => await this.listStream(ctx, wsIds)\n })\n }\n }\n if (res.IsTruncated === true) {\n token = res.NextContinuationToken\n } else {\n break\n }\n }\n return Array.from(info.values())\n } else {\n const productPostfix = this.getBucketFolder({\n uuid: '' as WorkspaceUuid,\n dataId: '' as WorkspaceDataId,\n url: ''\n })\n const buckets = await this.client.listBuckets()\n return (buckets.Buckets ?? [])\n .filter((it) => it.Name !== undefined && it.Name.endsWith(productPostfix))\n .map((it) => {\n let name = (it.Name ?? '') as WorkspaceDataId\n name = name.slice(0, name.length - productPostfix.length) as WorkspaceDataId\n const wsIds = {\n uuid: name as unknown as WorkspaceUuid,\n dataId: name,\n url: ''\n }\n return {\n name,\n delete: async () => {\n await this.delete(ctx, wsIds)\n },\n list: async () => await this.listStream(ctx, wsIds)\n }\n })\n }\n } catch (err: any) {\n if (err.Code === 'NoSuchBucket') {\n return []\n }\n ctx.error('failed to list buckets', { rootBucket: this.opt.rootBucket })\n console.error(err)\n return []\n }\n }\n\n getDocumentKey (wsIds: WorkspaceIds, name: string): string {\n return this.opt.rootBucket === undefined ? name : `${this.getBucketFolder(wsIds)}/${name}`\n }\n\n @withContext('remove')\n async remove (ctx: MeasureContext, wsIds: WorkspaceIds, objectNames: string[]): Promise<void> {\n await this.client.deleteObjects({\n Bucket: this.getBucketId(wsIds),\n Delete: {\n Objects: objectNames.map((it) => ({ Key: this.getDocumentKey(wsIds, it) }))\n }\n })\n }\n\n @withContext('delete')\n async delete (ctx: MeasureContext, wsIds: WorkspaceIds): Promise<void> {\n try {\n await removeAllObjects(ctx, this, wsIds)\n } catch (err: any) {\n ctx.error('failed t oclean all objecrs', { error: err })\n }\n if (this.opt.rootBucket === undefined) {\n // We should also delete bucket\n await this.client.deleteBucket({\n Bucket: this.getBucketId(wsIds)\n })\n }\n }\n\n stripPrefix (prefix: string | undefined, key: string): string {\n if (prefix !== undefined && key.startsWith(prefix)) {\n return key.slice(prefix.length)\n }\n return key\n }\n\n rootPrefix (wsIds: WorkspaceIds): string | undefined {\n return this.opt.rootBucket !== undefined ? this.getBucketFolder(wsIds) + '/' : undefined\n }\n\n async copy (sourceId: WorkspaceIds, targetId: WorkspaceIds, objectName: string): Promise<void> {\n const copyOp = new CopyObjectCommand({\n Bucket: this.getBucketId(targetId),\n Key: this.getDocumentKey(targetId, objectName),\n CopySource: `${this.getBucketId(sourceId)}/${this.getDocumentKey(sourceId, objectName)}`\n })\n await this.client.send(copyOp)\n }\n\n @withContext('listStream')\n async listStream (ctx: MeasureContext, wsIds: WorkspaceIds): Promise<BlobStorageIterator> {\n let hasMore = true\n const buffer: ListBlobResult[] = []\n let token: string | undefined\n\n const rootPrefix = this.rootPrefix(wsIds)\n return {\n next: async (): Promise<ListBlobResult[]> => {\n try {\n while (hasMore && buffer.length < 50) {\n const res = await this.client.listObjectsV2({\n Bucket: this.getBucketId(wsIds),\n Prefix: rootPrefix ?? '',\n ContinuationToken: token\n })\n if (res.IsTruncated === true) {\n token = res.NextContinuationToken\n } else {\n hasMore = false\n }\n\n for (const data of res.Contents ?? []) {\n const _id = this.stripPrefix(rootPrefix, data.Key ?? '')\n buffer.push({\n _id: _id as Ref<Blob>,\n _class: core.class.Blob,\n etag: data.ETag ?? '',\n size: data.Size ?? 0,\n provider: this.opt.name,\n space: core.space.Configuration,\n modifiedBy: core.account.ConfigUser,\n modifiedOn: data.LastModified?.getTime() ?? 0\n })\n }\n }\n } catch (err: any) {\n ctx.error('Failed to get list', { error: err, wsIds })\n }\n return buffer.splice(0, 50)\n },\n close: async () => {}\n }\n }\n\n @withContext('stat')\n async stat (ctx: MeasureContext, wsIds: WorkspaceIds, objectName: string): Promise<Blob | undefined> {\n try {\n const result = await this.client.headObject({\n Bucket: this.getBucketId(wsIds),\n Key: this.getDocumentKey(wsIds, objectName)\n })\n const rootPrefix = this.rootPrefix(wsIds)\n return {\n provider: '',\n _class: core.class.Blob,\n _id: this.stripPrefix(rootPrefix, objectName) as Ref<Blob>,\n contentType: result.ContentType ?? '',\n size: result.ContentLength ?? 0,\n etag: result.ETag ?? '',\n space: core.space.Configuration,\n modifiedBy: core.account.System,\n modifiedOn: result.LastModified?.getTime() ?? 0,\n version: result.VersionId ?? null\n }\n } catch (err: any) {\n if (err?.$metadata?.httpStatusCode !== 404) {\n ctx.warn('no object found', { error: err, objectName, wsIds })\n }\n }\n }\n\n @withContext('get')\n async get (ctx: MeasureContext, wsIds: WorkspaceIds, objectName: string): Promise<Readable> {\n return await this.doGet(ctx, wsIds, objectName)\n }\n\n async doGet (ctx: MeasureContext, wsIds: WorkspaceIds, objectName: string, range?: string): Promise<Readable> {\n try {\n const res = await this.client.getObject({\n Bucket: this.getBucketId(wsIds),\n Key: this.getDocumentKey(wsIds, objectName),\n Range: range\n })\n\n const stream = res.Body?.transformToWebStream()\n\n if (stream !== undefined) {\n return Readable.fromWeb(stream as ReadableStream<any>)\n } else {\n const readable = new Readable()\n readable._read = () => {}\n readable.push(null)\n return readable\n }\n } catch (err: any) {\n // In case of error return undefined\n throw new NoSuchKeyError(`uuid=${wsIds.uuid} dataId=${wsIds.dataId} missing ${objectName}`, err)\n }\n }\n\n @withContext('put')\n put (\n ctx: MeasureContext,\n wsIds: WorkspaceIds,\n objectName: string,\n stream: Readable | Buffer | string,\n contentType: string,\n size?: number\n ): Promise<UploadedObjectInfo> {\n if (size !== undefined && size < 1024 * 1024 * 5) {\n return ctx.with(\n 'simple-put',\n {},\n async () => {\n const cmd = new PutObjectCommand({\n Bucket: this.getBucketId(wsIds),\n Key: this.getDocumentKey(wsIds, objectName),\n ContentType: contentType,\n ContentLength: size,\n Body: stream\n })\n const response = await this.client.send(cmd)\n return {\n etag: response.ETag ?? '',\n versionId: response.VersionId ?? null\n }\n },\n { size, objectName, wsIds }\n )\n // Less 5Mb\n } else {\n return ctx.with(\n 'multipart-upload',\n {},\n async () => {\n const uploadTask = new Upload({\n client: this.client,\n params: {\n Bucket: this.getBucketId(wsIds),\n Key: this.getDocumentKey(wsIds, objectName),\n ContentType: contentType,\n Body: stream\n },\n\n // (optional) concurrency configuration\n // queueSize: 1,\n\n // (optional) size of each part, in bytes, at least 5MB\n partSize: 1024 * 1024 * 5,\n leavePartsOnError: false\n })\n\n const output = await uploadTask.done()\n return {\n etag: output.ETag ?? '',\n versionId: output.VersionId ?? null\n }\n },\n { size, objectName, wsIds }\n )\n }\n }\n\n @withContext('read')\n async read (ctx: MeasureContext, wsIds: WorkspaceIds, name: string): Promise<Buffer[]> {\n const data = await this.doGet(ctx, wsIds, name)\n const chunks: Buffer[] = []\n\n await new Promise((resolve, reject) => {\n data.on('data', (chunk) => {\n chunks.push(chunk)\n })\n\n data.on('end', () => {\n data.destroy()\n resolve(null)\n })\n data.on('error', (err) => {\n data.destroy()\n reject(err)\n })\n })\n return chunks\n }\n\n @withContext('partial')\n async partial (\n ctx: MeasureContext,\n wsIds: WorkspaceIds,\n objectName: string,\n offset: number,\n length?: number\n ): Promise<Readable> {\n const range = length !== undefined ? `bytes=${offset}-${offset + length}` : `bytes=${offset}-`\n return await this.doGet(ctx, wsIds, objectName, range)\n }\n\n @withContext('getUrl')\n async getUrl (ctx: MeasureContext, wsIds: WorkspaceIds, objectName: string): Promise<string> {\n const filesUrl = getMetadata(serverCore.metadata.FilesUrl) ?? ''\n return filesUrl.replaceAll(':workspace', getDataId(wsIds)).replaceAll(':blobId', objectName)\n }\n}\n\nexport function processConfigFromEnv (storageConfig: StorageConfiguration): string | undefined {\n const endpoint = process.env.S3_ENDPOINT\n if (endpoint === undefined) {\n return 'S3_ENDPOINT'\n }\n const accessKey = process.env.S3_ACCESS_KEY\n if (accessKey === undefined) {\n return 'S3_ACCESS_KEY'\n }\n\n const secretKey = process.env.S3_SECRET_KEY\n if (secretKey === undefined) {\n return 'S3_SECRET_KEY'\n }\n\n const config: S3Config = {\n kind: 's3',\n name: 's3',\n region: 'auto',\n endpoint,\n accessKey,\n secretKey\n }\n storageConfig.storages.push(config)\n storageConfig.default = 's3'\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["HttpAgent", "HttpsAgent", "core", "serverCore"]
7
+ }
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+ var import_core = require("@hanzo/core");
5
+ var import__ = require(".");
6
+ const MB = 1024 * 1024;
7
+ const config = { default: "minio", storages: [] };
8
+ const minioConfigVar = (0, import__.processConfigFromEnv)(config);
9
+ if (minioConfigVar !== void 0 || config.storages[0] === void 0) {
10
+ console.error("No S3 config env is configured:" + minioConfigVar);
11
+ it.skip("No S3 config env is configured", async () => {
12
+ });
13
+ process.exit(1);
14
+ }
15
+ const toolCtx = new import_core.MeasureMetricsContext("test", {});
16
+ const storageService = new import__.S3Service({ ...config.storages[0], rootBucket: "haiodo-test-bucket" });
17
+ async function doTest() {
18
+ const existingTestBuckets = await storageService.listBuckets(toolCtx);
19
+ for (const b of existingTestBuckets) {
20
+ await b.delete();
21
+ }
22
+ const genWorkspaceId1 = (0, import_core.generateId)();
23
+ const wsIds1 = {
24
+ uuid: genWorkspaceId1,
25
+ dataId: genWorkspaceId1,
26
+ url: ""
27
+ };
28
+ await storageService.make(toolCtx, wsIds1);
29
+ let st1 = Date.now();
30
+ const sz = 10;
31
+ const stream = Buffer.alloc(sz * 1024 * 1024);
32
+ for (let i = 0; i < 10; i++) {
33
+ const st = Date.now();
34
+ await storageService.put(toolCtx, wsIds1, `testObject.${i}`, stream, "application/octet-stream", stream.length);
35
+ console.log("upload time", Date.now() - st);
36
+ }
37
+ let now = Date.now();
38
+ console.log(`upload performance: ${Math.round(sz * 10 * 1e3 * 100 / (now - st1)) / 100} mb per second`);
39
+ st1 = Date.now();
40
+ for (let i = 0; i < 10; i++) {
41
+ const st = Date.now();
42
+ await storageService.read(toolCtx, wsIds1, `testObject.${i}`);
43
+ console.log("download time", Date.now() - st);
44
+ }
45
+ now = Date.now();
46
+ console.log(`download performance: ${Math.round(sz * 10 * 1e3 * 100 / (now - st1)) / 100} mb per second`);
47
+ st1 = Date.now();
48
+ for (let i = 0; i < 10; i++) {
49
+ const st = Date.now();
50
+ const readable = await storageService.get(toolCtx, wsIds1, `testObject.${i}`);
51
+ const chunks = [];
52
+ readable.on("data", (chunk) => {
53
+ chunks.push(chunk);
54
+ });
55
+ await new Promise((resolve) => {
56
+ readable.on("end", () => {
57
+ resolve();
58
+ readable.destroy();
59
+ });
60
+ });
61
+ console.log("download time 2", Date.now() - st);
62
+ }
63
+ now = Date.now();
64
+ console.log(`download performance: ${Math.round(sz * 10 * 1e3 * 100 / (now - st1)) / 100} mb per second`);
65
+ st1 = Date.now();
66
+ for (let i = 0; i < 10; i++) {
67
+ const st = Date.now();
68
+ for (let i2 = 0; i2 < sz; i2++) {
69
+ const readable = await storageService.partial(toolCtx, wsIds1, `testObject.${i2}`, i2 * MB, MB);
70
+ const chunks = [];
71
+ readable.on("data", (chunk) => {
72
+ chunks.push(chunk);
73
+ });
74
+ await new Promise((resolve) => {
75
+ readable.on("end", () => {
76
+ resolve();
77
+ readable.destroy();
78
+ });
79
+ });
80
+ }
81
+ console.log("download time 2", Date.now() - st);
82
+ }
83
+ now = Date.now();
84
+ console.log(`download performance: ${Math.round(sz * 10 * 1e3 * 100 / (now - st1)) / 100} mb per second`);
85
+ }
86
+ __name(doTest, "doTest");
87
+ void doTest().catch((err) => {
88
+ console.error(err);
89
+ });
90
+ console.log("done");
91
+ //# sourceMappingURL=perfTest.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/perfTest.ts"],
4
+ "sourcesContent": ["import { MeasureMetricsContext, type WorkspaceDataId, type WorkspaceUuid, generateId } from '@hanzo/core'\nimport type { StorageConfiguration } from '@hanzo/server-core'\nimport { S3Service, processConfigFromEnv, type S3Config } from '.'\n\nconst MB = 1024 * 1024\n\nconst config: StorageConfiguration = { default: 'minio', storages: [] }\nconst minioConfigVar = processConfigFromEnv(config)\nif (minioConfigVar !== undefined || config.storages[0] === undefined) {\n console.error('No S3 config env is configured:' + minioConfigVar)\n it.skip('No S3 config env is configured', async () => {})\n process.exit(1)\n}\nconst toolCtx = new MeasureMetricsContext('test', {})\nconst storageService = new S3Service({ ...(config.storages[0] as S3Config), rootBucket: 'haiodo-test-bucket' })\n\nasync function doTest (): Promise<void> {\n const existingTestBuckets = await storageService.listBuckets(toolCtx)\n // Delete old buckets\n for (const b of existingTestBuckets) {\n await b.delete()\n }\n\n const genWorkspaceId1 = generateId() as unknown as WorkspaceDataId\n\n const wsIds1 = {\n uuid: genWorkspaceId1 as unknown as WorkspaceUuid,\n dataId: genWorkspaceId1,\n url: ''\n }\n await storageService.make(toolCtx, wsIds1)\n /// /////// Uploads\n let st1 = Date.now()\n const sz = 10\n const stream = Buffer.alloc(sz * 1024 * 1024)\n for (let i = 0; i < 10; i++) {\n // We need 1Mb random file to check upload speed.\n const st = Date.now()\n await storageService.put(toolCtx, wsIds1, `testObject.${i}`, stream, 'application/octet-stream', stream.length)\n console.log('upload time', Date.now() - st)\n }\n let now = Date.now()\n console.log(`upload performance: ${Math.round((sz * 10 * 1000 * 100) / (now - st1)) / 100} mb per second`)\n\n /// // Downloads 1\n st1 = Date.now()\n for (let i = 0; i < 10; i++) {\n // We need 1Mb random file to check upload speed.\n const st = Date.now()\n await storageService.read(toolCtx, wsIds1, `testObject.${i}`)\n console.log('download time', Date.now() - st)\n }\n\n now = Date.now()\n console.log(`download performance: ${Math.round((sz * 10 * 1000 * 100) / (now - st1)) / 100} mb per second`)\n\n /// Downloads 2\n st1 = Date.now()\n for (let i = 0; i < 10; i++) {\n // We need 1Mb random file to check upload speed.\n const st = Date.now()\n const readable = await storageService.get(toolCtx, wsIds1, `testObject.${i}`)\n const chunks: Buffer[] = []\n readable.on('data', (chunk) => {\n chunks.push(chunk)\n })\n await new Promise<void>((resolve) => {\n readable.on('end', () => {\n resolve()\n readable.destroy()\n })\n })\n console.log('download time 2', Date.now() - st)\n }\n\n now = Date.now()\n console.log(`download performance: ${Math.round((sz * 10 * 1000 * 100) / (now - st1)) / 100} mb per second`)\n\n /// Downloads 3\n st1 = Date.now()\n for (let i = 0; i < 10; i++) {\n // We need 1Mb random file to check upload speed.\n const st = Date.now()\n for (let i = 0; i < sz; i++) {\n const readable = await storageService.partial(toolCtx, wsIds1, `testObject.${i}`, i * MB, MB)\n const chunks: Buffer[] = []\n readable.on('data', (chunk) => {\n chunks.push(chunk)\n })\n await new Promise<void>((resolve) => {\n readable.on('end', () => {\n resolve()\n readable.destroy()\n })\n })\n }\n console.log('download time 2', Date.now() - st)\n }\n\n now = Date.now()\n console.log(`download performance: ${Math.round((sz * 10 * 1000 * 100) / (now - st1)) / 100} mb per second`)\n}\nvoid doTest().catch((err) => {\n console.error(err)\n})\nconsole.log('done')\n"],
5
+ "mappings": "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",
6
+ "names": ["i"]
7
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@hanzo/s3",
3
+ "version": "0.6.3",
4
+ "main": "lib/index.js",
5
+ "svelte": "src/index.ts",
6
+ "types": "types/index.d.ts",
7
+ "author": "Hanzo <dev@hanzo.ai>",
8
+ "template": "@hanzo/node-package",
9
+ "license": "EPL-2.0",
10
+ "scripts": {
11
+ "build": "compile",
12
+ "build:watch": "compile",
13
+ "test": "jest --passWithNoTests --silent --forceExit",
14
+ "format": "format src",
15
+ "_phase:build": "compile transpile src",
16
+ "_phase:test": "jest --passWithNoTests --silent --forceExit",
17
+ "_phase:format": "format src",
18
+ "_phase:validate": "compile validate"
19
+ },
20
+ "devDependencies": {
21
+ "@hanzo/platform-rig": "^0.6.0",
22
+ "@typescript-eslint/eslint-plugin": "^6.11.0",
23
+ "eslint-plugin-import": "^2.26.0",
24
+ "eslint-plugin-promise": "^6.1.1",
25
+ "eslint-plugin-n": "^15.4.0",
26
+ "eslint": "^8.54.0",
27
+ "@typescript-eslint/parser": "^6.11.0",
28
+ "eslint-config-standard-with-typescript": "^40.0.0",
29
+ "prettier": "^3.1.0",
30
+ "typescript": "^5.3.3",
31
+ "@types/node": "~20.11.16",
32
+ "jest": "^29.7.0",
33
+ "ts-jest": "^29.1.1",
34
+ "@types/jest": "^29.5.5"
35
+ },
36
+ "dependencies": {
37
+ "@hanzo/core": "^0.6.37",
38
+ "@hanzo/platform": "^0.6.15",
39
+ "@hanzo/server-core": "^0.6.4",
40
+ "@hanzo/storage": "^0.6.3",
41
+ "@aws-sdk/client-s3": "^3.738.0",
42
+ "@aws-sdk/s3-request-presigner": "^3.738.0",
43
+ "@aws-sdk/lib-storage": "^3.738.0",
44
+ "@smithy/node-http-handler": "^4.0.2"
45
+ }
46
+ }