@wener/common 2.0.2 → 2.0.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/ai/qwen3vl/index.js +2 -0
- package/lib/ai/qwen3vl/index.js.map +1 -0
- package/lib/ai/qwen3vl/utils.js +31 -0
- package/lib/ai/qwen3vl/utils.js.map +1 -0
- package/lib/ai/vision/DocLayoutElementTypeSchema.js +28 -0
- package/lib/ai/vision/DocLayoutElementTypeSchema.js.map +1 -0
- package/lib/ai/vision/ImageAnnotationSchema.js +50 -0
- package/lib/ai/vision/ImageAnnotationSchema.js.map +1 -0
- package/lib/ai/vision/index.js +3 -0
- package/lib/ai/vision/index.js.map +1 -0
- package/lib/ai/vision/resolveImageAnnotation.js +105 -0
- package/lib/ai/vision/resolveImageAnnotation.js.map +1 -0
- package/lib/cn/ChineseResidentIdNo.js +21 -14
- package/lib/cn/ChineseResidentIdNo.js.map +1 -0
- package/lib/cn/ChineseResidentIdNo.test.js +1 -1
- package/lib/cn/DivisionCode.js +30 -25
- package/lib/cn/DivisionCode.js.map +1 -0
- package/lib/cn/DivisionCode.test.js +1 -1
- package/lib/cn/Mod11.js +38 -81
- package/lib/cn/Mod11.js.map +1 -0
- package/lib/cn/Mod31.js +41 -90
- package/lib/cn/Mod31.js.map +1 -0
- package/lib/cn/UnifiedSocialCreditCode.js +43 -34
- package/lib/cn/UnifiedSocialCreditCode.js.map +1 -0
- package/lib/cn/UnifiedSocialCreditCode.test.js +1 -1
- package/lib/cn/formatChineseAmount.js +77 -0
- package/lib/cn/formatChineseAmount.js.map +1 -0
- package/lib/cn/index.js +7 -1
- package/lib/cn/index.js.map +1 -0
- package/lib/cn/parseChineseNumber.js +94 -0
- package/lib/cn/parseChineseNumber.js.map +1 -0
- package/lib/cn/parseChineseNumber.test.js +278 -0
- package/lib/cn/pinyin/cartesianProduct.js +22 -0
- package/lib/cn/pinyin/cartesianProduct.js.map +1 -0
- package/lib/cn/pinyin/cartesianProduct.test.js +179 -0
- package/lib/cn/pinyin/data.json +23573 -0
- package/lib/cn/pinyin/loader.js +14 -0
- package/lib/cn/pinyin/loader.js.map +1 -0
- package/lib/cn/pinyin/preload.js +3 -0
- package/lib/cn/pinyin/preload.js.map +1 -0
- package/lib/cn/pinyin/toPinyin.test.js +167 -0
- package/lib/cn/pinyin/toPinyinPure.js +33 -0
- package/lib/cn/pinyin/toPinyinPure.js.map +1 -0
- package/lib/cn/pinyin/transform.js +14 -0
- package/lib/cn/pinyin/transform.js.map +1 -0
- package/lib/cn/types.d.js +2 -0
- package/lib/cn/types.d.js.map +1 -0
- package/lib/consola/createStandardConsolaReporter.js +6 -6
- package/lib/consola/createStandardConsolaReporter.js.map +1 -0
- package/lib/consola/formatLogObject.js +65 -145
- package/lib/consola/formatLogObject.js.map +1 -0
- package/lib/consola/formatLogObject.test.js +184 -0
- package/lib/consola/index.js +1 -0
- package/lib/consola/index.js.map +1 -0
- package/lib/data/formatSort.js +6 -5
- package/lib/data/formatSort.js.map +1 -0
- package/lib/data/index.js +1 -0
- package/lib/data/index.js.map +1 -0
- package/lib/data/maybeNumber.js +5 -7
- package/lib/data/maybeNumber.js.map +1 -0
- package/lib/data/parseSort.js +22 -28
- package/lib/data/parseSort.js.map +1 -0
- package/lib/data/resolvePagination.js +13 -17
- package/lib/data/resolvePagination.js.map +1 -0
- package/lib/data/types.d.js +2 -0
- package/lib/data/types.d.js.map +1 -0
- package/lib/dayjs/dayjs.js +21 -19
- package/lib/dayjs/dayjs.js.map +1 -0
- package/lib/dayjs/formatDuration.js +15 -14
- package/lib/dayjs/formatDuration.js.map +1 -0
- package/lib/dayjs/index.js +2 -0
- package/lib/dayjs/index.js.map +1 -0
- package/lib/dayjs/parseDuration.js +5 -8
- package/lib/dayjs/parseDuration.js.map +1 -0
- package/lib/dayjs/parseRelativeTime.js +90 -0
- package/lib/dayjs/parseRelativeTime.js.map +1 -0
- package/lib/dayjs/parseRelativeTime.test.js +247 -0
- package/lib/dayjs/resolveRelativeTime.js +158 -0
- package/lib/dayjs/resolveRelativeTime.js.map +1 -0
- package/lib/dayjs/resolveRelativeTime.test.js +310 -0
- package/lib/decimal/index.js +1 -0
- package/lib/decimal/index.js.map +1 -0
- package/lib/decimal/parseDecimal.js +3 -1
- package/lib/decimal/parseDecimal.js.map +1 -0
- package/lib/emittery/emitter.js +10 -0
- package/lib/emittery/emitter.js.map +1 -0
- package/lib/emittery/index.js +2 -0
- package/lib/emittery/index.js.map +1 -0
- package/lib/foundation/schema/SexType.js +5 -3
- package/lib/foundation/schema/SexType.js.map +1 -0
- package/lib/foundation/schema/index.js +1 -0
- package/lib/foundation/schema/index.js.map +1 -0
- package/lib/foundation/schema/parseSexType.js +1 -0
- package/lib/foundation/schema/parseSexType.js.map +1 -0
- package/lib/foundation/schema/types.js +4 -2
- package/lib/foundation/schema/types.js.map +1 -0
- package/lib/fs/FileSystemError.js +23 -0
- package/lib/fs/FileSystemError.js.map +1 -0
- package/lib/fs/IFileSystem.d.js +3 -0
- package/lib/fs/IFileSystem.d.js.map +1 -0
- package/lib/fs/MemoryFileSystem.test.js +188 -0
- package/lib/fs/createBrowserFileSystem.js +248 -0
- package/lib/fs/createBrowserFileSystem.js.map +1 -0
- package/lib/fs/createMemoryFileSystem.js +516 -0
- package/lib/fs/createMemoryFileSystem.js.map +1 -0
- package/lib/fs/createSandboxFileSystem.js +108 -0
- package/lib/fs/createSandboxFileSystem.js.map +1 -0
- package/lib/fs/createWebDavFileSystem.js +137 -0
- package/lib/fs/createWebDavFileSystem.js.map +1 -0
- package/lib/fs/findMimeType.js +17 -0
- package/lib/fs/findMimeType.js.map +1 -0
- package/lib/fs/index.js +8 -0
- package/lib/fs/index.js.map +1 -0
- package/lib/fs/orpc/FileSystemContract.js +93 -0
- package/lib/fs/orpc/FileSystemContract.js.map +1 -0
- package/lib/fs/orpc/createContractClientFileSystem.js +93 -0
- package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -0
- package/lib/fs/orpc/index.js +3 -0
- package/lib/fs/orpc/index.js.map +1 -0
- package/lib/fs/orpc/server/createFileSystemContractImpl.js +63 -0
- package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -0
- package/lib/fs/orpc/server/index.js +2 -0
- package/lib/fs/orpc/server/index.js.map +1 -0
- package/lib/fs/s3/createS3MiniFileSystem.js +705 -0
- package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -0
- package/lib/fs/s3/index.js +2 -0
- package/lib/fs/s3/index.js.map +1 -0
- package/lib/fs/s3/s3mini.test.js +584 -0
- package/lib/fs/scandir.js +59 -0
- package/lib/fs/scandir.js.map +1 -0
- package/lib/fs/server/createDatabaseFileSystem.js +750 -0
- package/lib/fs/server/createDatabaseFileSystem.js.map +1 -0
- package/lib/fs/server/createNodeFileSystem.js +401 -0
- package/lib/fs/server/createNodeFileSystem.js.map +1 -0
- package/lib/fs/server/dbfs.test.js +221 -0
- package/lib/fs/server/index.js +2 -0
- package/lib/fs/server/index.js.map +1 -0
- package/lib/fs/server/loadTestDatabase.js +127 -0
- package/lib/fs/server/loadTestDatabase.js.map +1 -0
- package/lib/fs/tests/runFileSystemTest.js +318 -0
- package/lib/fs/tests/runFileSystemTest.js.map +1 -0
- package/lib/fs/types.js +27 -0
- package/lib/fs/types.js.map +1 -0
- package/lib/fs/utils/getFileUrl.js +35 -0
- package/lib/fs/utils/getFileUrl.js.map +1 -0
- package/lib/fs/utils.js +22 -0
- package/lib/fs/utils.js.map +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -0
- package/lib/jsonschema/JsonSchema.js +146 -172
- package/lib/jsonschema/JsonSchema.js.map +1 -0
- package/lib/jsonschema/forEachJsonSchema.js +44 -0
- package/lib/jsonschema/forEachJsonSchema.js.map +1 -0
- package/lib/jsonschema/index.js +2 -0
- package/lib/jsonschema/index.js.map +1 -0
- package/lib/jsonschema/types.d.js +2 -0
- package/lib/jsonschema/types.d.js.map +1 -0
- package/lib/meta/defineFileType.js +20 -103
- package/lib/meta/defineFileType.js.map +1 -0
- package/lib/meta/defineInit.js +31 -250
- package/lib/meta/defineInit.js.map +1 -0
- package/lib/meta/defineMetadata.js +24 -140
- package/lib/meta/defineMetadata.js.map +1 -0
- package/lib/meta/index.js +1 -0
- package/lib/meta/index.js.map +1 -0
- package/lib/orpc/createOpenApiContractClient.js +27 -0
- package/lib/orpc/createOpenApiContractClient.js.map +1 -0
- package/lib/orpc/createRpcContractClient.js +34 -0
- package/lib/orpc/createRpcContractClient.js.map +1 -0
- package/lib/orpc/index.js +3 -0
- package/lib/orpc/index.js.map +1 -0
- package/lib/orpc/resolveLinkPlugins.js +28 -0
- package/lib/orpc/resolveLinkPlugins.js.map +1 -0
- package/lib/password/PHC.js +63 -87
- package/lib/password/PHC.js.map +1 -0
- package/lib/password/PHC.test.js +11 -3
- package/lib/password/Password.js +29 -294
- package/lib/password/Password.js.map +1 -0
- package/lib/password/Password.test.js +35 -22
- package/lib/password/createArgon2PasswordAlgorithm.js +35 -191
- package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -0
- package/lib/password/createBase64PasswordAlgorithm.js +8 -141
- package/lib/password/createBase64PasswordAlgorithm.js.map +1 -0
- package/lib/password/createBcryptPasswordAlgorithm.js +13 -168
- package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -0
- package/lib/password/createPBKDF2PasswordAlgorithm.js +46 -228
- package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -0
- package/lib/password/createScryptPasswordAlgorithm.js +55 -211
- package/lib/password/createScryptPasswordAlgorithm.js.map +1 -0
- package/lib/password/index.js +1 -0
- package/lib/password/index.js.map +1 -0
- package/lib/password/server/index.js +1 -0
- package/lib/password/server/index.js.map +1 -0
- package/lib/resource/Identifiable.js +2 -0
- package/lib/resource/Identifiable.js.map +1 -0
- package/lib/resource/ListQuery.js +21 -93
- package/lib/resource/ListQuery.js.map +1 -0
- package/lib/resource/getTitleOfResource.js +3 -5
- package/lib/resource/getTitleOfResource.js.map +1 -0
- package/lib/resource/index.js +1 -0
- package/lib/resource/index.js.map +1 -0
- package/lib/resource/schema/AnyResourceSchema.js +2 -1
- package/lib/resource/schema/AnyResourceSchema.js.map +1 -0
- package/lib/resource/schema/BaseResourceSchema.js +2 -1
- package/lib/resource/schema/BaseResourceSchema.js.map +1 -0
- package/lib/resource/schema/ResourceActionType.js +6 -4
- package/lib/resource/schema/ResourceActionType.js.map +1 -0
- package/lib/resource/schema/ResourceStatus.js +5 -3
- package/lib/resource/schema/ResourceStatus.js.map +1 -0
- package/lib/resource/schema/ResourceType.js +5 -3
- package/lib/resource/schema/ResourceType.js.map +1 -0
- package/lib/resource/schema/index.js +1 -0
- package/lib/resource/schema/index.js.map +1 -0
- package/lib/resource/schema/types.js +16 -20
- package/lib/resource/schema/types.js.map +1 -0
- package/lib/s3/formatS3Url.js +65 -0
- package/lib/s3/formatS3Url.js.map +1 -0
- package/lib/s3/formatS3Url.test.js +262 -0
- package/lib/s3/index.js +3 -0
- package/lib/s3/index.js.map +1 -0
- package/lib/s3/parseS3Url.js +65 -0
- package/lib/s3/parseS3Url.js.map +1 -0
- package/lib/s3/parseS3Url.test.js +270 -0
- package/lib/schema/SchemaRegistry.js +38 -38
- package/lib/schema/SchemaRegistry.js.map +1 -0
- package/lib/schema/TypeSchema.d.js +2 -0
- package/lib/schema/TypeSchema.d.js.map +1 -0
- package/lib/schema/createSchemaData.js +26 -125
- package/lib/schema/createSchemaData.js.map +1 -0
- package/lib/schema/findJsonSchemaByPath.js +13 -36
- package/lib/schema/findJsonSchemaByPath.js.map +1 -0
- package/lib/schema/formatZodError.js +140 -0
- package/lib/schema/formatZodError.js.map +1 -0
- package/lib/schema/formatZodError.test.js +196 -0
- package/lib/schema/getSchemaCache.js +5 -5
- package/lib/schema/getSchemaCache.js.map +1 -0
- package/lib/schema/getSchemaOptions.js +8 -11
- package/lib/schema/getSchemaOptions.js.map +1 -0
- package/lib/schema/index.js +2 -1
- package/lib/schema/index.js.map +1 -0
- package/lib/schema/toJsonSchema.js +47 -290
- package/lib/schema/toJsonSchema.js.map +1 -0
- package/lib/schema/validate.js +33 -45
- package/lib/schema/validate.js.map +1 -0
- package/lib/tools/generateSchema.js +39 -197
- package/lib/tools/generateSchema.js.map +1 -0
- package/lib/tools/renderJsonSchemaToMarkdownDoc.js +55 -143
- package/lib/tools/renderJsonSchemaToMarkdownDoc.js.map +1 -0
- package/lib/utils/buildBaseUrl.js +13 -0
- package/lib/utils/buildBaseUrl.js.map +1 -0
- package/lib/utils/buildRedactorFormSchema.js +59 -0
- package/lib/utils/buildRedactorFormSchema.js.map +1 -0
- package/lib/utils/getEstimateProcessTime.js +12 -11
- package/lib/utils/getEstimateProcessTime.js.map +1 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/index.js.map +1 -0
- package/lib/utils/resolveFeatureOptions.js +12 -0
- package/lib/utils/resolveFeatureOptions.js.map +1 -0
- package/package.json +61 -13
- package/src/ai/qwen3vl/index.ts +1 -0
- package/src/ai/qwen3vl/utils.ts +36 -0
- package/src/ai/vision/DocLayoutElementTypeSchema.ts +30 -0
- package/src/ai/vision/ImageAnnotationSchema.ts +60 -0
- package/src/ai/vision/index.ts +2 -0
- package/src/ai/vision/resolveImageAnnotation.ts +135 -0
- package/src/cn/ChineseResidentIdNo.test.ts +1 -1
- package/src/cn/ChineseResidentIdNo.ts +8 -0
- package/src/cn/DivisionCode.test.ts +1 -1
- package/src/cn/DivisionCode.ts +8 -0
- package/src/cn/UnifiedSocialCreditCode.test.ts +1 -1
- package/src/cn/UnifiedSocialCreditCode.ts +15 -0
- package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +23 -0
- package/src/cn/formatChineseAmount.ts +61 -0
- package/src/cn/index.ts +7 -1
- package/src/cn/parseChineseNumber.test.ts +159 -0
- package/src/cn/parseChineseNumber.ts +97 -0
- package/src/cn/pinyin/cartesianProduct.test.ts +64 -0
- package/src/cn/pinyin/cartesianProduct.ts +24 -0
- package/src/cn/pinyin/data.json +23573 -0
- package/src/cn/pinyin/loader.ts +12 -0
- package/src/cn/pinyin/preload.ts +3 -0
- package/src/cn/pinyin/toPinyin.test.ts +12 -0
- package/src/cn/pinyin/toPinyinPure.ts +43 -0
- package/src/cn/pinyin/transform.ts +12 -0
- package/src/consola/formatLogObject.test.ts +27 -0
- package/src/consola/formatLogObject.ts +34 -6
- package/src/dayjs/dayjs.ts +18 -18
- package/src/dayjs/index.ts +3 -1
- package/src/dayjs/parseRelativeTime.test.ts +185 -0
- package/src/dayjs/parseRelativeTime.ts +115 -0
- package/src/dayjs/resolveRelativeTime.test.ts +357 -0
- package/src/dayjs/resolveRelativeTime.ts +167 -0
- package/src/emittery/emitter.ts +9 -0
- package/src/emittery/index.ts +1 -0
- package/src/fs/FileSystemError.ts +26 -0
- package/src/fs/IFileSystem.d.ts +102 -0
- package/src/fs/MemoryFileSystem.test.ts +37 -0
- package/src/fs/createBrowserFileSystem.ts +291 -0
- package/src/fs/createMemoryFileSystem.ts +604 -0
- package/src/fs/createSandboxFileSystem.ts +136 -0
- package/src/fs/createWebDavFileSystem.ts +172 -0
- package/src/fs/findMimeType.ts +23 -0
- package/src/fs/index.ts +8 -0
- package/src/fs/orpc/FileSystemContract.ts +92 -0
- package/src/fs/orpc/createContractClientFileSystem.ts +115 -0
- package/src/fs/orpc/index.ts +2 -0
- package/src/fs/orpc/server/createFileSystemContractImpl.ts +64 -0
- package/src/fs/orpc/server/index.ts +1 -0
- package/src/fs/s3/createS3MiniFileSystem.ts +830 -0
- package/src/fs/s3/index.ts +1 -0
- package/src/fs/s3/s3mini.test.ts +264 -0
- package/src/fs/scandir.ts +75 -0
- package/src/fs/server/createDatabaseFileSystem.ts +668 -0
- package/src/fs/server/createNodeFileSystem.ts +499 -0
- package/src/fs/server/dbfs.test.ts +47 -0
- package/src/fs/server/index.ts +1 -0
- package/src/fs/server/loadTestDatabase.ts +131 -0
- package/src/fs/tests/runFileSystemTest.ts +288 -0
- package/src/fs/types.ts +29 -0
- package/src/fs/utils/getFileUrl.ts +44 -0
- package/src/fs/utils.ts +23 -0
- package/src/jsonschema/JsonSchema.ts +118 -110
- package/src/jsonschema/forEachJsonSchema.ts +50 -0
- package/src/jsonschema/index.ts +1 -0
- package/src/orpc/createOpenApiContractClient.ts +52 -0
- package/src/orpc/createRpcContractClient.ts +50 -0
- package/src/orpc/index.ts +2 -0
- package/src/orpc/resolveLinkPlugins.ts +29 -0
- package/src/password/PHC.ts +3 -3
- package/src/password/Password.test.ts +1 -1
- package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
- package/src/resource/schema/AnyResourceSchema.ts +16 -2
- package/src/s3/formatS3Url.test.ts +254 -0
- package/src/s3/formatS3Url.ts +84 -0
- package/src/s3/index.ts +2 -0
- package/src/s3/parseS3Url.test.ts +258 -0
- package/src/s3/parseS3Url.ts +88 -0
- package/src/schema/SchemaRegistry.ts +35 -33
- package/src/schema/formatZodError.test.ts +196 -0
- package/src/schema/formatZodError.ts +151 -0
- package/src/schema/getSchemaOptions.ts +2 -2
- package/src/schema/index.ts +1 -1
- package/src/utils/buildBaseUrl.ts +12 -0
- package/src/utils/buildRedactorFormSchema.ts +85 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/resolveFeatureOptions.ts +14 -0
- package/src/cn/ChineseResidentIdNo.mod.ts +0 -7
- package/src/cn/DivisionCode.mod.ts +0 -7
- package/src/cn/UnifiedSocialCreditCode.mod.ts +0 -7
- package/src/cn/mod.ts +0 -3
- package/src/schema/SchemaRegistry.mod.ts +0 -1
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
import { basename, dirname, normalize } from "node:path";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
import { formatS3Url, parseS3Url } from "@wener/common/s3";
|
|
4
|
+
import { S3mini, sanitizeETag } from "s3mini";
|
|
5
|
+
export function createS3MiniFileSystem(options = {}) {
|
|
6
|
+
const parsed = parseS3Url(options);
|
|
7
|
+
if (!parsed) {
|
|
8
|
+
throw new Error('S3 URL or connection options are required');
|
|
9
|
+
}
|
|
10
|
+
const { client, prefix } = options;
|
|
11
|
+
if (!client && (!parsed.endpoint || !parsed.bucket)) {
|
|
12
|
+
throw new Error('S3 endpoint and bucket are required when client is not provided');
|
|
13
|
+
}
|
|
14
|
+
let s3mini;
|
|
15
|
+
let bucket;
|
|
16
|
+
if (client) {
|
|
17
|
+
s3mini = client;
|
|
18
|
+
bucket = client.bucketName || parsed.bucket || '';
|
|
19
|
+
} else {
|
|
20
|
+
bucket = parsed.bucket || '';
|
|
21
|
+
// Construct full endpoint URL with bucket for S3mini
|
|
22
|
+
const endpointUrl = formatS3Url(parsed, {
|
|
23
|
+
credentials: false,
|
|
24
|
+
useParams: false
|
|
25
|
+
});
|
|
26
|
+
s3mini = new S3mini({
|
|
27
|
+
accessKeyId: parsed.accessKeyId || '',
|
|
28
|
+
secretAccessKey: parsed.secretAccessKey || '',
|
|
29
|
+
endpoint: endpointUrl,
|
|
30
|
+
region: parsed.region
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Normalize prefix: remove leading/trailing slashes, ensure it ends with / when not empty
|
|
34
|
+
const normalizedPrefix = prefix ? prefix.replace(/^\/+/, '').replace(/\/+$/, '') : '';
|
|
35
|
+
return new S3FS(s3mini, bucket, normalizedPrefix);
|
|
36
|
+
}
|
|
37
|
+
let S3FS = class S3FS {
|
|
38
|
+
client;
|
|
39
|
+
bucket;
|
|
40
|
+
prefix;
|
|
41
|
+
constructor(client, bucket, prefix = ''){
|
|
42
|
+
this.client = client;
|
|
43
|
+
this.bucket = bucket;
|
|
44
|
+
this.prefix = prefix;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Normalize path to S3 key format (remove leading slash, handle relative paths)
|
|
48
|
+
* and prepend the prefix if one is set
|
|
49
|
+
*/ normalizeKey(path) {
|
|
50
|
+
if (!path || path === '/') {
|
|
51
|
+
return this.prefix;
|
|
52
|
+
}
|
|
53
|
+
const normalized = normalize(path).replace(/^\/+/, '').replace(/\\/g, '/');
|
|
54
|
+
// Prepend prefix if set
|
|
55
|
+
if (this.prefix) {
|
|
56
|
+
return this.prefix + '/' + normalized;
|
|
57
|
+
}
|
|
58
|
+
return normalized;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Remove prefix from S3 key to get the file system path
|
|
62
|
+
*/ stripPrefix(key) {
|
|
63
|
+
if (!this.prefix || !key.startsWith(this.prefix + '/')) {
|
|
64
|
+
// If key doesn't start with prefix, return as-is (shouldn't happen normally)
|
|
65
|
+
return key.startsWith('/') ? key : '/' + key;
|
|
66
|
+
}
|
|
67
|
+
const withoutPrefix = key.slice(this.prefix.length);
|
|
68
|
+
return withoutPrefix || '/';
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convert S3 key back to file system path (strip prefix and add leading slash)
|
|
72
|
+
*/ keyToPath(key) {
|
|
73
|
+
if (!key) {
|
|
74
|
+
return '/';
|
|
75
|
+
}
|
|
76
|
+
return this.stripPrefix(key);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get directory path from a key
|
|
80
|
+
*/ getDirectory(key) {
|
|
81
|
+
if (!key) {
|
|
82
|
+
return '/';
|
|
83
|
+
}
|
|
84
|
+
const dir = dirname(key).replace(/\\/g, '/');
|
|
85
|
+
return dir === '.' ? '/' : '/' + dir;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Check if a key represents a directory (ends with /)
|
|
89
|
+
*/ isDirectoryKey(key) {
|
|
90
|
+
return key.endsWith('/');
|
|
91
|
+
}
|
|
92
|
+
toFileStat(key, obj) {
|
|
93
|
+
const isDir = this.isDirectoryKey(key);
|
|
94
|
+
const path = this.keyToPath(key);
|
|
95
|
+
const directory = this.getDirectory(key);
|
|
96
|
+
const size = typeof obj.Size === 'string' ? parseInt(obj.Size, 10) || 0 : obj.Size || 0;
|
|
97
|
+
const mtime = obj.LastModified ? new Date(obj.LastModified).getTime() : Date.now();
|
|
98
|
+
return {
|
|
99
|
+
directory,
|
|
100
|
+
path,
|
|
101
|
+
name: isDir ? basename(key.slice(0, -1)) || basename(directory) || '/' : basename(key),
|
|
102
|
+
kind: isDir ? 'directory' : 'file',
|
|
103
|
+
mtime,
|
|
104
|
+
size,
|
|
105
|
+
meta: obj.ETag ? {
|
|
106
|
+
etag: sanitizeETag(obj.ETag)
|
|
107
|
+
} : {}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
checkAborted(signal) {
|
|
111
|
+
if (signal?.aborted) {
|
|
112
|
+
throw new Error('The operation was aborted');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async readdir(dir, options = {}) {
|
|
116
|
+
const { glob, recursive, depth = 1, kind, hidden = true, signal } = options;
|
|
117
|
+
this.checkAborted(signal);
|
|
118
|
+
const dirPrefix = this.normalizeKey(dir);
|
|
119
|
+
const prefixWithSlash = dirPrefix ? dirPrefix.endsWith('/') ? dirPrefix : dirPrefix + '/' : '';
|
|
120
|
+
try {
|
|
121
|
+
// When delimiter is '/', S3 returns:
|
|
122
|
+
// - Files: Key without trailing /, Size > 0
|
|
123
|
+
// - Directories: Key with trailing /, Size: 0
|
|
124
|
+
const delimiter = recursive ? undefined : '/';
|
|
125
|
+
const objects = await this.client.listObjects(delimiter ?? '', prefixWithSlash, undefined, {
|
|
126
|
+
delimiter,
|
|
127
|
+
signal
|
|
128
|
+
});
|
|
129
|
+
if (!objects || objects.length === 0) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
let results = [];
|
|
133
|
+
const seenPaths = new Set();
|
|
134
|
+
for (const obj of objects){
|
|
135
|
+
this.checkAborted(signal);
|
|
136
|
+
const key = obj.Key || '';
|
|
137
|
+
if (!key) continue;
|
|
138
|
+
// Skip if not under our prefix
|
|
139
|
+
if (prefixWithSlash && !key.startsWith(prefixWithSlash)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Strip the prefix from the key for relative path calculation
|
|
143
|
+
const relativeKey = prefixWithSlash ? key.slice(prefixWithSlash.length) : key;
|
|
144
|
+
// Skip empty relative keys (the directory itself)
|
|
145
|
+
if (!relativeKey || relativeKey === '/') {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Directory: Key ends with '/' (from CommonPrefixes or explicit marker)
|
|
149
|
+
const isDir = key.endsWith('/');
|
|
150
|
+
// For non-recursive, only show immediate children
|
|
151
|
+
if (!recursive && depth === 1) {
|
|
152
|
+
// For directories, the relative key looks like "dirname/"
|
|
153
|
+
// For files, the relative key looks like "filename"
|
|
154
|
+
// We only want immediate children, so no more slashes in the middle
|
|
155
|
+
const keyWithoutTrailingSlash = relativeKey.replace(/\/$/, '');
|
|
156
|
+
if (keyWithoutTrailingSlash.includes('/')) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Deduplicate by path
|
|
161
|
+
const pathKey = key.replace(/\/$/, ''); // Normalize for deduplication
|
|
162
|
+
if (seenPaths.has(pathKey)) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
seenPaths.add(pathKey);
|
|
166
|
+
const stat = this.toFileStat(key, {
|
|
167
|
+
Key: key,
|
|
168
|
+
Size: obj.Size,
|
|
169
|
+
LastModified: obj.LastModified,
|
|
170
|
+
ETag: obj.ETag
|
|
171
|
+
});
|
|
172
|
+
// Filter by hidden
|
|
173
|
+
if (!hidden && stat.name.startsWith('.')) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
// Filter by kind
|
|
177
|
+
if (kind && stat.kind !== kind) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
results.push(stat);
|
|
181
|
+
}
|
|
182
|
+
// Handle recursive with depth > 1
|
|
183
|
+
if (!recursive && depth > 1) {
|
|
184
|
+
const subdirs = results.filter((entry)=>entry.kind === 'directory');
|
|
185
|
+
for (const subdir of subdirs){
|
|
186
|
+
this.checkAborted(signal);
|
|
187
|
+
const maxDepth = depth - 1;
|
|
188
|
+
if (maxDepth > 0) {
|
|
189
|
+
const subEntries = await this.readdir(subdir.path, {
|
|
190
|
+
...options,
|
|
191
|
+
depth: maxDepth
|
|
192
|
+
});
|
|
193
|
+
results = [
|
|
194
|
+
...results,
|
|
195
|
+
...subEntries
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Handle glob filtering
|
|
201
|
+
if (glob) {
|
|
202
|
+
const { matcher } = await import("micromatch");
|
|
203
|
+
const match = matcher(glob);
|
|
204
|
+
results = results.filter((entry)=>match(entry.path));
|
|
205
|
+
}
|
|
206
|
+
return results;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error.code === 'NoSuchKey' || error.message?.includes('404')) {
|
|
209
|
+
throw new Error(`Directory not found: ${dir}`);
|
|
210
|
+
}
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async stat(entry, options = {}) {
|
|
215
|
+
const { signal } = options;
|
|
216
|
+
this.checkAborted(signal);
|
|
217
|
+
const key = this.normalizeKey(entry);
|
|
218
|
+
if (!key) {
|
|
219
|
+
// Root directory
|
|
220
|
+
return {
|
|
221
|
+
directory: '/',
|
|
222
|
+
path: '/',
|
|
223
|
+
name: '/',
|
|
224
|
+
kind: 'directory',
|
|
225
|
+
mtime: Date.now(),
|
|
226
|
+
size: 0,
|
|
227
|
+
meta: {}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
// Try to check if object exists and get metadata
|
|
232
|
+
const exists = await this.client.objectExists(key, {
|
|
233
|
+
signal
|
|
234
|
+
});
|
|
235
|
+
if (exists === true) {
|
|
236
|
+
// Get ETag and size
|
|
237
|
+
const etag = await this.client.getEtag(key, {
|
|
238
|
+
signal
|
|
239
|
+
});
|
|
240
|
+
const size = await this.client.getContentLength(key);
|
|
241
|
+
// Get LastModified from listing (since objectExists doesn't return it)
|
|
242
|
+
const objects = await this.client.listObjects('/', key, 1, {
|
|
243
|
+
delimiter: '/',
|
|
244
|
+
signal
|
|
245
|
+
});
|
|
246
|
+
const obj = objects?.[0];
|
|
247
|
+
return this.toFileStat(key, {
|
|
248
|
+
Key: key,
|
|
249
|
+
Size: size,
|
|
250
|
+
LastModified: obj?.LastModified || new Date(),
|
|
251
|
+
ETag: etag || undefined
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
// If object not found, try checking if it's a directory (prefix listing)
|
|
255
|
+
const dirKey = key.endsWith('/') ? key : key + '/';
|
|
256
|
+
const objects = await this.client.listObjects('/', dirKey, 1, {
|
|
257
|
+
delimiter: '/',
|
|
258
|
+
signal
|
|
259
|
+
});
|
|
260
|
+
if (objects && objects.length > 0) {
|
|
261
|
+
// It's a directory
|
|
262
|
+
return {
|
|
263
|
+
directory: this.getDirectory(key),
|
|
264
|
+
path: this.keyToPath(key),
|
|
265
|
+
name: basename(key.replace(/\/$/, '')) || '/',
|
|
266
|
+
kind: 'directory',
|
|
267
|
+
mtime: Date.now(),
|
|
268
|
+
size: 0,
|
|
269
|
+
meta: {}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
throw new Error(`File not found: ${entry}`);
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (error.message?.includes('not found') || error.code === 'NoSuchKey') {
|
|
275
|
+
throw new Error(`File not found: ${entry}`);
|
|
276
|
+
}
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async mkdir(path, options = {}) {
|
|
281
|
+
const { recursive = false, signal } = options;
|
|
282
|
+
this.checkAborted(signal);
|
|
283
|
+
// In S3, directories don't actually exist - they're just prefixes
|
|
284
|
+
// Optionally create a marker object (empty object with trailing slash)
|
|
285
|
+
const key = this.normalizeKey(path);
|
|
286
|
+
if (!key) {
|
|
287
|
+
return; // Root directory, nothing to do
|
|
288
|
+
}
|
|
289
|
+
// Ensure it ends with / to indicate directory
|
|
290
|
+
const dirKey = key.endsWith('/') ? key : key + '/';
|
|
291
|
+
// Try to create a marker object (0-byte object)
|
|
292
|
+
try {
|
|
293
|
+
await this.client.putObject(dirKey, '', 'application/x-directory', undefined, undefined);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
// If it already exists or we don't have permission, that's okay for mkdir
|
|
296
|
+
if (!error.message?.includes('409') && !error.message?.includes('403')) {
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async readFile(path, options = {}) {
|
|
302
|
+
const { encoding = 'binary', signal, onDownloadProgress } = options;
|
|
303
|
+
this.checkAborted(signal);
|
|
304
|
+
const key = this.normalizeKey(path);
|
|
305
|
+
if (!key) {
|
|
306
|
+
throw new Error('Cannot read root directory');
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
// Use getObjectArrayBuffer for binary data
|
|
310
|
+
const data = await this.client.getObjectArrayBuffer(key, {
|
|
311
|
+
signal
|
|
312
|
+
});
|
|
313
|
+
if (!data) {
|
|
314
|
+
throw new Error(`File not found: ${path}`);
|
|
315
|
+
}
|
|
316
|
+
// Handle progress reporting if needed
|
|
317
|
+
if (onDownloadProgress) {
|
|
318
|
+
onDownloadProgress({
|
|
319
|
+
loaded: data.byteLength,
|
|
320
|
+
total: data.byteLength
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (encoding === 'text') {
|
|
324
|
+
return new TextDecoder().decode(data);
|
|
325
|
+
}
|
|
326
|
+
return new Uint8Array(data);
|
|
327
|
+
} catch (error) {
|
|
328
|
+
if (error.code === 'NoSuchKey' || error.message?.includes('404')) {
|
|
329
|
+
throw new Error(`File not found: ${path}`);
|
|
330
|
+
}
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async writeFile(path, data, options = {}) {
|
|
335
|
+
const { signal, overwrite = true, onUploadProgress } = options;
|
|
336
|
+
this.checkAborted(signal);
|
|
337
|
+
const key = this.normalizeKey(path);
|
|
338
|
+
if (!key) {
|
|
339
|
+
throw new Error('Cannot write to root directory');
|
|
340
|
+
}
|
|
341
|
+
// Check if file exists and overwrite is false
|
|
342
|
+
if (!overwrite) {
|
|
343
|
+
const exists = await this.exists(path);
|
|
344
|
+
if (exists) {
|
|
345
|
+
throw new Error(`File already exists: ${path}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Convert data to buffer or string
|
|
349
|
+
let body;
|
|
350
|
+
if (data instanceof Readable) {
|
|
351
|
+
// For streams, we need to read them into a buffer
|
|
352
|
+
const chunks = [];
|
|
353
|
+
let loaded = 0;
|
|
354
|
+
if (onUploadProgress) {
|
|
355
|
+
data.on('data', (chunk)=>{
|
|
356
|
+
chunks.push(chunk);
|
|
357
|
+
loaded += chunk.length;
|
|
358
|
+
onUploadProgress({
|
|
359
|
+
loaded,
|
|
360
|
+
total: -1
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
} else {
|
|
364
|
+
data.on('data', (chunk)=>{
|
|
365
|
+
chunks.push(chunk);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
body = await new Promise((resolve, reject)=>{
|
|
369
|
+
const allChunks = [];
|
|
370
|
+
data.on('data', (chunk)=>allChunks.push(chunk));
|
|
371
|
+
data.on('end', ()=>resolve(Buffer.concat(allChunks)));
|
|
372
|
+
data.on('error', reject);
|
|
373
|
+
if (signal) {
|
|
374
|
+
signal.addEventListener('abort', ()=>{
|
|
375
|
+
data.destroy();
|
|
376
|
+
reject(new Error('The operation was aborted'));
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
} else if (data instanceof ArrayBuffer) {
|
|
381
|
+
body = Buffer.from(data);
|
|
382
|
+
} else if (data instanceof Buffer) {
|
|
383
|
+
body = data;
|
|
384
|
+
} else if (typeof data === 'string') {
|
|
385
|
+
body = data;
|
|
386
|
+
} else {
|
|
387
|
+
// ArrayBufferView
|
|
388
|
+
body = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
await this.client.putObject(key, body, undefined, undefined, undefined);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async rm(path, options = {}) {
|
|
397
|
+
const { recursive = false, force = false, signal } = options;
|
|
398
|
+
this.checkAborted(signal);
|
|
399
|
+
const key = this.normalizeKey(path);
|
|
400
|
+
if (!key) {
|
|
401
|
+
throw new Error('Cannot remove root directory');
|
|
402
|
+
}
|
|
403
|
+
try {
|
|
404
|
+
if (recursive) {
|
|
405
|
+
// List all objects with this prefix (no delimiter = recursive)
|
|
406
|
+
const prefix = key.endsWith('/') ? key : key + '/';
|
|
407
|
+
const objects = await this.client.listObjects('', prefix, undefined, {
|
|
408
|
+
signal
|
|
409
|
+
});
|
|
410
|
+
if (objects) {
|
|
411
|
+
// Delete all objects
|
|
412
|
+
const keys = objects.map((obj)=>obj.Key || '').filter(Boolean);
|
|
413
|
+
if (keys.length > 0) {
|
|
414
|
+
await this.client.deleteObjects(keys);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Also delete the marker object if it exists
|
|
418
|
+
const markerKey = prefix;
|
|
419
|
+
try {
|
|
420
|
+
await this.client.deleteObject(markerKey);
|
|
421
|
+
} catch {
|
|
422
|
+
// Ignore if marker doesn't exist
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
// Delete single object
|
|
426
|
+
await this.client.deleteObject(key);
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (force && (error.code === 'NoSuchKey' || error.message?.includes('404'))) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
async rename(oldPath, newPath, options = {}) {
|
|
436
|
+
const { signal, overwrite = false } = options;
|
|
437
|
+
this.checkAborted(signal);
|
|
438
|
+
const oldKey = this.normalizeKey(oldPath);
|
|
439
|
+
const newKey = this.normalizeKey(newPath);
|
|
440
|
+
if (!oldKey) {
|
|
441
|
+
throw new Error('Cannot rename root directory');
|
|
442
|
+
}
|
|
443
|
+
// Check if target exists and overwrite is false
|
|
444
|
+
if (!overwrite) {
|
|
445
|
+
const exists = await this.exists(newPath);
|
|
446
|
+
if (exists) {
|
|
447
|
+
throw new Error(`Destination already exists: ${newPath}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
// Check if it's a directory (has objects with prefix)
|
|
452
|
+
const isDir = oldKey.endsWith('/');
|
|
453
|
+
const prefix = isDir ? oldKey : oldKey + '/';
|
|
454
|
+
const objects = await this.client.listObjects('', prefix, undefined, {
|
|
455
|
+
signal
|
|
456
|
+
});
|
|
457
|
+
if (objects && objects.length > 0) {
|
|
458
|
+
// It's a directory or has multiple objects, move all
|
|
459
|
+
const newPrefix = newKey.endsWith('/') ? newKey : newKey + '/';
|
|
460
|
+
// Move all objects
|
|
461
|
+
await Promise.all(objects.map(async (obj)=>{
|
|
462
|
+
const objKey = obj.Key || '';
|
|
463
|
+
if (!objKey) return;
|
|
464
|
+
const relativeKey = objKey.slice(prefix.length);
|
|
465
|
+
const newObjKey = newPrefix + relativeKey;
|
|
466
|
+
// Move object (copy + delete)
|
|
467
|
+
await this.client.moveObject(objKey, newObjKey);
|
|
468
|
+
}));
|
|
469
|
+
// Move marker object if it exists
|
|
470
|
+
try {
|
|
471
|
+
await this.client.moveObject(prefix, newPrefix);
|
|
472
|
+
} catch {
|
|
473
|
+
// Ignore if marker doesn't exist
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
// Single file - use moveObject
|
|
477
|
+
await this.client.moveObject(oldKey, newKey);
|
|
478
|
+
}
|
|
479
|
+
} catch (error) {
|
|
480
|
+
if (error.code === 'NoSuchKey' || error.message?.includes('404')) {
|
|
481
|
+
throw new Error(`Source file not found: ${oldPath}`);
|
|
482
|
+
}
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async exists(path) {
|
|
487
|
+
try {
|
|
488
|
+
const key = this.normalizeKey(path);
|
|
489
|
+
if (!key) {
|
|
490
|
+
return true; // Root always exists
|
|
491
|
+
}
|
|
492
|
+
const exists = await this.client.objectExists(key);
|
|
493
|
+
if (exists === true) {
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
// Check if it's a directory
|
|
497
|
+
const dirKey = key.endsWith('/') ? key : key + '/';
|
|
498
|
+
const objects = await this.client.listObjects('/', dirKey, 1, {
|
|
499
|
+
delimiter: '/'
|
|
500
|
+
});
|
|
501
|
+
return objects !== null && objects.length > 0;
|
|
502
|
+
} catch {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async copy(src, dest, options = {}) {
|
|
507
|
+
const { signal, overwrite = true, shallow = false } = options;
|
|
508
|
+
this.checkAborted(signal);
|
|
509
|
+
const srcKey = this.normalizeKey(src);
|
|
510
|
+
const destKey = this.normalizeKey(dest);
|
|
511
|
+
if (!srcKey) {
|
|
512
|
+
throw new Error('Cannot copy root directory');
|
|
513
|
+
}
|
|
514
|
+
// Check if source exists
|
|
515
|
+
try {
|
|
516
|
+
const srcStat = await this.stat(src);
|
|
517
|
+
// Check if destination exists and overwrite is false
|
|
518
|
+
if (!overwrite) {
|
|
519
|
+
const exists = await this.exists(dest);
|
|
520
|
+
if (exists) {
|
|
521
|
+
throw new Error(`Destination already exists: ${dest}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (srcStat.kind === 'directory') {
|
|
525
|
+
// Copy directory recursively
|
|
526
|
+
const srcPrefix = srcKey.endsWith('/') ? srcKey : srcKey + '/';
|
|
527
|
+
const destPrefix = destKey.endsWith('/') ? destKey : destKey + '/';
|
|
528
|
+
const objects = await this.client.listObjects(shallow ? '/' : '', srcPrefix, undefined, {
|
|
529
|
+
...shallow ? {
|
|
530
|
+
delimiter: '/'
|
|
531
|
+
} : {},
|
|
532
|
+
signal
|
|
533
|
+
});
|
|
534
|
+
if (objects) {
|
|
535
|
+
// Copy all objects
|
|
536
|
+
await Promise.all(objects.map(async (obj)=>{
|
|
537
|
+
const objKey = obj.Key || '';
|
|
538
|
+
if (!objKey) return;
|
|
539
|
+
const relativeKey = objKey.slice(srcPrefix.length);
|
|
540
|
+
const newObjKey = destPrefix + relativeKey;
|
|
541
|
+
await this.client.copyObject(objKey, newObjKey);
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
// Copy marker object if it exists
|
|
545
|
+
try {
|
|
546
|
+
await this.client.copyObject(srcPrefix, destPrefix);
|
|
547
|
+
} catch {
|
|
548
|
+
// Ignore if marker doesn't exist
|
|
549
|
+
}
|
|
550
|
+
} else {
|
|
551
|
+
// Copy single file
|
|
552
|
+
await this.client.copyObject(srcKey, destKey);
|
|
553
|
+
}
|
|
554
|
+
} catch (error) {
|
|
555
|
+
if (error.code === 'NoSuchKey' || error.message?.includes('404')) {
|
|
556
|
+
throw new Error(`Source file not found: ${src}`);
|
|
557
|
+
}
|
|
558
|
+
throw error;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
createReadStream(path, options = {}) {
|
|
562
|
+
const key = this.normalizeKey(path);
|
|
563
|
+
if (!key) {
|
|
564
|
+
throw new Error('Cannot read root directory');
|
|
565
|
+
}
|
|
566
|
+
const { range, signal } = options;
|
|
567
|
+
// Use getObjectRaw with range support
|
|
568
|
+
const responsePromise = this.client.getObjectRaw(key, !range, range?.start, range?.end, {
|
|
569
|
+
signal
|
|
570
|
+
}, undefined);
|
|
571
|
+
// Convert Response to Readable stream
|
|
572
|
+
let nodeStream = null;
|
|
573
|
+
const stream = new Readable({
|
|
574
|
+
async read () {
|
|
575
|
+
if (!nodeStream) {
|
|
576
|
+
try {
|
|
577
|
+
const response = await responsePromise;
|
|
578
|
+
if (!response.body) {
|
|
579
|
+
this.emit('error', new Error('No response body'));
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
// Convert ReadableStream to Node Readable
|
|
583
|
+
const reader = response.body.getReader();
|
|
584
|
+
const decoder = new TextDecoder();
|
|
585
|
+
nodeStream = new Readable({
|
|
586
|
+
async read () {
|
|
587
|
+
try {
|
|
588
|
+
const { done, value } = await reader.read();
|
|
589
|
+
if (done) {
|
|
590
|
+
this.push(null);
|
|
591
|
+
} else {
|
|
592
|
+
this.push(Buffer.from(value));
|
|
593
|
+
}
|
|
594
|
+
} catch (err) {
|
|
595
|
+
this.emit('error', err);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
nodeStream.on('data', (chunk)=>{
|
|
600
|
+
this.push(chunk);
|
|
601
|
+
});
|
|
602
|
+
nodeStream.on('end', ()=>{
|
|
603
|
+
this.push(null);
|
|
604
|
+
});
|
|
605
|
+
nodeStream.on('error', (err)=>{
|
|
606
|
+
this.emit('error', err);
|
|
607
|
+
});
|
|
608
|
+
} catch (err) {
|
|
609
|
+
this.emit('error', err);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
signal?.addEventListener('abort', ()=>{
|
|
615
|
+
stream.destroy(new Error('The operation was aborted'));
|
|
616
|
+
});
|
|
617
|
+
return stream;
|
|
618
|
+
}
|
|
619
|
+
createReadableStream(path, options = {}) {
|
|
620
|
+
const key = this.normalizeKey(path);
|
|
621
|
+
if (!key) {
|
|
622
|
+
throw new Error('Cannot read root directory');
|
|
623
|
+
}
|
|
624
|
+
const { range, signal } = options;
|
|
625
|
+
// Use getObjectRaw which returns a Response with ReadableStream
|
|
626
|
+
const responsePromise = this.client.getObjectRaw(key, !range, range?.start, range?.end, {
|
|
627
|
+
signal
|
|
628
|
+
}, undefined);
|
|
629
|
+
return new ReadableStream({
|
|
630
|
+
async start (controller) {
|
|
631
|
+
try {
|
|
632
|
+
const response = await responsePromise;
|
|
633
|
+
if (!response.body) {
|
|
634
|
+
controller.error(new Error('No response body'));
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
const reader = response.body.getReader();
|
|
638
|
+
signal?.addEventListener('abort', ()=>{
|
|
639
|
+
reader.cancel(new Error('The operation was aborted'));
|
|
640
|
+
controller.error(new Error('The operation was aborted'));
|
|
641
|
+
});
|
|
642
|
+
while(true){
|
|
643
|
+
const { done, value } = await reader.read();
|
|
644
|
+
if (done) {
|
|
645
|
+
controller.close();
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
controller.enqueue(value);
|
|
649
|
+
}
|
|
650
|
+
} catch (err) {
|
|
651
|
+
controller.error(err);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
createWritableStream(path, options = {}) {
|
|
657
|
+
const key = this.normalizeKey(path);
|
|
658
|
+
if (!key) {
|
|
659
|
+
throw new Error('Cannot write to root directory');
|
|
660
|
+
}
|
|
661
|
+
const { signal, overwrite = true } = options;
|
|
662
|
+
this.checkAborted(signal);
|
|
663
|
+
// Create a WritableStream that buffers data and uploads when done
|
|
664
|
+
const buffer = [];
|
|
665
|
+
let controller;
|
|
666
|
+
const client = this.client;
|
|
667
|
+
const checkAborted = this.checkAborted.bind(this);
|
|
668
|
+
return new WritableStream({
|
|
669
|
+
start (ctrl) {
|
|
670
|
+
controller = ctrl;
|
|
671
|
+
},
|
|
672
|
+
async write (chunk) {
|
|
673
|
+
buffer.push(chunk);
|
|
674
|
+
},
|
|
675
|
+
async close () {
|
|
676
|
+
try {
|
|
677
|
+
checkAborted(signal);
|
|
678
|
+
const data = Buffer.concat(buffer.map((chunk)=>Buffer.from(chunk)));
|
|
679
|
+
await client.putObject(key, data, undefined, undefined, undefined);
|
|
680
|
+
// Controller closes automatically when close() completes successfully
|
|
681
|
+
} catch (error) {
|
|
682
|
+
controller.error(error);
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
abort (reason) {
|
|
686
|
+
buffer.length = 0;
|
|
687
|
+
controller.error(reason);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
getUrl(path, options) {
|
|
692
|
+
if (typeof path === 'object' && path?.kind !== 'file') {
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const key = typeof path === 'string' ? this.normalizeKey(path) : this.normalizeKey(path.path);
|
|
696
|
+
if (!key) {
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
// Construct URL from endpoint - S3mini doesn't provide presigned URLs in basic API
|
|
700
|
+
// This is a fallback - real implementation would need presigned URLs
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
//# sourceMappingURL=createS3MiniFileSystem.js.map
|