@h-ai/storage 1.0.0-beta.21 → 1.0.0-beta.23
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.en.md +14 -6
- package/README.ja.md +14 -6
- package/README.md +14 -6
- package/dist/browser.d.ts +17 -32
- package/dist/browser.js +2 -2
- package/dist/{chunk-JAJYHGLI.js → chunk-RXYTWURA.js} +29 -42
- package/dist/chunk-RXYTWURA.js.map +1 -0
- package/dist/{chunk-FK7KRCML.js → chunk-WOVOQXRY.js} +19 -7
- package/dist/chunk-WOVOQXRY.js.map +1 -0
- package/dist/client/index.js +1 -1
- package/dist/node.js +320 -142
- package/dist/node.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-FK7KRCML.js.map +0 -1
- package/dist/chunk-JAJYHGLI.js.map +0 -1
package/dist/node.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import { HaiStorageError, StorageConfigSchema, PresignUploadOptionsSchema, PresignOptionsSchema } from './chunk-
|
|
2
|
-
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-
|
|
3
|
-
import { storageM, MIME_TYPES, MIME_TYPE_DEFAULT } from './chunk-
|
|
4
|
-
export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-
|
|
1
|
+
import { HaiStorageError, StorageConfigSchema, PresignUploadOptionsSchema, PresignOptionsSchema } from './chunk-WOVOQXRY.js';
|
|
2
|
+
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-WOVOQXRY.js';
|
|
3
|
+
import { storageM, MIME_TYPES, MIME_TYPE_DEFAULT } from './chunk-RXYTWURA.js';
|
|
4
|
+
export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-RXYTWURA.js';
|
|
5
5
|
export { STORAGE_DELETE_MAX_KEYS, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema, StorageKeySchema, StoragePrefixSchema, StoragePresignExpiresInSchema, getStorageKeyIssue } from './chunk-6YG3N3CW.js';
|
|
6
6
|
import process from 'node:process';
|
|
7
7
|
import { core, err, ok } from '@h-ai/core';
|
|
8
8
|
import { Buffer } from 'node:buffer';
|
|
9
9
|
import * as crypto from 'node:crypto';
|
|
10
|
-
import {
|
|
10
|
+
import { timingSafeEqual, createHash } from 'node:crypto';
|
|
11
11
|
import * as fs from 'node:fs';
|
|
12
12
|
import * as fsp from 'node:fs/promises';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
-
import { Readable } from 'node:stream';
|
|
14
|
+
import { finished, Readable } from 'node:stream';
|
|
15
15
|
import { pipeline } from 'node:stream/promises';
|
|
16
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
16
17
|
import { S3Client, HeadBucketCommand, CreateBucketCommand, ListObjectsV2Command, PutObjectCommand, GetObjectCommand, CopyObjectCommand, DeleteObjectsCommand, DeleteObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
|
|
17
18
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
19
|
+
import { getSystemErrorMessage } from 'node:util';
|
|
18
20
|
|
|
19
21
|
function toStorageDeleteResult(summary) {
|
|
20
22
|
if (summary.failures.length === 0)
|
|
@@ -30,6 +32,68 @@ function toStorageDeleteResult(summary) {
|
|
|
30
32
|
}
|
|
31
33
|
};
|
|
32
34
|
}
|
|
35
|
+
async function uploadWithSource(data, signal, write) {
|
|
36
|
+
const readable = data instanceof Readable ? data : void 0;
|
|
37
|
+
if (readable) {
|
|
38
|
+
const cleanup = finished(readable, { readable: true, writable: false }, () => cleanup());
|
|
39
|
+
}
|
|
40
|
+
let sourceFailure;
|
|
41
|
+
function failReading(cause) {
|
|
42
|
+
sourceFailure = new Error("Storage upload source failed", { cause });
|
|
43
|
+
throw sourceFailure;
|
|
44
|
+
}
|
|
45
|
+
function track(source) {
|
|
46
|
+
return {
|
|
47
|
+
[Symbol.asyncIterator]() {
|
|
48
|
+
let iterator;
|
|
49
|
+
try {
|
|
50
|
+
iterator = source[Symbol.asyncIterator]();
|
|
51
|
+
} catch (cause) {
|
|
52
|
+
return failReading(cause);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
async next() {
|
|
56
|
+
try {
|
|
57
|
+
return await iterator.next();
|
|
58
|
+
} catch (cause) {
|
|
59
|
+
return failReading(cause);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
return: iterator.return?.bind(iterator)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const input = typeof data === "string" || data instanceof Uint8Array || readable ? data : track(data);
|
|
69
|
+
const result = await write(input);
|
|
70
|
+
if (result.success)
|
|
71
|
+
return result;
|
|
72
|
+
const { cause } = result.error;
|
|
73
|
+
const readFailure = sourceFailure;
|
|
74
|
+
const failedInput = readFailure !== void 0 && cause === readFailure;
|
|
75
|
+
const originalCause = failedInput ? readFailure.cause : cause;
|
|
76
|
+
if (signal?.aborted && (Object.is(originalCause, signal.reason) || originalCause instanceof Error && originalCause.name === "AbortError")) {
|
|
77
|
+
return err({
|
|
78
|
+
...result.error,
|
|
79
|
+
message: storageM("storage_uploadCanceled"),
|
|
80
|
+
cause: signal.reason
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (failedInput || readable?.errored && readable.errored === cause) {
|
|
84
|
+
return err({
|
|
85
|
+
...HaiStorageError.UPLOAD_FAILED,
|
|
86
|
+
message: storageM("storage_uploadSourceReadFailed"),
|
|
87
|
+
cause: originalCause,
|
|
88
|
+
...result.error.ext && { ext: result.error.ext }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
} finally {
|
|
93
|
+
if (readable && !readable.readableEnded && !readable.destroyed)
|
|
94
|
+
readable.destroy();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
33
97
|
async function* toByteStream(data, signal) {
|
|
34
98
|
signal?.throwIfAborted();
|
|
35
99
|
if (typeof data === "string" || data instanceof Uint8Array) {
|
|
@@ -38,6 +102,7 @@ async function* toByteStream(data, signal) {
|
|
|
38
102
|
}
|
|
39
103
|
const iterator = data[Symbol.asyncIterator]();
|
|
40
104
|
let completed = false;
|
|
105
|
+
let failed = false;
|
|
41
106
|
try {
|
|
42
107
|
while (true) {
|
|
43
108
|
signal?.throwIfAborted();
|
|
@@ -63,61 +128,27 @@ async function* toByteStream(data, signal) {
|
|
|
63
128
|
yield result.value;
|
|
64
129
|
}
|
|
65
130
|
signal?.throwIfAborted();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
failed = true;
|
|
133
|
+
throw error;
|
|
66
134
|
} finally {
|
|
67
135
|
if (!completed && iterator.return) {
|
|
68
|
-
|
|
69
|
-
|
|
136
|
+
const closed = Promise.resolve().then(() => iterator.return?.());
|
|
137
|
+
if (failed || signal?.aborted)
|
|
138
|
+
void closed.catch(() => {
|
|
70
139
|
});
|
|
71
140
|
else
|
|
72
|
-
await
|
|
141
|
+
await closed;
|
|
73
142
|
}
|
|
74
143
|
}
|
|
75
144
|
}
|
|
76
145
|
|
|
77
|
-
// src/providers/storage-operation-log.ts
|
|
78
|
-
function normalizePayloadValue(value) {
|
|
79
|
-
if (value instanceof Uint8Array) {
|
|
80
|
-
return { byteLength: value.byteLength };
|
|
81
|
-
}
|
|
82
|
-
return value;
|
|
83
|
-
}
|
|
84
|
-
function stringifyOperationPayload(payload, maxLength) {
|
|
85
|
-
if (maxLength <= 0) {
|
|
86
|
-
return "";
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
const text = JSON.stringify(payload, (_key, value) => {
|
|
90
|
-
if (typeof value === "bigint") {
|
|
91
|
-
return value.toString();
|
|
92
|
-
}
|
|
93
|
-
if (typeof value === "function") {
|
|
94
|
-
return "[Function]";
|
|
95
|
-
}
|
|
96
|
-
return normalizePayloadValue(value);
|
|
97
|
-
});
|
|
98
|
-
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
|
99
|
-
} catch {
|
|
100
|
-
const text = String(payload);
|
|
101
|
-
return text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function logStorageOperation(logger4, config, category, operation, payload) {
|
|
105
|
-
if (!config?.[category]) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
logger4[config.level]("Storage operation executed", {
|
|
109
|
-
category,
|
|
110
|
-
operation,
|
|
111
|
-
payload: stringifyOperationPayload(payload, config.maxLength)
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
146
|
// src/providers/storage-provider-local.ts
|
|
116
147
|
var logger = core.logger.child({ module: "storage", scope: "provider-local" });
|
|
117
148
|
var PATH_SEGMENT_SPLIT_REGEX = /[\\/]+/;
|
|
118
149
|
var METADATA_DIRECTORY = ".hai-storage-meta";
|
|
119
150
|
function toStorageError(error, key) {
|
|
120
|
-
const e = error;
|
|
151
|
+
const e = error ?? {};
|
|
121
152
|
if (e.code === "EEXIST") {
|
|
122
153
|
return {
|
|
123
154
|
...HaiStorageError.ALREADY_EXISTS,
|
|
@@ -258,9 +289,6 @@ function createLocalProvider() {
|
|
|
258
289
|
}
|
|
259
290
|
return target;
|
|
260
291
|
}
|
|
261
|
-
function logOperation(category, operation, payload) {
|
|
262
|
-
logStorageOperation(logger, config?.operationLog, category, operation, payload);
|
|
263
|
-
}
|
|
264
292
|
function metadataKey(filePath, stat2) {
|
|
265
293
|
return path.relative(getConfig().root, path.join(path.dirname(filePath), METADATA_DIRECTORY, `${metadataGeneration(stat2)}.meta.json`));
|
|
266
294
|
}
|
|
@@ -287,7 +315,6 @@ function createLocalProvider() {
|
|
|
287
315
|
if (error.code !== "ENOENT")
|
|
288
316
|
throw error;
|
|
289
317
|
}
|
|
290
|
-
logOperation("write", "file.put", { key, options });
|
|
291
318
|
const dir2 = path.dirname(filePath);
|
|
292
319
|
await fsp.mkdir(dir2, { recursive: true, mode: cfg.directoryMode });
|
|
293
320
|
temporaryPath = `${filePath}.${crypto.randomUUID()}.upload.tmp`;
|
|
@@ -329,19 +356,18 @@ function createLocalProvider() {
|
|
|
329
356
|
await fsp.unlink(previousMetadataPath);
|
|
330
357
|
} catch (error) {
|
|
331
358
|
if (error.code !== "ENOENT")
|
|
332
|
-
logger.warn("Obsolete local metadata cleanup failed"
|
|
359
|
+
logger.warn("Obsolete local metadata cleanup failed");
|
|
333
360
|
}
|
|
334
361
|
}
|
|
335
362
|
return ok(metadata);
|
|
336
363
|
} catch (error) {
|
|
337
|
-
logger.warn("Put file failed", { key, error });
|
|
338
364
|
return err(toStorageError(error, key));
|
|
339
365
|
} finally {
|
|
340
366
|
if (temporaryPath)
|
|
341
367
|
await fsp.unlink(temporaryPath).catch(() => {
|
|
342
368
|
});
|
|
343
369
|
if (preparedMetadataPath && !published)
|
|
344
|
-
await fsp.unlink(preparedMetadataPath).catch(() => logger.warn("Unpublished local metadata cleanup failed"
|
|
370
|
+
await fsp.unlink(preparedMetadataPath).catch(() => logger.warn("Unpublished local metadata cleanup failed"));
|
|
345
371
|
}
|
|
346
372
|
},
|
|
347
373
|
async get(key, options = {}) {
|
|
@@ -368,7 +394,6 @@ function createLocalProvider() {
|
|
|
368
394
|
}
|
|
369
395
|
try {
|
|
370
396
|
const filePath = await fullPath(key);
|
|
371
|
-
logOperation("read", "file.getStream", { key, options });
|
|
372
397
|
options.signal?.throwIfAborted();
|
|
373
398
|
const stat2 = await fsp.stat(filePath);
|
|
374
399
|
if (stat2.isDirectory()) {
|
|
@@ -397,7 +422,6 @@ function createLocalProvider() {
|
|
|
397
422
|
}
|
|
398
423
|
try {
|
|
399
424
|
const filePath = await fullPath(key);
|
|
400
|
-
logOperation("read", "file.head", { key });
|
|
401
425
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
402
426
|
const stat2 = await fsp.stat(filePath, { bigint: true });
|
|
403
427
|
if (stat2.isDirectory()) {
|
|
@@ -420,17 +444,9 @@ function createLocalProvider() {
|
|
|
420
444
|
try {
|
|
421
445
|
customMetadata = JSON.parse(serializedMetadata);
|
|
422
446
|
} catch {
|
|
423
|
-
logger.warn("Invalid local metadata record", {
|
|
424
|
-
key,
|
|
425
|
-
generation: metadataGeneration(stat2)
|
|
426
|
-
});
|
|
427
447
|
return err(HaiStorageError.OPERATION_FAILED, storageM("storage_metadataInvalid"));
|
|
428
448
|
}
|
|
429
449
|
if (!isMetadataRecord(customMetadata)) {
|
|
430
|
-
logger.warn("Invalid local metadata shape", {
|
|
431
|
-
key,
|
|
432
|
-
generation: metadataGeneration(stat2)
|
|
433
|
-
});
|
|
434
450
|
return err(HaiStorageError.OPERATION_FAILED, storageM("storage_metadataInvalid"));
|
|
435
451
|
}
|
|
436
452
|
return ok({
|
|
@@ -458,7 +474,6 @@ function createLocalProvider() {
|
|
|
458
474
|
}
|
|
459
475
|
try {
|
|
460
476
|
const filePath = await fullPath(key);
|
|
461
|
-
logOperation("read", "file.exists", { key });
|
|
462
477
|
await fsp.access(filePath, fs.constants.F_OK);
|
|
463
478
|
return ok(true);
|
|
464
479
|
} catch (error) {
|
|
@@ -480,26 +495,22 @@ function createLocalProvider() {
|
|
|
480
495
|
try {
|
|
481
496
|
const filePath = await fullPath(key);
|
|
482
497
|
const generationKey = metadataKey(filePath, await fsp.stat(filePath, { bigint: true }));
|
|
483
|
-
logOperation("write", "file.delete", { key });
|
|
484
|
-
logger.debug("Deleting file", { key });
|
|
485
498
|
await fsp.unlink(filePath);
|
|
486
499
|
try {
|
|
487
500
|
await fsp.unlink(await fullPath(generationKey));
|
|
488
501
|
} catch (error) {
|
|
489
502
|
if (error.code !== "ENOENT")
|
|
490
|
-
logger.warn("Deleted object metadata cleanup failed"
|
|
503
|
+
logger.warn("Deleted object metadata cleanup failed");
|
|
491
504
|
}
|
|
492
505
|
return ok(void 0);
|
|
493
506
|
} catch (error) {
|
|
494
507
|
if (error.code === "ENOENT") {
|
|
495
508
|
return ok(void 0);
|
|
496
509
|
}
|
|
497
|
-
logger.warn("Delete file failed", { key, error });
|
|
498
510
|
return err(toStorageError(error, key));
|
|
499
511
|
}
|
|
500
512
|
},
|
|
501
513
|
async deleteMany(keys) {
|
|
502
|
-
logOperation("write", "file.deleteMany", { count: keys.length, keys });
|
|
503
514
|
const summary = { deletedKeys: [], failures: [] };
|
|
504
515
|
const uniqueKeys = [
|
|
505
516
|
...new Set(keys)
|
|
@@ -531,7 +542,6 @@ function createLocalProvider() {
|
|
|
531
542
|
return toStorageDeleteResult(summary);
|
|
532
543
|
},
|
|
533
544
|
async copy(sourceKey, destKey, options = {}) {
|
|
534
|
-
logOperation("write", "file.copy", { sourceKey, destKey, options });
|
|
535
545
|
const source = await file.head(sourceKey);
|
|
536
546
|
if (!source.success)
|
|
537
547
|
return source;
|
|
@@ -554,7 +564,6 @@ function createLocalProvider() {
|
|
|
554
564
|
if (typeof prefix !== "string" || typeof delimiter !== "string" || !Number.isInteger(maxKeys) || maxKeys < 1 || maxKeys > 1e3 || Buffer.from(prefix).toString("utf8") !== prefix || Buffer.from(delimiter).toString("utf8") !== delimiter) {
|
|
555
565
|
return err(HaiStorageError.INVALID_LIST_OPTIONS, storageM("storage_listOptionsInvalid"));
|
|
556
566
|
}
|
|
557
|
-
logOperation("read", "dir.list", { options });
|
|
558
567
|
safePath(cfg.root, prefix);
|
|
559
568
|
const root = await fsp.realpath(cfg.root);
|
|
560
569
|
const scope = crypto.createHash("sha256").update(JSON.stringify([root, prefix, delimiter])).digest("hex");
|
|
@@ -647,7 +656,6 @@ function createLocalProvider() {
|
|
|
647
656
|
}
|
|
648
657
|
try {
|
|
649
658
|
const dirPath = await fullPath(prefix);
|
|
650
|
-
logOperation("write", "dir.delete", { prefix });
|
|
651
659
|
await fsp.rm(dirPath, { recursive: true, force: true });
|
|
652
660
|
return ok(void 0);
|
|
653
661
|
} catch (error) {
|
|
@@ -659,16 +667,13 @@ function createLocalProvider() {
|
|
|
659
667
|
}
|
|
660
668
|
};
|
|
661
669
|
const presign = {
|
|
662
|
-
async getUrl(
|
|
663
|
-
logOperation("read", "presign.getUrl", { key, options });
|
|
670
|
+
async getUrl() {
|
|
664
671
|
return err(HaiStorageError.UNSUPPORTED_OPERATION, storageM("storage_localPresignUnsupported"));
|
|
665
672
|
},
|
|
666
|
-
async putUrl(
|
|
667
|
-
logOperation("write", "presign.putUrl", { key, options });
|
|
673
|
+
async putUrl() {
|
|
668
674
|
return err(HaiStorageError.UNSUPPORTED_OPERATION, storageM("storage_localPresignUnsupported"));
|
|
669
675
|
},
|
|
670
|
-
publicUrl(
|
|
671
|
-
logOperation("read", "presign.publicUrl", { key });
|
|
676
|
+
publicUrl() {
|
|
672
677
|
return null;
|
|
673
678
|
}
|
|
674
679
|
};
|
|
@@ -686,20 +691,19 @@ function createLocalProvider() {
|
|
|
686
691
|
}
|
|
687
692
|
try {
|
|
688
693
|
config = cfg;
|
|
689
|
-
logger.
|
|
694
|
+
logger.debug("Connecting local provider");
|
|
690
695
|
await fsp.mkdir(cfg.root, { recursive: true, mode: cfg.directoryMode });
|
|
691
696
|
config = { ...cfg, root: await fsp.realpath(cfg.root) };
|
|
692
697
|
connected = true;
|
|
693
|
-
logger.
|
|
698
|
+
logger.debug("Local provider connected");
|
|
694
699
|
return ok(void 0);
|
|
695
700
|
} catch (error) {
|
|
696
|
-
logger.error("Local provider connect failed", { error });
|
|
697
701
|
config = null;
|
|
698
702
|
return err(toStorageError(error));
|
|
699
703
|
}
|
|
700
704
|
},
|
|
701
705
|
async close() {
|
|
702
|
-
logger.
|
|
706
|
+
logger.debug("Local provider disconnected");
|
|
703
707
|
config = null;
|
|
704
708
|
connected = false;
|
|
705
709
|
},
|
|
@@ -718,8 +722,47 @@ var RETRYABLE_DELETE_CODES = /* @__PURE__ */ new Set([
|
|
|
718
722
|
"SlowDown",
|
|
719
723
|
"RequestTimeout"
|
|
720
724
|
]);
|
|
725
|
+
var uploadDiagnostics = new AsyncLocalStorage();
|
|
726
|
+
var UPLOAD_COMMANDS = /* @__PURE__ */ new Set([
|
|
727
|
+
"PutObject",
|
|
728
|
+
"CreateMultipartUpload",
|
|
729
|
+
"UploadPart",
|
|
730
|
+
"CompleteMultipartUpload",
|
|
731
|
+
"AbortMultipartUpload"
|
|
732
|
+
]);
|
|
733
|
+
function observeUploadRequests(client) {
|
|
734
|
+
client.middlewareStack.add((next, context) => async (args) => {
|
|
735
|
+
const diagnostics = uploadDiagnostics.getStore();
|
|
736
|
+
if (!diagnostics)
|
|
737
|
+
return next(args);
|
|
738
|
+
const operation = context.commandName?.replace(/Command$/, "");
|
|
739
|
+
const input = args.input;
|
|
740
|
+
if (!operation || !UPLOAD_COMMANDS.has(operation) || !input || typeof input !== "object" || !("Bucket" in input) || input.Bucket !== diagnostics.bucket || !("Key" in input) || input.Key !== diagnostics.key) {
|
|
741
|
+
return next(args);
|
|
742
|
+
}
|
|
743
|
+
try {
|
|
744
|
+
const response = await next(args);
|
|
745
|
+
if (operation === "UploadPart" && (!("ETag" in response.output) || !response.output.ETag)) {
|
|
746
|
+
const { httpStatusCode, requestId } = response.output.$metadata;
|
|
747
|
+
const partNumber = "PartNumber" in input && typeof input.PartNumber === "number" ? input.PartNumber : void 0;
|
|
748
|
+
throw Object.assign(new Error("S3 upload part response is missing ETag"), {
|
|
749
|
+
name: "MissingETag",
|
|
750
|
+
partNumber,
|
|
751
|
+
$metadata: { httpStatusCode, requestId }
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return response;
|
|
755
|
+
} catch (cause) {
|
|
756
|
+
if (operation === "AbortMultipartUpload")
|
|
757
|
+
diagnostics.cleanup = { cause };
|
|
758
|
+
else
|
|
759
|
+
diagnostics.failure ??= { cause, operation };
|
|
760
|
+
throw cause;
|
|
761
|
+
}
|
|
762
|
+
}, { step: "initialize", name: "haiStorageObserveUploadRequests" });
|
|
763
|
+
}
|
|
721
764
|
function toStorageError2(error, key) {
|
|
722
|
-
const e = error;
|
|
765
|
+
const e = error ?? {};
|
|
723
766
|
if (e.name === "PreconditionFailed" || e.$metadata?.httpStatusCode === 412) {
|
|
724
767
|
return {
|
|
725
768
|
...HaiStorageError.ALREADY_EXISTS,
|
|
@@ -738,7 +781,7 @@ function toStorageError2(error, key) {
|
|
|
738
781
|
cause: error
|
|
739
782
|
};
|
|
740
783
|
}
|
|
741
|
-
if (e.name === "AccessDenied" || e.Code === "AccessDenied") {
|
|
784
|
+
if (e.name === "AccessDenied" || e.Code === "AccessDenied" || e.$metadata?.httpStatusCode === 403) {
|
|
742
785
|
return {
|
|
743
786
|
...HaiStorageError.PERMISSION_DENIED,
|
|
744
787
|
message: storageM("storage_permissionDenied", {
|
|
@@ -843,9 +886,6 @@ function createS3Provider() {
|
|
|
843
886
|
function fullKey(key) {
|
|
844
887
|
return withPrefix(key, getConfig().prefix);
|
|
845
888
|
}
|
|
846
|
-
function logOperation(category, operation, payload) {
|
|
847
|
-
logStorageOperation(logger2, config?.operationLog, category, operation, payload);
|
|
848
|
-
}
|
|
849
889
|
const file = {
|
|
850
890
|
async put(key, data, options = {}) {
|
|
851
891
|
try {
|
|
@@ -865,19 +905,19 @@ function createS3Provider() {
|
|
|
865
905
|
ContentDisposition: options.contentDisposition,
|
|
866
906
|
IfNoneMatch: options.ifNoneMatch
|
|
867
907
|
};
|
|
868
|
-
logOperation("write", "file.put", {
|
|
869
|
-
key,
|
|
870
|
-
fullPath,
|
|
871
|
-
byteLength: buffered ? size : void 0,
|
|
872
|
-
options
|
|
873
|
-
});
|
|
874
908
|
if (buffered) {
|
|
875
909
|
await s3Client.send(new PutObjectCommand({ ...params, Body: body }), { abortSignal: options.signal });
|
|
876
910
|
} else {
|
|
911
|
+
const diagnostics = { bucket: s3Config.bucket, key: fullPath };
|
|
877
912
|
async function* counted() {
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
913
|
+
try {
|
|
914
|
+
for await (const chunk of toByteStream(data, options.signal)) {
|
|
915
|
+
size += chunk.byteLength;
|
|
916
|
+
yield chunk;
|
|
917
|
+
}
|
|
918
|
+
} catch (cause) {
|
|
919
|
+
diagnostics.failure ??= { cause };
|
|
920
|
+
throw cause;
|
|
881
921
|
}
|
|
882
922
|
}
|
|
883
923
|
const stream = Readable.from(counted(), { objectMode: false });
|
|
@@ -888,13 +928,32 @@ function createS3Provider() {
|
|
|
888
928
|
if (options.signal?.aborted)
|
|
889
929
|
abort();
|
|
890
930
|
const { Upload } = await import('@aws-sdk/lib-storage');
|
|
891
|
-
|
|
931
|
+
const upload = new Upload({
|
|
892
932
|
client: s3Client,
|
|
893
933
|
params: { ...params, Body: stream },
|
|
894
|
-
|
|
934
|
+
partSize: s3Config.multipart.partSizeBytes,
|
|
935
|
+
queueSize: s3Config.multipart.queueSize,
|
|
895
936
|
leavePartsOnError: false,
|
|
896
937
|
abortController: controller
|
|
897
|
-
})
|
|
938
|
+
});
|
|
939
|
+
await uploadDiagnostics.run(diagnostics, () => upload.done());
|
|
940
|
+
} catch (cause) {
|
|
941
|
+
const failure = diagnostics.failure;
|
|
942
|
+
const cleanup = diagnostics.cleanup;
|
|
943
|
+
const original = cleanup && failure && Object.is(cause, cleanup.cause) ? failure.cause : cause;
|
|
944
|
+
const error = toStorageError2(original, key);
|
|
945
|
+
const s3Operation = failure && Object.is(failure.cause, original) ? failure.operation : void 0;
|
|
946
|
+
if (s3Operation || cleanup) {
|
|
947
|
+
error.ext = {
|
|
948
|
+
...error.ext,
|
|
949
|
+
s3Operation,
|
|
950
|
+
...cleanup && {
|
|
951
|
+
cleanupPending: true,
|
|
952
|
+
cleanupError: toStorageError2(cleanup.cause)
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
return err(error);
|
|
898
957
|
} finally {
|
|
899
958
|
options.signal?.removeEventListener("abort", abort);
|
|
900
959
|
stream.destroy();
|
|
@@ -932,7 +991,6 @@ function createS3Provider() {
|
|
|
932
991
|
const s3Client = getClient();
|
|
933
992
|
const s3Config = getConfig();
|
|
934
993
|
const fullPath = fullKey(key);
|
|
935
|
-
logOperation("read", "file.getStream", { key, fullPath, options });
|
|
936
994
|
let Range;
|
|
937
995
|
if (options.rangeStart !== void 0 || options.rangeEnd !== void 0) {
|
|
938
996
|
const start = options.rangeStart ?? 0;
|
|
@@ -961,7 +1019,6 @@ function createS3Provider() {
|
|
|
961
1019
|
const s3Client = getClient();
|
|
962
1020
|
const s3Config = getConfig();
|
|
963
1021
|
const fullPath = fullKey(key);
|
|
964
|
-
logOperation("read", "file.head", { key, fullPath });
|
|
965
1022
|
const response = await s3Client.send(new HeadObjectCommand({
|
|
966
1023
|
Bucket: s3Config.bucket,
|
|
967
1024
|
Key: fullPath
|
|
@@ -979,7 +1036,6 @@ function createS3Provider() {
|
|
|
979
1036
|
}
|
|
980
1037
|
},
|
|
981
1038
|
async exists(key) {
|
|
982
|
-
logOperation("read", "file.exists", { key });
|
|
983
1039
|
const result = await file.head(key);
|
|
984
1040
|
if (result.success) {
|
|
985
1041
|
return ok(true);
|
|
@@ -994,7 +1050,6 @@ function createS3Provider() {
|
|
|
994
1050
|
const s3Client = getClient();
|
|
995
1051
|
const s3Config = getConfig();
|
|
996
1052
|
const fullPath = fullKey(key);
|
|
997
|
-
logOperation("write", "file.delete", { key, fullPath });
|
|
998
1053
|
await s3Client.send(new DeleteObjectCommand({
|
|
999
1054
|
Bucket: s3Config.bucket,
|
|
1000
1055
|
Key: fullPath
|
|
@@ -1008,7 +1063,6 @@ function createS3Provider() {
|
|
|
1008
1063
|
try {
|
|
1009
1064
|
const s3Client = getClient("deleteMany");
|
|
1010
1065
|
const s3Config = getConfig();
|
|
1011
|
-
logOperation("write", "file.deleteMany", { count: keys.length, keys });
|
|
1012
1066
|
const uniqueKeys = [
|
|
1013
1067
|
...new Set(keys)
|
|
1014
1068
|
];
|
|
@@ -1110,13 +1164,6 @@ function createS3Provider() {
|
|
|
1110
1164
|
s3Config.bucket,
|
|
1111
1165
|
...sourceFullPath.split("/")
|
|
1112
1166
|
].map((part) => encodeURIComponent(part).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`)).join("/");
|
|
1113
|
-
logOperation("write", "file.copy", {
|
|
1114
|
-
sourceKey,
|
|
1115
|
-
destKey,
|
|
1116
|
-
sourceFullPath,
|
|
1117
|
-
destFullPath,
|
|
1118
|
-
options
|
|
1119
|
-
});
|
|
1120
1167
|
await s3Client.send(new CopyObjectCommand({
|
|
1121
1168
|
Bucket: s3Config.bucket,
|
|
1122
1169
|
CopySource: copySource,
|
|
@@ -1136,7 +1183,6 @@ function createS3Provider() {
|
|
|
1136
1183
|
try {
|
|
1137
1184
|
const s3Client = getClient();
|
|
1138
1185
|
const s3Config = getConfig();
|
|
1139
|
-
logOperation("read", "dir.list", { options });
|
|
1140
1186
|
const response = await s3Client.send(new ListObjectsV2Command({
|
|
1141
1187
|
Bucket: s3Config.bucket,
|
|
1142
1188
|
Prefix: withPrefix(options.prefix ?? "", s3Config.prefix) || void 0,
|
|
@@ -1165,7 +1211,6 @@ function createS3Provider() {
|
|
|
1165
1211
|
},
|
|
1166
1212
|
async delete(prefix) {
|
|
1167
1213
|
try {
|
|
1168
|
-
logOperation("write", "dir.delete", { prefix });
|
|
1169
1214
|
let continuationToken;
|
|
1170
1215
|
do {
|
|
1171
1216
|
const listResult = await dir.list({
|
|
@@ -1198,7 +1243,6 @@ function createS3Provider() {
|
|
|
1198
1243
|
const s3Client = getClient("presign");
|
|
1199
1244
|
const s3Config = getConfig();
|
|
1200
1245
|
const fullPath = fullKey(key);
|
|
1201
|
-
logOperation("read", "presign.getUrl", { key, fullPath, options });
|
|
1202
1246
|
const basePath = publicBasePath;
|
|
1203
1247
|
const command = new GetObjectCommand({
|
|
1204
1248
|
Bucket: s3Config.bucket,
|
|
@@ -1224,7 +1268,6 @@ function createS3Provider() {
|
|
|
1224
1268
|
const s3Client = getClient("presign");
|
|
1225
1269
|
const s3Config = getConfig();
|
|
1226
1270
|
const fullPath = fullKey(key);
|
|
1227
|
-
logOperation("write", "presign.putUrl", { key, fullPath, options });
|
|
1228
1271
|
const basePath = publicBasePath;
|
|
1229
1272
|
const command = new PutObjectCommand({
|
|
1230
1273
|
Bucket: s3Config.bucket,
|
|
@@ -1249,7 +1292,6 @@ function createS3Provider() {
|
|
|
1249
1292
|
return null;
|
|
1250
1293
|
}
|
|
1251
1294
|
const fullPath = fullKey(key);
|
|
1252
|
-
logOperation("read", "presign.publicUrl", { key, fullPath });
|
|
1253
1295
|
return `${s3Config.publicUrl.replace(TRAILING_SLASHES_REGEX, "")}/${fullPath}`;
|
|
1254
1296
|
}
|
|
1255
1297
|
};
|
|
@@ -1268,11 +1310,7 @@ function createS3Provider() {
|
|
|
1268
1310
|
try {
|
|
1269
1311
|
await closeClients();
|
|
1270
1312
|
config = cfg;
|
|
1271
|
-
logger2.
|
|
1272
|
-
bucket: cfg.bucket,
|
|
1273
|
-
region: cfg.region,
|
|
1274
|
-
endpoint: cfg.endpoint ? core.sanitize.sanitizeUrl(cfg.endpoint) : void 0
|
|
1275
|
-
});
|
|
1313
|
+
logger2.debug("Connecting S3 provider");
|
|
1276
1314
|
const clientConfig = {
|
|
1277
1315
|
region: cfg.region,
|
|
1278
1316
|
endpoint: cfg.endpoint,
|
|
@@ -1286,6 +1324,7 @@ function createS3Provider() {
|
|
|
1286
1324
|
}
|
|
1287
1325
|
};
|
|
1288
1326
|
client = new S3Client(clientConfig);
|
|
1327
|
+
observeUploadRequests(client);
|
|
1289
1328
|
deleteClient = new S3Client({ ...clientConfig, maxAttempts: 1 });
|
|
1290
1329
|
if (cfg.publicEndpoint) {
|
|
1291
1330
|
const endpoint = new URL(cfg.publicEndpoint);
|
|
@@ -1311,7 +1350,7 @@ function createS3Provider() {
|
|
|
1311
1350
|
CreateBucketConfiguration: { LocationConstraint: cfg.region }
|
|
1312
1351
|
}
|
|
1313
1352
|
}));
|
|
1314
|
-
logger2.
|
|
1353
|
+
logger2.debug("S3 bucket created");
|
|
1315
1354
|
} catch (createError) {
|
|
1316
1355
|
if (!(createError instanceof Error && createError.name === "BucketAlreadyOwnedByYou"))
|
|
1317
1356
|
throw createError;
|
|
@@ -1326,10 +1365,9 @@ function createS3Provider() {
|
|
|
1326
1365
|
}));
|
|
1327
1366
|
config = cfg;
|
|
1328
1367
|
ready = true;
|
|
1329
|
-
logger2.
|
|
1368
|
+
logger2.debug("S3 provider connected");
|
|
1330
1369
|
return ok(void 0);
|
|
1331
1370
|
} catch (error) {
|
|
1332
|
-
logger2.error("S3 provider connect failed", { error });
|
|
1333
1371
|
const original = toStorageError2(error);
|
|
1334
1372
|
try {
|
|
1335
1373
|
await closeClients();
|
|
@@ -1385,9 +1423,136 @@ var checksum = {
|
|
|
1385
1423
|
return ok(actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes));
|
|
1386
1424
|
}
|
|
1387
1425
|
};
|
|
1426
|
+
var logger3 = core.logger.child({ module: "storage", scope: "operations" });
|
|
1427
|
+
var ERROR_NAME = /^[\w:-]{1,80}$/;
|
|
1428
|
+
var FAILURE_REASONS = {
|
|
1429
|
+
[HaiStorageError.CONNECTION_FAILED.code]: "Storage connection or cleanup failed",
|
|
1430
|
+
[HaiStorageError.OPERATION_FAILED.code]: "Storage operation failed",
|
|
1431
|
+
[HaiStorageError.NOT_FOUND.code]: "Storage object was not found",
|
|
1432
|
+
[HaiStorageError.ALREADY_EXISTS.code]: "Destination object already exists",
|
|
1433
|
+
[HaiStorageError.PERMISSION_DENIED.code]: "Storage access was denied",
|
|
1434
|
+
[HaiStorageError.QUOTA_EXCEEDED.code]: "Storage quota or disk space was exhausted",
|
|
1435
|
+
[HaiStorageError.INVALID_PATH.code]: "Storage path was invalid",
|
|
1436
|
+
[HaiStorageError.IO_ERROR.code]: "Storage read or write failed",
|
|
1437
|
+
[HaiStorageError.NETWORK_ERROR.code]: "Storage network request failed",
|
|
1438
|
+
[HaiStorageError.NOT_INITIALIZED.code]: "Storage provider was not initialized",
|
|
1439
|
+
[HaiStorageError.UNSUPPORTED_TYPE.code]: "Storage provider type was unsupported",
|
|
1440
|
+
[HaiStorageError.CONFIG_ERROR.code]: "Storage configuration was invalid",
|
|
1441
|
+
[HaiStorageError.PRESIGN_FAILED.code]: "Could not sign a storage URL",
|
|
1442
|
+
[HaiStorageError.UPLOAD_FAILED.code]: "Could not read the upload source",
|
|
1443
|
+
[HaiStorageError.DOWNLOAD_FAILED.code]: "Storage download failed",
|
|
1444
|
+
[HaiStorageError.CHECKSUM_FAILED.code]: "Could not calculate the checksum",
|
|
1445
|
+
[HaiStorageError.INVALID_CHECKSUM.code]: "Checksum input was invalid",
|
|
1446
|
+
[HaiStorageError.UNSUPPORTED_OPERATION.code]: "Storage provider does not support this operation",
|
|
1447
|
+
[HaiStorageError.INVALID_LIST_OPTIONS.code]: "Storage list options or cursor were invalid"
|
|
1448
|
+
};
|
|
1449
|
+
function storageErrorContext(code, cause, cancelled = false) {
|
|
1450
|
+
const details = cause && typeof cause === "object" ? cause : {};
|
|
1451
|
+
const name = "name" in details ? details.name : void 0;
|
|
1452
|
+
const causeCode = "code" in details && typeof details.code === "string" ? details.code : name;
|
|
1453
|
+
const errno = "errno" in details ? details.errno : void 0;
|
|
1454
|
+
const metadata = "$metadata" in details ? details.$metadata : void 0;
|
|
1455
|
+
const status = typeof metadata === "object" && metadata !== null && "httpStatusCode" in metadata ? metadata.httpStatusCode : void 0;
|
|
1456
|
+
const requestId = typeof metadata === "object" && metadata !== null && "requestId" in metadata ? metadata.requestId : void 0;
|
|
1457
|
+
const partNumber = "partNumber" in details ? details.partNumber : void 0;
|
|
1458
|
+
return {
|
|
1459
|
+
code,
|
|
1460
|
+
reason: cancelled || name === "AbortError" || causeCode === "ABORT_ERR" ? "Storage operation was cancelled" : FAILURE_REASONS[code] ?? "Storage operation failed",
|
|
1461
|
+
causeCode: typeof causeCode === "string" && ERROR_NAME.test(causeCode) ? causeCode : void 0,
|
|
1462
|
+
systemReason: typeof errno === "number" && Number.isInteger(errno) && errno < 0 && errno >= -2147483648 ? getSystemErrorMessage(errno) : void 0,
|
|
1463
|
+
httpStatus: typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? status : void 0,
|
|
1464
|
+
providerRequestId: typeof requestId === "string" && ERROR_NAME.test(requestId) ? requestId : void 0,
|
|
1465
|
+
partNumber: typeof partNumber === "number" && Number.isInteger(partNumber) && partNumber >= 1 && partNumber <= 1e4 ? partNumber : void 0
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
function warnFailure(operation, failure, signal) {
|
|
1469
|
+
const batch = failure.ext?.deleteMany;
|
|
1470
|
+
const s3Operation = failure.ext?.s3Operation;
|
|
1471
|
+
const cleanup = failure.ext?.cleanupError;
|
|
1472
|
+
const cleanupContext = cleanup && typeof cleanup === "object" && "code" in cleanup && typeof cleanup.code === "string" && ERROR_NAME.test(cleanup.code) ? storageErrorContext(cleanup.code, "cause" in cleanup ? cleanup.cause : void 0) : void 0;
|
|
1473
|
+
const failures = batch && typeof batch === "object" && "failures" in batch && Array.isArray(batch.failures) ? batch.failures : void 0;
|
|
1474
|
+
logger3.warn("Storage operation failed", {
|
|
1475
|
+
operation,
|
|
1476
|
+
...storageErrorContext(failure.code, failure.cause, signal?.aborted && Object.is(failure.cause, signal.reason)),
|
|
1477
|
+
s3Operation: typeof s3Operation === "string" && ERROR_NAME.test(s3Operation) ? s3Operation : void 0,
|
|
1478
|
+
cleanupError: cleanupContext,
|
|
1479
|
+
failedCount: failures?.length,
|
|
1480
|
+
failureCodes: failures && [
|
|
1481
|
+
...new Set(failures.slice(0, 32).flatMap((item) => {
|
|
1482
|
+
if (typeof item === "object" && item !== null && "code" in item && typeof item.code === "string" && ERROR_NAME.test(item.code))
|
|
1483
|
+
return [item.code];
|
|
1484
|
+
return [];
|
|
1485
|
+
}))
|
|
1486
|
+
]
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
async function logStorageResult(operation, pending, signal) {
|
|
1490
|
+
const result = await pending.catch((cause) => err(HaiStorageError.OPERATION_FAILED, storageM("storage_operationFailed", {
|
|
1491
|
+
params: {
|
|
1492
|
+
error: cause instanceof Error ? cause.message : storageM("storage_unknownError")
|
|
1493
|
+
}
|
|
1494
|
+
}), cause));
|
|
1495
|
+
if (!result.success)
|
|
1496
|
+
warnFailure(operation, result.error, signal);
|
|
1497
|
+
return result;
|
|
1498
|
+
}
|
|
1499
|
+
async function* logReadStream(stream) {
|
|
1500
|
+
try {
|
|
1501
|
+
yield* stream;
|
|
1502
|
+
} catch (cause) {
|
|
1503
|
+
warnFailure("file.getStream", { code: HaiStorageError.DOWNLOAD_FAILED.code, cause });
|
|
1504
|
+
throw cause;
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
function debugOperation(config, category, operation, summary) {
|
|
1508
|
+
if (!config?.[category])
|
|
1509
|
+
return;
|
|
1510
|
+
const text = summary && config.maxLength > 0 ? JSON.stringify(summary) : "";
|
|
1511
|
+
const payload = text.length > config.maxLength ? `${text.slice(0, config.maxLength)}...` : text;
|
|
1512
|
+
logger3.debug("Storage operation started", { category, operation, payload });
|
|
1513
|
+
}
|
|
1514
|
+
function withStorageOperationLogs(provider, config) {
|
|
1515
|
+
const { file, dir, presign } = provider;
|
|
1516
|
+
function run(category, operation, invoke, summary) {
|
|
1517
|
+
debugOperation(config, category, operation, summary);
|
|
1518
|
+
return logStorageResult(operation, invoke());
|
|
1519
|
+
}
|
|
1520
|
+
return {
|
|
1521
|
+
file: {
|
|
1522
|
+
put(key, data, options) {
|
|
1523
|
+
debugOperation(config, "write", "file.put", {
|
|
1524
|
+
byteLength: typeof data === "string" ? Buffer.byteLength(data) : data instanceof Uint8Array ? data.byteLength : void 0
|
|
1525
|
+
});
|
|
1526
|
+
return logStorageResult("file.put", uploadWithSource(data, options?.signal, (input) => file.put(key, input, options)), options?.signal);
|
|
1527
|
+
},
|
|
1528
|
+
get: (key, options) => run("read", "file.get", () => file.get(key, options)),
|
|
1529
|
+
async getStream(key, options) {
|
|
1530
|
+
const result = await run("read", "file.getStream", () => file.getStream(key, options));
|
|
1531
|
+
return result.success ? ok(logReadStream(result.data)) : result;
|
|
1532
|
+
},
|
|
1533
|
+
head: (key) => run("read", "file.head", () => file.head(key)),
|
|
1534
|
+
exists: (key) => run("read", "file.exists", () => file.exists(key)),
|
|
1535
|
+
delete: (key) => run("write", "file.delete", () => file.delete(key)),
|
|
1536
|
+
deleteMany: (keys) => run("write", "file.deleteMany", () => file.deleteMany(keys), { count: keys.length }),
|
|
1537
|
+
copy: (source, destination, options) => run("write", "file.copy", () => file.copy(source, destination, options))
|
|
1538
|
+
},
|
|
1539
|
+
dir: {
|
|
1540
|
+
list: (options) => run("read", "dir.list", () => dir.list(options), { maxKeys: options?.maxKeys }),
|
|
1541
|
+
delete: (prefix) => run("write", "dir.delete", () => dir.delete(prefix))
|
|
1542
|
+
},
|
|
1543
|
+
presign: {
|
|
1544
|
+
getUrl: (key, options) => run("read", "presign.getUrl", () => presign.getUrl(key, options)),
|
|
1545
|
+
putUrl: (key, options) => run("write", "presign.putUrl", () => presign.putUrl(key, options)),
|
|
1546
|
+
publicUrl(key) {
|
|
1547
|
+
debugOperation(config, "read", "presign.publicUrl");
|
|
1548
|
+
return presign.publicUrl(key);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1388
1553
|
|
|
1389
1554
|
// src/storage-main.ts
|
|
1390
|
-
var
|
|
1555
|
+
var logger4 = core.logger.child({ module: "storage", scope: "main" });
|
|
1391
1556
|
var currentProvider = null;
|
|
1392
1557
|
var currentConfig = null;
|
|
1393
1558
|
var initInProgress = false;
|
|
@@ -1407,7 +1572,7 @@ async function closeOwnedProvider() {
|
|
|
1407
1572
|
pendingProvider = null;
|
|
1408
1573
|
return ok(void 0);
|
|
1409
1574
|
} catch (error) {
|
|
1410
|
-
|
|
1575
|
+
logger4.error("Provider cleanup failed; retaining resource for retry", storageErrorContext(HaiStorageError.CONNECTION_FAILED.code, error));
|
|
1411
1576
|
return err({
|
|
1412
1577
|
...HaiStorageError.CONNECTION_FAILED,
|
|
1413
1578
|
message: storageM("storage_operationFailed", {
|
|
@@ -1463,8 +1628,16 @@ var notInitializedPresign = new Proxy(
|
|
|
1463
1628
|
get: (target, prop, receiver) => prop === "publicUrl" ? () => null : Reflect.get(target, prop, receiver)
|
|
1464
1629
|
}
|
|
1465
1630
|
);
|
|
1631
|
+
var unavailableOperations = withStorageOperationLogs({
|
|
1632
|
+
file: notInitializedFile,
|
|
1633
|
+
dir: notInitializedDir,
|
|
1634
|
+
presign: notInitializedPresign
|
|
1635
|
+
});
|
|
1466
1636
|
var storage = {
|
|
1467
|
-
checksum
|
|
1637
|
+
checksum: {
|
|
1638
|
+
generate: (data, options) => logStorageResult("checksum.generate", checksum.generate(data, options)),
|
|
1639
|
+
verify: (data, expected, options) => logStorageResult("checksum.verify", checksum.verify(data, expected, options))
|
|
1640
|
+
},
|
|
1468
1641
|
/**
|
|
1469
1642
|
* 初始化存储连接。
|
|
1470
1643
|
*
|
|
@@ -1485,7 +1658,7 @@ var storage = {
|
|
|
1485
1658
|
*/
|
|
1486
1659
|
async init(config) {
|
|
1487
1660
|
if (initInProgress || closingPromise) {
|
|
1488
|
-
|
|
1661
|
+
logger4.error("Storage initialization refused during an existing lifecycle operation", { code: HaiStorageError.OPERATION_FAILED.code });
|
|
1489
1662
|
return err(
|
|
1490
1663
|
HaiStorageError.OPERATION_FAILED,
|
|
1491
1664
|
storageM("storage_operationFailed", {
|
|
@@ -1502,10 +1675,10 @@ var storage = {
|
|
|
1502
1675
|
const cleanup = await closeOwnedProvider();
|
|
1503
1676
|
if (!cleanup.success)
|
|
1504
1677
|
return cleanup;
|
|
1505
|
-
|
|
1678
|
+
logger4.debug("Initializing storage module");
|
|
1506
1679
|
const parseResult = StorageConfigSchema.safeParse(config);
|
|
1507
1680
|
if (!parseResult.success) {
|
|
1508
|
-
|
|
1681
|
+
logger4.error("Storage config validation failed", { code: HaiStorageError.CONFIG_ERROR.code, issueCount: parseResult.error.issues.length });
|
|
1509
1682
|
return err(
|
|
1510
1683
|
HaiStorageError.CONFIG_ERROR,
|
|
1511
1684
|
storageM("storage_configError", {
|
|
@@ -1515,25 +1688,30 @@ var storage = {
|
|
|
1515
1688
|
);
|
|
1516
1689
|
}
|
|
1517
1690
|
const parsed = parseResult.data;
|
|
1518
|
-
if (core.module.requiresSharedState(process.env) && parsed.type === "local")
|
|
1691
|
+
if (core.module.requiresSharedState(process.env) && parsed.type === "local") {
|
|
1692
|
+
logger4.error("Storage initialization requires a shared provider", { code: HaiStorageError.CONFIG_ERROR.code, provider: parsed.type });
|
|
1519
1693
|
return err(HaiStorageError.CONFIG_ERROR, storageM("storage_sharedBackendRequired"));
|
|
1694
|
+
}
|
|
1520
1695
|
const provider = createProvider(parsed);
|
|
1521
1696
|
pendingProvider = provider;
|
|
1522
1697
|
const connectResult = await provider.connect(parsed);
|
|
1523
1698
|
if (!connectResult.success) {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1699
|
+
logger4.error("Storage module initialization failed", {
|
|
1700
|
+
provider: provider.name,
|
|
1701
|
+
...storageErrorContext(connectResult.error.code, connectResult.error.cause)
|
|
1527
1702
|
});
|
|
1528
1703
|
return await failInitialization(connectResult);
|
|
1529
1704
|
}
|
|
1705
|
+
currentProvider = {
|
|
1706
|
+
...provider,
|
|
1707
|
+
...withStorageOperationLogs(provider, parsed.operationLog)
|
|
1708
|
+
};
|
|
1530
1709
|
pendingProvider = null;
|
|
1531
|
-
currentProvider = provider;
|
|
1532
1710
|
currentConfig = parsed;
|
|
1533
|
-
|
|
1711
|
+
logger4.debug("Storage module initialized");
|
|
1534
1712
|
return ok(void 0);
|
|
1535
1713
|
} catch (error) {
|
|
1536
|
-
|
|
1714
|
+
logger4.error("Storage module initialization failed", storageErrorContext(HaiStorageError.CONNECTION_FAILED.code, error));
|
|
1537
1715
|
return await failInitialization(err(
|
|
1538
1716
|
HaiStorageError.CONNECTION_FAILED,
|
|
1539
1717
|
storageM("storage_operationFailed", {
|
|
@@ -1551,15 +1729,15 @@ var storage = {
|
|
|
1551
1729
|
},
|
|
1552
1730
|
/** 文件操作接口。未初始化时所有方法返回 NOT_INITIALIZED 错误 */
|
|
1553
1731
|
get file() {
|
|
1554
|
-
return currentProvider?.file ??
|
|
1732
|
+
return currentProvider?.file ?? unavailableOperations.file;
|
|
1555
1733
|
},
|
|
1556
1734
|
/** 目录操作接口。未初始化时所有方法返回 NOT_INITIALIZED 错误 */
|
|
1557
1735
|
get dir() {
|
|
1558
|
-
return currentProvider?.dir ??
|
|
1736
|
+
return currentProvider?.dir ?? unavailableOperations.dir;
|
|
1559
1737
|
},
|
|
1560
1738
|
/** 签名 URL 操作接口。未初始化时 publicUrl 返回 null,其余返回错误 */
|
|
1561
1739
|
get presign() {
|
|
1562
|
-
return currentProvider?.presign ??
|
|
1740
|
+
return currentProvider?.presign ?? unavailableOperations.presign;
|
|
1563
1741
|
},
|
|
1564
1742
|
/** 当前脱敏后的存储配置快照;未初始化或已关闭时为 null */
|
|
1565
1743
|
get config() {
|