@h-ai/storage 1.0.0-beta.2 → 1.0.0-beta.21
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 +102 -41
- package/README.ja.md +101 -40
- package/README.md +144 -81
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +3 -3
- package/dist/{chunk-P65WYXJG.js → chunk-6YG3N3CW.js} +8 -4
- package/dist/chunk-6YG3N3CW.js.map +1 -0
- package/dist/{chunk-ANAESRWS.js → chunk-FK7KRCML.js} +5 -5
- package/dist/chunk-FK7KRCML.js.map +1 -0
- package/dist/{chunk-TLZY4IP6.js → chunk-JAJYHGLI.js} +61 -17
- package/dist/chunk-JAJYHGLI.js.map +1 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +1 -1
- package/dist/node.js +371 -145
- package/dist/node.js.map +1 -1
- package/dist/schemas.d.ts +2 -1
- package/dist/schemas.js +1 -1
- package/package.json +16 -13
- package/dist/chunk-ANAESRWS.js.map +0 -1
- package/dist/chunk-P65WYXJG.js.map +0 -1
- package/dist/chunk-TLZY4IP6.js.map +0 -1
package/dist/node.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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-
|
|
5
|
-
export { STORAGE_DELETE_MAX_KEYS, STORAGE_KEY_MAX_BYTES, StorageDeleteKeysSchema, StorageKeySchema, StoragePrefixSchema, StoragePresignExpiresInSchema, getStorageKeyIssue } from './chunk-
|
|
1
|
+
import { HaiStorageError, StorageConfigSchema, PresignUploadOptionsSchema, PresignOptionsSchema } from './chunk-FK7KRCML.js';
|
|
2
|
+
export { HaiStorageError, LocalConfigSchema, PresignOptionsSchema, PresignUploadOptionsSchema, S3ConfigSchema, StorageConfigSchema, StorageOperationLogConfigSchema, StorageTypeSchema } from './chunk-FK7KRCML.js';
|
|
3
|
+
import { storageM, MIME_TYPES, MIME_TYPE_DEFAULT } from './chunk-JAJYHGLI.js';
|
|
4
|
+
export { downloadAndSave, downloadWithPresignedUrl, formatFileSize, getFileExtension, getMimeType, uploadWithPresignedUrl } from './chunk-JAJYHGLI.js';
|
|
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';
|
|
@@ -23,7 +23,9 @@ function toStorageDeleteResult(summary) {
|
|
|
23
23
|
success: false,
|
|
24
24
|
error: {
|
|
25
25
|
...HaiStorageError.OPERATION_FAILED,
|
|
26
|
-
message: storageM("storage_deleteManyFailed", {
|
|
26
|
+
message: storageM("storage_deleteManyFailed", {
|
|
27
|
+
params: { count: summary.failures.length }
|
|
28
|
+
}),
|
|
27
29
|
ext: { deleteMany: summary }
|
|
28
30
|
}
|
|
29
31
|
};
|
|
@@ -113,22 +115,33 @@ function logStorageOperation(logger4, config, category, operation, payload) {
|
|
|
113
115
|
// src/providers/storage-provider-local.ts
|
|
114
116
|
var logger = core.logger.child({ module: "storage", scope: "provider-local" });
|
|
115
117
|
var PATH_SEGMENT_SPLIT_REGEX = /[\\/]+/;
|
|
118
|
+
var METADATA_DIRECTORY = ".hai-storage-meta";
|
|
116
119
|
function toStorageError(error, key) {
|
|
117
120
|
const e = error;
|
|
118
121
|
if (e.code === "EEXIST") {
|
|
119
|
-
return {
|
|
122
|
+
return {
|
|
123
|
+
...HaiStorageError.ALREADY_EXISTS,
|
|
124
|
+
message: storageM("storage_fileAlreadyExists", {
|
|
125
|
+
params: { key: key ?? "" }
|
|
126
|
+
}),
|
|
127
|
+
cause: error
|
|
128
|
+
};
|
|
120
129
|
}
|
|
121
130
|
if (e.code === "ENOENT") {
|
|
122
131
|
return {
|
|
123
132
|
...HaiStorageError.NOT_FOUND,
|
|
124
|
-
message: storageM("storage_fileNotFound", {
|
|
133
|
+
message: storageM("storage_fileNotFound", {
|
|
134
|
+
params: { key: key ?? "" }
|
|
135
|
+
}),
|
|
125
136
|
cause: error
|
|
126
137
|
};
|
|
127
138
|
}
|
|
128
139
|
if (e.code === "EACCES" || e.code === "EPERM") {
|
|
129
140
|
return {
|
|
130
141
|
...HaiStorageError.PERMISSION_DENIED,
|
|
131
|
-
message: storageM("storage_permissionDenied", {
|
|
142
|
+
message: storageM("storage_permissionDenied", {
|
|
143
|
+
params: { key: key ?? "" }
|
|
144
|
+
}),
|
|
132
145
|
cause: error
|
|
133
146
|
};
|
|
134
147
|
}
|
|
@@ -142,7 +155,9 @@ function toStorageError(error, key) {
|
|
|
142
155
|
if (e.code === "EISDIR") {
|
|
143
156
|
return {
|
|
144
157
|
...HaiStorageError.INVALID_PATH,
|
|
145
|
-
message: storageM("storage_pathIsDir", {
|
|
158
|
+
message: storageM("storage_pathIsDir", {
|
|
159
|
+
params: { key: key ?? "" }
|
|
160
|
+
}),
|
|
146
161
|
cause: error
|
|
147
162
|
};
|
|
148
163
|
}
|
|
@@ -154,8 +169,8 @@ function toStorageError(error, key) {
|
|
|
154
169
|
};
|
|
155
170
|
}
|
|
156
171
|
return {
|
|
157
|
-
|
|
158
|
-
message: storageM("storage_ioError"
|
|
172
|
+
...HaiStorageError.IO_ERROR,
|
|
173
|
+
message: storageM("storage_ioError"),
|
|
159
174
|
cause: error
|
|
160
175
|
};
|
|
161
176
|
}
|
|
@@ -164,10 +179,33 @@ function getMimeType2(filePath) {
|
|
|
164
179
|
return MIME_TYPES[ext] || MIME_TYPE_DEFAULT;
|
|
165
180
|
}
|
|
166
181
|
function calculateEtag(stat2) {
|
|
167
|
-
|
|
182
|
+
const modified = "mtimeNs" in stat2 ? Number(stat2.mtimeNs) / 1e6 : stat2.mtimeMs;
|
|
183
|
+
return `"${stat2.size.toString(16)}-${modified.toString(16)}"`;
|
|
184
|
+
}
|
|
185
|
+
function metadataGeneration(stat2) {
|
|
186
|
+
return crypto.createHash("sha256").update([
|
|
187
|
+
stat2.dev,
|
|
188
|
+
stat2.ino,
|
|
189
|
+
stat2.birthtimeNs,
|
|
190
|
+
stat2.mtimeNs,
|
|
191
|
+
stat2.size
|
|
192
|
+
].join(":")).digest("hex");
|
|
193
|
+
}
|
|
194
|
+
function isMetadataRecord(value) {
|
|
195
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
196
|
+
return false;
|
|
197
|
+
if ("contentType" in value && typeof value.contentType !== "string")
|
|
198
|
+
return false;
|
|
199
|
+
if ("metadata" in value) {
|
|
200
|
+
const metadata = value.metadata;
|
|
201
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata) || Object.values(metadata).some((item) => typeof item !== "string")) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
168
206
|
}
|
|
169
207
|
function isMetaFile(key) {
|
|
170
|
-
return key.endsWith(".meta.json") ||
|
|
208
|
+
return key.toLowerCase().split(PATH_SEGMENT_SPLIT_REGEX).some((segment) => segment === METADATA_DIRECTORY || segment.endsWith(".meta.json") || segment.endsWith(".upload.tmp"));
|
|
171
209
|
}
|
|
172
210
|
function createInvalidPathError() {
|
|
173
211
|
const error = new Error(storageM("storage_pathTraversal"));
|
|
@@ -182,7 +220,7 @@ function safePath(root, key) {
|
|
|
182
220
|
if (path.isAbsolute(key)) {
|
|
183
221
|
throw createInvalidPathError();
|
|
184
222
|
}
|
|
185
|
-
if (key.includes("\0") || hasPathTraversalSegment(key)) {
|
|
223
|
+
if (key.includes("\0") || hasPathTraversalSegment(key) || process.platform === "win32" && key.split(PATH_SEGMENT_SPLIT_REGEX).some((segment) => /[:. ]$/.test(segment) || segment.includes(":"))) {
|
|
186
224
|
throw createInvalidPathError();
|
|
187
225
|
}
|
|
188
226
|
const normalized = path.normalize(key);
|
|
@@ -203,52 +241,96 @@ function createLocalProvider() {
|
|
|
203
241
|
}
|
|
204
242
|
return config;
|
|
205
243
|
}
|
|
206
|
-
function fullPath(key) {
|
|
207
|
-
|
|
244
|
+
async function fullPath(key) {
|
|
245
|
+
const root = getConfig().root;
|
|
246
|
+
const target = safePath(root, key);
|
|
247
|
+
let current = root;
|
|
248
|
+
for (const segment of path.relative(root, target).split(path.sep).filter(Boolean)) {
|
|
249
|
+
current = path.join(current, segment);
|
|
250
|
+
try {
|
|
251
|
+
if ((await fsp.lstat(current)).isSymbolicLink())
|
|
252
|
+
throw createInvalidPathError();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (error.code === "ENOENT")
|
|
255
|
+
break;
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return target;
|
|
208
260
|
}
|
|
209
261
|
function logOperation(category, operation, payload) {
|
|
210
262
|
logStorageOperation(logger, config?.operationLog, category, operation, payload);
|
|
211
263
|
}
|
|
264
|
+
function metadataKey(filePath, stat2) {
|
|
265
|
+
return path.relative(getConfig().root, path.join(path.dirname(filePath), METADATA_DIRECTORY, `${metadataGeneration(stat2)}.meta.json`));
|
|
266
|
+
}
|
|
212
267
|
const file = {
|
|
213
268
|
async put(key, data, options = {}) {
|
|
214
269
|
if (isMetaFile(key)) {
|
|
215
270
|
return err(
|
|
216
271
|
HaiStorageError.INVALID_PATH,
|
|
217
|
-
storageM("storage_metaFileAccess", {
|
|
272
|
+
storageM("storage_metaFileAccess", {
|
|
273
|
+
params: { key }
|
|
274
|
+
})
|
|
218
275
|
);
|
|
219
276
|
}
|
|
220
277
|
let temporaryPath;
|
|
278
|
+
let preparedMetadataPath;
|
|
279
|
+
let published = false;
|
|
221
280
|
try {
|
|
222
|
-
const filePath = fullPath(key);
|
|
281
|
+
const filePath = await fullPath(key);
|
|
282
|
+
const cfg = getConfig();
|
|
283
|
+
let previousMetadataKey;
|
|
284
|
+
try {
|
|
285
|
+
previousMetadataKey = metadataKey(filePath, await fsp.stat(filePath, { bigint: true }));
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error.code !== "ENOENT")
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
223
290
|
logOperation("write", "file.put", { key, options });
|
|
224
291
|
const dir2 = path.dirname(filePath);
|
|
225
|
-
await fsp.mkdir(dir2, { recursive: true, mode:
|
|
292
|
+
await fsp.mkdir(dir2, { recursive: true, mode: cfg.directoryMode });
|
|
226
293
|
temporaryPath = `${filePath}.${crypto.randomUUID()}.upload.tmp`;
|
|
227
294
|
await pipeline(
|
|
228
295
|
Readable.from(toByteStream(data, options.signal), { objectMode: false }),
|
|
229
|
-
fs.createWriteStream(temporaryPath, { flags: "wx", mode:
|
|
296
|
+
fs.createWriteStream(temporaryPath, { flags: "wx", mode: cfg.fileMode }),
|
|
230
297
|
{ signal: options.signal }
|
|
231
298
|
);
|
|
232
299
|
options.signal?.throwIfAborted();
|
|
300
|
+
const stat2 = await fsp.stat(temporaryPath, { bigint: true });
|
|
301
|
+
const metaPath = await fullPath(metadataKey(filePath, stat2));
|
|
302
|
+
const serializedMetadata = JSON.stringify({ contentType: options.contentType, metadata: options.metadata });
|
|
303
|
+
await fsp.mkdir(path.dirname(metaPath), { recursive: true, mode: cfg.directoryMode });
|
|
304
|
+
const metadataFile = await fsp.open(metaPath, "wx", cfg.fileMode);
|
|
305
|
+
preparedMetadataPath = metaPath;
|
|
306
|
+
try {
|
|
307
|
+
await metadataFile.writeFile(serializedMetadata);
|
|
308
|
+
} finally {
|
|
309
|
+
await metadataFile.close();
|
|
310
|
+
}
|
|
311
|
+
options.signal?.throwIfAborted();
|
|
233
312
|
if (options.ifNoneMatch === "*")
|
|
234
313
|
await fsp.link(temporaryPath, filePath);
|
|
235
314
|
else
|
|
236
315
|
await fsp.rename(temporaryPath, filePath);
|
|
237
|
-
|
|
316
|
+
published = true;
|
|
238
317
|
const metadata = {
|
|
239
318
|
key,
|
|
240
|
-
size: stat2.size,
|
|
319
|
+
size: Number(stat2.size),
|
|
241
320
|
contentType: options.contentType || getMimeType2(key),
|
|
242
321
|
lastModified: stat2.mtime,
|
|
243
322
|
etag: calculateEtag(stat2),
|
|
244
323
|
metadata: options.metadata
|
|
245
324
|
};
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
})
|
|
325
|
+
if (previousMetadataKey) {
|
|
326
|
+
try {
|
|
327
|
+
const previousMetadataPath = await fullPath(previousMetadataKey);
|
|
328
|
+
if (previousMetadataPath !== metaPath)
|
|
329
|
+
await fsp.unlink(previousMetadataPath);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (error.code !== "ENOENT")
|
|
332
|
+
logger.warn("Obsolete local metadata cleanup failed", { key });
|
|
333
|
+
}
|
|
252
334
|
}
|
|
253
335
|
return ok(metadata);
|
|
254
336
|
} catch (error) {
|
|
@@ -258,6 +340,8 @@ function createLocalProvider() {
|
|
|
258
340
|
if (temporaryPath)
|
|
259
341
|
await fsp.unlink(temporaryPath).catch(() => {
|
|
260
342
|
});
|
|
343
|
+
if (preparedMetadataPath && !published)
|
|
344
|
+
await fsp.unlink(preparedMetadataPath).catch(() => logger.warn("Unpublished local metadata cleanup failed", { key }));
|
|
261
345
|
}
|
|
262
346
|
},
|
|
263
347
|
async get(key, options = {}) {
|
|
@@ -277,16 +361,21 @@ function createLocalProvider() {
|
|
|
277
361
|
if (isMetaFile(key)) {
|
|
278
362
|
return err(
|
|
279
363
|
HaiStorageError.INVALID_PATH,
|
|
280
|
-
storageM("storage_metaFileAccess", {
|
|
364
|
+
storageM("storage_metaFileAccess", {
|
|
365
|
+
params: { key }
|
|
366
|
+
})
|
|
281
367
|
);
|
|
282
368
|
}
|
|
283
369
|
try {
|
|
284
|
-
const filePath = fullPath(key);
|
|
370
|
+
const filePath = await fullPath(key);
|
|
285
371
|
logOperation("read", "file.getStream", { key, options });
|
|
286
372
|
options.signal?.throwIfAborted();
|
|
287
373
|
const stat2 = await fsp.stat(filePath);
|
|
288
|
-
if (stat2.isDirectory())
|
|
289
|
-
return err(HaiStorageError.INVALID_PATH, storageM("storage_pathIsDir", {
|
|
374
|
+
if (stat2.isDirectory()) {
|
|
375
|
+
return err(HaiStorageError.INVALID_PATH, storageM("storage_pathIsDir", {
|
|
376
|
+
params: { key }
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
290
379
|
async function* read() {
|
|
291
380
|
const stream = fs.createReadStream(filePath, { start: options.rangeStart, end: options.rangeEnd, signal: options.signal });
|
|
292
381
|
for await (const chunk of stream)
|
|
@@ -301,35 +390,59 @@ function createLocalProvider() {
|
|
|
301
390
|
if (isMetaFile(key)) {
|
|
302
391
|
return err(
|
|
303
392
|
HaiStorageError.INVALID_PATH,
|
|
304
|
-
storageM("storage_metaFileAccess", {
|
|
393
|
+
storageM("storage_metaFileAccess", {
|
|
394
|
+
params: { key }
|
|
395
|
+
})
|
|
305
396
|
);
|
|
306
397
|
}
|
|
307
398
|
try {
|
|
308
|
-
const filePath = fullPath(key);
|
|
399
|
+
const filePath = await fullPath(key);
|
|
309
400
|
logOperation("read", "file.head", { key });
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
HaiStorageError.INVALID_PATH,
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
401
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
402
|
+
const stat2 = await fsp.stat(filePath, { bigint: true });
|
|
403
|
+
if (stat2.isDirectory()) {
|
|
404
|
+
return err(HaiStorageError.INVALID_PATH, storageM("storage_pathIsDir", {
|
|
405
|
+
params: { key }
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
let serializedMetadata;
|
|
409
|
+
try {
|
|
410
|
+
const metaPath = await fullPath(metadataKey(filePath, stat2));
|
|
411
|
+
serializedMetadata = await fsp.readFile(metaPath, "utf8");
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (!["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
|
|
414
|
+
throw error;
|
|
415
|
+
if (metadataGeneration(await fsp.stat(filePath, { bigint: true })) !== metadataGeneration(stat2))
|
|
416
|
+
continue;
|
|
417
|
+
return err(HaiStorageError.OPERATION_FAILED, storageM("storage_metadataRequired"));
|
|
418
|
+
}
|
|
419
|
+
let customMetadata;
|
|
420
|
+
try {
|
|
421
|
+
customMetadata = JSON.parse(serializedMetadata);
|
|
422
|
+
} catch {
|
|
423
|
+
logger.warn("Invalid local metadata record", {
|
|
424
|
+
key,
|
|
425
|
+
generation: metadataGeneration(stat2)
|
|
426
|
+
});
|
|
427
|
+
return err(HaiStorageError.OPERATION_FAILED, storageM("storage_metadataInvalid"));
|
|
428
|
+
}
|
|
429
|
+
if (!isMetadataRecord(customMetadata)) {
|
|
430
|
+
logger.warn("Invalid local metadata shape", {
|
|
431
|
+
key,
|
|
432
|
+
generation: metadataGeneration(stat2)
|
|
433
|
+
});
|
|
434
|
+
return err(HaiStorageError.OPERATION_FAILED, storageM("storage_metadataInvalid"));
|
|
435
|
+
}
|
|
436
|
+
return ok({
|
|
437
|
+
key,
|
|
438
|
+
size: Number(stat2.size),
|
|
439
|
+
contentType: customMetadata.contentType || getMimeType2(key),
|
|
440
|
+
lastModified: stat2.mtime,
|
|
441
|
+
etag: calculateEtag(stat2),
|
|
442
|
+
metadata: customMetadata.metadata
|
|
443
|
+
});
|
|
323
444
|
}
|
|
324
|
-
|
|
325
|
-
return ok({
|
|
326
|
-
key,
|
|
327
|
-
size: stat2.size,
|
|
328
|
-
contentType: customMetadata.contentType || getMimeType2(key),
|
|
329
|
-
lastModified: stat2.mtime,
|
|
330
|
-
etag,
|
|
331
|
-
metadata: customMetadata.metadata
|
|
332
|
-
});
|
|
445
|
+
return err(HaiStorageError.OPERATION_FAILED, storageM("storage_fileChanged"));
|
|
333
446
|
} catch (error) {
|
|
334
447
|
return err(toStorageError(error, key));
|
|
335
448
|
}
|
|
@@ -338,11 +451,13 @@ function createLocalProvider() {
|
|
|
338
451
|
if (isMetaFile(key)) {
|
|
339
452
|
return err(
|
|
340
453
|
HaiStorageError.INVALID_PATH,
|
|
341
|
-
storageM("storage_metaFileAccess", {
|
|
454
|
+
storageM("storage_metaFileAccess", {
|
|
455
|
+
params: { key }
|
|
456
|
+
})
|
|
342
457
|
);
|
|
343
458
|
}
|
|
344
459
|
try {
|
|
345
|
-
const filePath = fullPath(key);
|
|
460
|
+
const filePath = await fullPath(key);
|
|
346
461
|
logOperation("read", "file.exists", { key });
|
|
347
462
|
await fsp.access(filePath, fs.constants.F_OK);
|
|
348
463
|
return ok(true);
|
|
@@ -357,17 +472,22 @@ function createLocalProvider() {
|
|
|
357
472
|
if (isMetaFile(key)) {
|
|
358
473
|
return err(
|
|
359
474
|
HaiStorageError.INVALID_PATH,
|
|
360
|
-
storageM("storage_metaFileAccess", {
|
|
475
|
+
storageM("storage_metaFileAccess", {
|
|
476
|
+
params: { key }
|
|
477
|
+
})
|
|
361
478
|
);
|
|
362
479
|
}
|
|
363
480
|
try {
|
|
364
|
-
const filePath = fullPath(key);
|
|
481
|
+
const filePath = await fullPath(key);
|
|
482
|
+
const generationKey = metadataKey(filePath, await fsp.stat(filePath, { bigint: true }));
|
|
365
483
|
logOperation("write", "file.delete", { key });
|
|
366
484
|
logger.debug("Deleting file", { key });
|
|
367
485
|
await fsp.unlink(filePath);
|
|
368
486
|
try {
|
|
369
|
-
await fsp.unlink(
|
|
370
|
-
} catch {
|
|
487
|
+
await fsp.unlink(await fullPath(generationKey));
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (error.code !== "ENOENT")
|
|
490
|
+
logger.warn("Deleted object metadata cleanup failed", { key });
|
|
371
491
|
}
|
|
372
492
|
return ok(void 0);
|
|
373
493
|
} catch (error) {
|
|
@@ -381,57 +501,47 @@ function createLocalProvider() {
|
|
|
381
501
|
async deleteMany(keys) {
|
|
382
502
|
logOperation("write", "file.deleteMany", { count: keys.length, keys });
|
|
383
503
|
const summary = { deletedKeys: [], failures: [] };
|
|
384
|
-
const uniqueKeys = [
|
|
504
|
+
const uniqueKeys = [
|
|
505
|
+
...new Set(keys)
|
|
506
|
+
];
|
|
385
507
|
for (let offset = 0; offset < uniqueKeys.length; offset += 32) {
|
|
386
508
|
const batch = uniqueKeys.slice(offset, offset + 32);
|
|
387
509
|
const results = await Promise.allSettled(batch.map((key) => file.delete(key)));
|
|
388
510
|
for (const [index, result] of results.entries()) {
|
|
389
511
|
const key = batch[index];
|
|
390
|
-
if (result.status === "rejected")
|
|
391
|
-
summary.failures.push({
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
512
|
+
if (result.status === "rejected") {
|
|
513
|
+
summary.failures.push({
|
|
514
|
+
key,
|
|
515
|
+
code: "UnexpectedDeleteFailure",
|
|
516
|
+
retryable: false,
|
|
517
|
+
outcome: "failed"
|
|
518
|
+
});
|
|
519
|
+
} else if (!result.value.success) {
|
|
520
|
+
summary.failures.push({
|
|
521
|
+
key,
|
|
522
|
+
code: String(result.value.error.code),
|
|
523
|
+
retryable: false,
|
|
524
|
+
outcome: "failed"
|
|
525
|
+
});
|
|
526
|
+
} else {
|
|
395
527
|
summary.deletedKeys.push(key);
|
|
528
|
+
}
|
|
396
529
|
}
|
|
397
530
|
}
|
|
398
531
|
return toStorageDeleteResult(summary);
|
|
399
532
|
},
|
|
400
533
|
async copy(sourceKey, destKey, options = {}) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
logger.debug("Copying file", { sourceKey, destKey });
|
|
413
|
-
await fsp.mkdir(path.dirname(destPath), { recursive: true, mode: getConfig().directoryMode });
|
|
414
|
-
await fsp.copyFile(sourcePath, destPath);
|
|
415
|
-
const stat2 = await fsp.stat(destPath);
|
|
416
|
-
const metadata = {
|
|
417
|
-
key: destKey,
|
|
418
|
-
size: stat2.size,
|
|
419
|
-
contentType: options.contentType || getMimeType2(destKey),
|
|
420
|
-
lastModified: stat2.mtime,
|
|
421
|
-
etag: calculateEtag(stat2),
|
|
422
|
-
metadata: options.metadata
|
|
423
|
-
};
|
|
424
|
-
if (options.contentType || options.metadata) {
|
|
425
|
-
const metaPath = `${destPath}.meta.json`;
|
|
426
|
-
await fsp.writeFile(metaPath, JSON.stringify({
|
|
427
|
-
contentType: options.contentType,
|
|
428
|
-
metadata: options.metadata
|
|
429
|
-
}));
|
|
430
|
-
}
|
|
431
|
-
return ok(metadata);
|
|
432
|
-
} catch (error) {
|
|
433
|
-
return err(toStorageError(error, sourceKey));
|
|
434
|
-
}
|
|
534
|
+
logOperation("write", "file.copy", { sourceKey, destKey, options });
|
|
535
|
+
const source = await file.head(sourceKey);
|
|
536
|
+
if (!source.success)
|
|
537
|
+
return source;
|
|
538
|
+
const stream = await file.getStream(sourceKey);
|
|
539
|
+
if (!stream.success)
|
|
540
|
+
return stream;
|
|
541
|
+
return file.put(destKey, stream.data, {
|
|
542
|
+
contentType: options.contentType ?? source.data.contentType,
|
|
543
|
+
metadata: options.metadata ?? source.data.metadata
|
|
544
|
+
});
|
|
435
545
|
}
|
|
436
546
|
};
|
|
437
547
|
const dir = {
|
|
@@ -507,7 +617,12 @@ function createLocalProvider() {
|
|
|
507
617
|
commonPrefixes.push(logicalKey);
|
|
508
618
|
} else {
|
|
509
619
|
const stat2 = await fsp.stat(entry.filePath);
|
|
510
|
-
files.push({
|
|
620
|
+
files.push({
|
|
621
|
+
key: entry.key,
|
|
622
|
+
size: stat2.size,
|
|
623
|
+
contentType: getMimeType2(entry.key),
|
|
624
|
+
lastModified: stat2.mtime
|
|
625
|
+
});
|
|
511
626
|
}
|
|
512
627
|
count++;
|
|
513
628
|
last = logicalKey;
|
|
@@ -516,15 +631,22 @@ function createLocalProvider() {
|
|
|
516
631
|
files,
|
|
517
632
|
commonPrefixes,
|
|
518
633
|
isTruncated,
|
|
519
|
-
...isTruncated ? {
|
|
634
|
+
...isTruncated ? {
|
|
635
|
+
nextContinuationToken: Buffer.from(JSON.stringify([1, scope, last])).toString("base64url")
|
|
636
|
+
} : {}
|
|
520
637
|
});
|
|
521
638
|
} catch (error) {
|
|
522
639
|
return err(toStorageError(error));
|
|
523
640
|
}
|
|
524
641
|
},
|
|
525
642
|
async delete(prefix) {
|
|
643
|
+
if (isMetaFile(prefix)) {
|
|
644
|
+
return err(HaiStorageError.INVALID_PATH, storageM("storage_metaFileAccess", {
|
|
645
|
+
params: { key: prefix }
|
|
646
|
+
}));
|
|
647
|
+
}
|
|
526
648
|
try {
|
|
527
|
-
const dirPath = fullPath(prefix);
|
|
649
|
+
const dirPath = await fullPath(prefix);
|
|
528
650
|
logOperation("write", "dir.delete", { prefix });
|
|
529
651
|
await fsp.rm(dirPath, { recursive: true, force: true });
|
|
530
652
|
return ok(void 0);
|
|
@@ -566,6 +688,7 @@ function createLocalProvider() {
|
|
|
566
688
|
config = cfg;
|
|
567
689
|
logger.info("Connecting local provider", { root: cfg.root });
|
|
568
690
|
await fsp.mkdir(cfg.root, { recursive: true, mode: cfg.directoryMode });
|
|
691
|
+
config = { ...cfg, root: await fsp.realpath(cfg.root) };
|
|
569
692
|
connected = true;
|
|
570
693
|
logger.info("Local provider connected", { root: cfg.root });
|
|
571
694
|
return ok(void 0);
|
|
@@ -589,22 +712,38 @@ var logger2 = core.logger.child({ module: "storage", scope: "provider-s3" });
|
|
|
589
712
|
var TRAILING_SLASHES_REGEX = /\/+$/;
|
|
590
713
|
var LEADING_SLASHES_REGEX = /^\/+/;
|
|
591
714
|
var DELETE_BATCH_SIZE = 1e3;
|
|
592
|
-
var RETRYABLE_DELETE_CODES = /* @__PURE__ */ new Set([
|
|
715
|
+
var RETRYABLE_DELETE_CODES = /* @__PURE__ */ new Set([
|
|
716
|
+
"InternalError",
|
|
717
|
+
"ServiceUnavailable",
|
|
718
|
+
"SlowDown",
|
|
719
|
+
"RequestTimeout"
|
|
720
|
+
]);
|
|
593
721
|
function toStorageError2(error, key) {
|
|
594
722
|
const e = error;
|
|
595
|
-
if (e.name === "PreconditionFailed" || e.$metadata?.httpStatusCode === 412)
|
|
596
|
-
return {
|
|
723
|
+
if (e.name === "PreconditionFailed" || e.$metadata?.httpStatusCode === 412) {
|
|
724
|
+
return {
|
|
725
|
+
...HaiStorageError.ALREADY_EXISTS,
|
|
726
|
+
message: storageM("storage_fileAlreadyExists", {
|
|
727
|
+
params: { key: key ?? "" }
|
|
728
|
+
}),
|
|
729
|
+
cause: error
|
|
730
|
+
};
|
|
731
|
+
}
|
|
597
732
|
if (e.name === "NoSuchKey" || e.Code === "NoSuchKey" || e.name === "NotFound" || e.Code === "NotFound" || e.name === "404" || e.$metadata?.httpStatusCode === 404) {
|
|
598
733
|
return {
|
|
599
734
|
...HaiStorageError.NOT_FOUND,
|
|
600
|
-
message: storageM("storage_fileNotFound", {
|
|
735
|
+
message: storageM("storage_fileNotFound", {
|
|
736
|
+
params: { key: key ?? "" }
|
|
737
|
+
}),
|
|
601
738
|
cause: error
|
|
602
739
|
};
|
|
603
740
|
}
|
|
604
741
|
if (e.name === "AccessDenied" || e.Code === "AccessDenied") {
|
|
605
742
|
return {
|
|
606
743
|
...HaiStorageError.PERMISSION_DENIED,
|
|
607
|
-
message: storageM("storage_permissionDenied", {
|
|
744
|
+
message: storageM("storage_permissionDenied", {
|
|
745
|
+
params: { key: key ?? "" }
|
|
746
|
+
}),
|
|
608
747
|
cause: error
|
|
609
748
|
};
|
|
610
749
|
}
|
|
@@ -624,7 +763,9 @@ function toStorageError2(error, key) {
|
|
|
624
763
|
}
|
|
625
764
|
return {
|
|
626
765
|
...HaiStorageError.OPERATION_FAILED,
|
|
627
|
-
message: storageM("storage_operationFailed", {
|
|
766
|
+
message: storageM("storage_operationFailed", {
|
|
767
|
+
params: { error: e.message ?? "" }
|
|
768
|
+
}),
|
|
628
769
|
cause: error
|
|
629
770
|
};
|
|
630
771
|
}
|
|
@@ -636,7 +777,7 @@ function withPrefix(key, prefix) {
|
|
|
636
777
|
function withoutPrefix(key, prefix) {
|
|
637
778
|
if (!prefix)
|
|
638
779
|
return key;
|
|
639
|
-
const normalizedPrefix =
|
|
780
|
+
const normalizedPrefix = withPrefix("", prefix);
|
|
640
781
|
if (key.startsWith(normalizedPrefix)) {
|
|
641
782
|
return key.slice(normalizedPrefix.length);
|
|
642
783
|
}
|
|
@@ -724,7 +865,12 @@ function createS3Provider() {
|
|
|
724
865
|
ContentDisposition: options.contentDisposition,
|
|
725
866
|
IfNoneMatch: options.ifNoneMatch
|
|
726
867
|
};
|
|
727
|
-
logOperation("write", "file.put", {
|
|
868
|
+
logOperation("write", "file.put", {
|
|
869
|
+
key,
|
|
870
|
+
fullPath,
|
|
871
|
+
byteLength: buffered ? size : void 0,
|
|
872
|
+
options
|
|
873
|
+
});
|
|
728
874
|
if (buffered) {
|
|
729
875
|
await s3Client.send(new PutObjectCommand({ ...params, Body: body }), { abortSignal: options.signal });
|
|
730
876
|
} else {
|
|
@@ -742,7 +888,13 @@ function createS3Provider() {
|
|
|
742
888
|
if (options.signal?.aborted)
|
|
743
889
|
abort();
|
|
744
890
|
const { Upload } = await import('@aws-sdk/lib-storage');
|
|
745
|
-
await new Upload({
|
|
891
|
+
await new Upload({
|
|
892
|
+
client: s3Client,
|
|
893
|
+
params: { ...params, Body: stream },
|
|
894
|
+
queueSize: 2,
|
|
895
|
+
leavePartsOnError: false,
|
|
896
|
+
abortController: controller
|
|
897
|
+
}).done();
|
|
746
898
|
} finally {
|
|
747
899
|
options.signal?.removeEventListener("abort", abort);
|
|
748
900
|
stream.destroy();
|
|
@@ -857,7 +1009,9 @@ function createS3Provider() {
|
|
|
857
1009
|
const s3Client = getClient("deleteMany");
|
|
858
1010
|
const s3Config = getConfig();
|
|
859
1011
|
logOperation("write", "file.deleteMany", { count: keys.length, keys });
|
|
860
|
-
const uniqueKeys = [
|
|
1012
|
+
const uniqueKeys = [
|
|
1013
|
+
...new Set(keys)
|
|
1014
|
+
];
|
|
861
1015
|
const summary = { deletedKeys: [], failures: [] };
|
|
862
1016
|
for (let offset = 0; offset < uniqueKeys.length; offset += DELETE_BATCH_SIZE) {
|
|
863
1017
|
let pending = uniqueKeys.slice(offset, offset + DELETE_BATCH_SIZE);
|
|
@@ -866,7 +1020,12 @@ function createS3Provider() {
|
|
|
866
1020
|
try {
|
|
867
1021
|
response = await s3Client.send(new DeleteObjectsCommand({
|
|
868
1022
|
Bucket: s3Config.bucket,
|
|
869
|
-
Delete: {
|
|
1023
|
+
Delete: {
|
|
1024
|
+
Objects: [
|
|
1025
|
+
...new Set(pending.map(fullKey))
|
|
1026
|
+
].map((Key) => ({ Key })),
|
|
1027
|
+
Quiet: false
|
|
1028
|
+
}
|
|
870
1029
|
}));
|
|
871
1030
|
} catch (cause) {
|
|
872
1031
|
const failure = cause && typeof cause === "object" ? cause : {};
|
|
@@ -875,7 +1034,12 @@ function createS3Provider() {
|
|
|
875
1034
|
const retryable = status === void 0 || status === 408 || status === 429 || status >= 500;
|
|
876
1035
|
const outcome = status !== void 0 && status >= 400 && status < 500 && status !== 408 ? "failed" : "unknown";
|
|
877
1036
|
summary.failures.push(...pending.map((key) => ({ key, code, retryable, outcome })));
|
|
878
|
-
summary.failures.push(...uniqueKeys.slice(offset + DELETE_BATCH_SIZE).map((key) => ({
|
|
1037
|
+
summary.failures.push(...uniqueKeys.slice(offset + DELETE_BATCH_SIZE).map((key) => ({
|
|
1038
|
+
key,
|
|
1039
|
+
code,
|
|
1040
|
+
retryable,
|
|
1041
|
+
outcome: "notAttempted"
|
|
1042
|
+
})));
|
|
879
1043
|
return toStorageDeleteResult(summary);
|
|
880
1044
|
}
|
|
881
1045
|
const outcomes = /* @__PURE__ */ new Map();
|
|
@@ -883,7 +1047,10 @@ function createS3Provider() {
|
|
|
883
1047
|
let malformed = false;
|
|
884
1048
|
const items = [
|
|
885
1049
|
...(response.Deleted ?? []).map((item) => ({ key: item.Key, code: null })),
|
|
886
|
-
...(response.Errors ?? []).map((item) => ({
|
|
1050
|
+
...(response.Errors ?? []).map((item) => ({
|
|
1051
|
+
key: item.Key,
|
|
1052
|
+
code: item.Code && /^[\w.-]{1,80}$/.test(item.Code) ? item.Code : "UnknownError"
|
|
1053
|
+
}))
|
|
887
1054
|
];
|
|
888
1055
|
for (const item of items) {
|
|
889
1056
|
if (!item.key || !requested.has(item.key) || outcomes.has(item.key)) {
|
|
@@ -893,19 +1060,35 @@ function createS3Provider() {
|
|
|
893
1060
|
outcomes.set(item.key, item.code);
|
|
894
1061
|
}
|
|
895
1062
|
if (malformed || outcomes.size !== requested.size) {
|
|
896
|
-
summary.failures.push(...pending.map((key) => ({
|
|
897
|
-
|
|
1063
|
+
summary.failures.push(...pending.map((key) => ({
|
|
1064
|
+
key,
|
|
1065
|
+
code: "InvalidDeleteResponse",
|
|
1066
|
+
retryable: false,
|
|
1067
|
+
outcome: "unknown"
|
|
1068
|
+
})));
|
|
1069
|
+
summary.failures.push(...uniqueKeys.slice(offset + DELETE_BATCH_SIZE).map((key) => ({
|
|
1070
|
+
key,
|
|
1071
|
+
code: "InvalidDeleteResponse",
|
|
1072
|
+
retryable: false,
|
|
1073
|
+
outcome: "notAttempted"
|
|
1074
|
+
})));
|
|
898
1075
|
return toStorageDeleteResult(summary);
|
|
899
1076
|
}
|
|
900
1077
|
const retry = [];
|
|
901
1078
|
for (const key of pending) {
|
|
902
1079
|
const code = outcomes.get(fullKey(key));
|
|
903
|
-
if (code === null)
|
|
1080
|
+
if (code === null) {
|
|
904
1081
|
summary.deletedKeys.push(key);
|
|
905
|
-
else if (code && RETRYABLE_DELETE_CODES.has(code) && attempt < 2)
|
|
1082
|
+
} else if (code && RETRYABLE_DELETE_CODES.has(code) && attempt < 2) {
|
|
906
1083
|
retry.push(key);
|
|
907
|
-
else
|
|
908
|
-
summary.failures.push({
|
|
1084
|
+
} else {
|
|
1085
|
+
summary.failures.push({
|
|
1086
|
+
key,
|
|
1087
|
+
code: code ?? "UnknownError",
|
|
1088
|
+
retryable: !!code && RETRYABLE_DELETE_CODES.has(code),
|
|
1089
|
+
outcome: "failed"
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
909
1092
|
}
|
|
910
1093
|
pending = retry;
|
|
911
1094
|
if (pending.length > 0)
|
|
@@ -923,10 +1106,20 @@ function createS3Provider() {
|
|
|
923
1106
|
const s3Config = getConfig();
|
|
924
1107
|
const sourceFullPath = fullKey(sourceKey);
|
|
925
1108
|
const destFullPath = fullKey(destKey);
|
|
926
|
-
|
|
1109
|
+
const copySource = [
|
|
1110
|
+
s3Config.bucket,
|
|
1111
|
+
...sourceFullPath.split("/")
|
|
1112
|
+
].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
|
+
});
|
|
927
1120
|
await s3Client.send(new CopyObjectCommand({
|
|
928
1121
|
Bucket: s3Config.bucket,
|
|
929
|
-
CopySource:
|
|
1122
|
+
CopySource: copySource,
|
|
930
1123
|
Key: destFullPath,
|
|
931
1124
|
ContentType: options.contentType,
|
|
932
1125
|
Metadata: options.metadata,
|
|
@@ -944,13 +1137,9 @@ function createS3Provider() {
|
|
|
944
1137
|
const s3Client = getClient();
|
|
945
1138
|
const s3Config = getConfig();
|
|
946
1139
|
logOperation("read", "dir.list", { options });
|
|
947
|
-
let prefix = s3Config.prefix || "";
|
|
948
|
-
if (options.prefix) {
|
|
949
|
-
prefix = withPrefix(options.prefix, s3Config.prefix);
|
|
950
|
-
}
|
|
951
1140
|
const response = await s3Client.send(new ListObjectsV2Command({
|
|
952
1141
|
Bucket: s3Config.bucket,
|
|
953
|
-
Prefix: prefix || void 0,
|
|
1142
|
+
Prefix: withPrefix(options.prefix ?? "", s3Config.prefix) || void 0,
|
|
954
1143
|
Delimiter: options.delimiter,
|
|
955
1144
|
MaxKeys: options.maxKeys,
|
|
956
1145
|
ContinuationToken: options.continuationToken
|
|
@@ -1101,7 +1290,11 @@ function createS3Provider() {
|
|
|
1101
1290
|
if (cfg.publicEndpoint) {
|
|
1102
1291
|
const endpoint = new URL(cfg.publicEndpoint);
|
|
1103
1292
|
publicBasePath = endpoint.pathname.replace(TRAILING_SLASHES_REGEX, "");
|
|
1104
|
-
presignClient = new S3Client({
|
|
1293
|
+
presignClient = new S3Client({
|
|
1294
|
+
...clientConfig,
|
|
1295
|
+
endpoint: endpoint.origin,
|
|
1296
|
+
forcePathStyle: publicBasePath ? true : cfg.forcePathStyle
|
|
1297
|
+
});
|
|
1105
1298
|
}
|
|
1106
1299
|
if (cfg.createBucket) {
|
|
1107
1300
|
try {
|
|
@@ -1114,7 +1307,9 @@ function createS3Provider() {
|
|
|
1114
1307
|
await client.send(new CreateBucketCommand({
|
|
1115
1308
|
Bucket: cfg.bucket,
|
|
1116
1309
|
// AWS us-east-1 要省略 LocationConstraint;兼容服务可使用自定义区域名。
|
|
1117
|
-
...cfg.region === "us-east-1" ? {} : {
|
|
1310
|
+
...cfg.region === "us-east-1" ? {} : {
|
|
1311
|
+
CreateBucketConfiguration: { LocationConstraint: cfg.region }
|
|
1312
|
+
}
|
|
1118
1313
|
}));
|
|
1119
1314
|
logger2.info("S3 bucket created", { bucket: cfg.bucket });
|
|
1120
1315
|
} catch (createError) {
|
|
@@ -1126,6 +1321,7 @@ function createS3Provider() {
|
|
|
1126
1321
|
}
|
|
1127
1322
|
await client.send(new ListObjectsV2Command({
|
|
1128
1323
|
Bucket: cfg.bucket,
|
|
1324
|
+
Prefix: withPrefix("", cfg.prefix) || void 0,
|
|
1129
1325
|
MaxKeys: 1
|
|
1130
1326
|
}));
|
|
1131
1327
|
config = cfg;
|
|
@@ -1139,7 +1335,10 @@ function createS3Provider() {
|
|
|
1139
1335
|
await closeClients();
|
|
1140
1336
|
return err(original);
|
|
1141
1337
|
} catch (cleanupError) {
|
|
1142
|
-
return err({
|
|
1338
|
+
return err({
|
|
1339
|
+
...original,
|
|
1340
|
+
ext: { ...original.ext, cleanupPending: true, cleanupError }
|
|
1341
|
+
});
|
|
1143
1342
|
}
|
|
1144
1343
|
}
|
|
1145
1344
|
},
|
|
@@ -1151,7 +1350,13 @@ function createS3Provider() {
|
|
|
1151
1350
|
}
|
|
1152
1351
|
var HEX_CHECKSUM = /^[\da-f]+$/i;
|
|
1153
1352
|
var BASE64_CHECKSUM = /^(?:[A-Z\d+/]{4})*(?:[A-Z\d+/]{2}==|[A-Z\d+/]{3}=)?$/i;
|
|
1154
|
-
var ALGORITHMS = /* @__PURE__ */ new Set([
|
|
1353
|
+
var ALGORITHMS = /* @__PURE__ */ new Set([
|
|
1354
|
+
"sha256",
|
|
1355
|
+
"sha384",
|
|
1356
|
+
"sha512",
|
|
1357
|
+
"sha1",
|
|
1358
|
+
"md5"
|
|
1359
|
+
]);
|
|
1155
1360
|
async function generate(data, options = {}) {
|
|
1156
1361
|
try {
|
|
1157
1362
|
const algorithm = options.algorithm ?? "sha256";
|
|
@@ -1203,9 +1408,16 @@ async function closeOwnedProvider() {
|
|
|
1203
1408
|
return ok(void 0);
|
|
1204
1409
|
} catch (error) {
|
|
1205
1410
|
logger3.error("Provider cleanup failed; retaining resource for retry", { error });
|
|
1206
|
-
return err({
|
|
1207
|
-
|
|
1208
|
-
|
|
1411
|
+
return err({
|
|
1412
|
+
...HaiStorageError.CONNECTION_FAILED,
|
|
1413
|
+
message: storageM("storage_operationFailed", {
|
|
1414
|
+
params: {
|
|
1415
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1416
|
+
}
|
|
1417
|
+
}),
|
|
1418
|
+
cause: error,
|
|
1419
|
+
ext: { cleanupPending: true }
|
|
1420
|
+
});
|
|
1209
1421
|
}
|
|
1210
1422
|
}
|
|
1211
1423
|
async function failInitialization(result) {
|
|
@@ -1216,9 +1428,15 @@ async function failInitialization(result) {
|
|
|
1216
1428
|
const ext = { ...result.error.ext };
|
|
1217
1429
|
delete ext.cleanupPending;
|
|
1218
1430
|
delete ext.cleanupError;
|
|
1219
|
-
return err({
|
|
1431
|
+
return err({
|
|
1432
|
+
...result.error,
|
|
1433
|
+
ext: Object.keys(ext).length > 0 ? ext : void 0
|
|
1434
|
+
});
|
|
1220
1435
|
}
|
|
1221
|
-
return err({
|
|
1436
|
+
return err({
|
|
1437
|
+
...result.error,
|
|
1438
|
+
ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error }
|
|
1439
|
+
});
|
|
1222
1440
|
}
|
|
1223
1441
|
function createProvider(config) {
|
|
1224
1442
|
switch (config.type) {
|
|
@@ -1227,7 +1445,9 @@ function createProvider(config) {
|
|
|
1227
1445
|
case "local":
|
|
1228
1446
|
return createLocalProvider();
|
|
1229
1447
|
default:
|
|
1230
|
-
throw new Error(storageM("storage_unsupportedType", {
|
|
1448
|
+
throw new Error(storageM("storage_unsupportedType", {
|
|
1449
|
+
params: { type: config.type }
|
|
1450
|
+
}));
|
|
1231
1451
|
}
|
|
1232
1452
|
}
|
|
1233
1453
|
var notInitialized = core.module.createNotInitializedKit(
|
|
@@ -1268,7 +1488,9 @@ var storage = {
|
|
|
1268
1488
|
logger3.warn("Storage init already in progress, skipping concurrent call");
|
|
1269
1489
|
return err(
|
|
1270
1490
|
HaiStorageError.OPERATION_FAILED,
|
|
1271
|
-
storageM("storage_operationFailed", {
|
|
1491
|
+
storageM("storage_operationFailed", {
|
|
1492
|
+
params: { error: "Concurrent initialization detected" }
|
|
1493
|
+
})
|
|
1272
1494
|
);
|
|
1273
1495
|
}
|
|
1274
1496
|
initInProgress = true;
|
|
@@ -1286,7 +1508,9 @@ var storage = {
|
|
|
1286
1508
|
logger3.error("Storage config validation failed", { error: parseResult.error.message });
|
|
1287
1509
|
return err(
|
|
1288
1510
|
HaiStorageError.CONFIG_ERROR,
|
|
1289
|
-
storageM("storage_configError", {
|
|
1511
|
+
storageM("storage_configError", {
|
|
1512
|
+
params: { error: parseResult.error.message }
|
|
1513
|
+
}),
|
|
1290
1514
|
parseResult.error
|
|
1291
1515
|
);
|
|
1292
1516
|
}
|
|
@@ -1313,7 +1537,9 @@ var storage = {
|
|
|
1313
1537
|
return await failInitialization(err(
|
|
1314
1538
|
HaiStorageError.CONNECTION_FAILED,
|
|
1315
1539
|
storageM("storage_operationFailed", {
|
|
1316
|
-
params: {
|
|
1540
|
+
params: {
|
|
1541
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1542
|
+
}
|
|
1317
1543
|
}),
|
|
1318
1544
|
error
|
|
1319
1545
|
));
|