@wener/common 2.0.3 → 2.0.6

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.
Files changed (312) hide show
  1. package/lib/ai/qwen3vl/index.js +1 -1
  2. package/lib/ai/qwen3vl/utils.js +15 -15
  3. package/lib/ai/qwen3vl/utils.js.map +1 -1
  4. package/lib/ai/vision/DocLayoutElementTypeSchema.js +22 -22
  5. package/lib/ai/vision/ImageAnnotationSchema.js +63 -47
  6. package/lib/ai/vision/index.js +2 -2
  7. package/lib/ai/vision/resolveImageAnnotation.js +81 -95
  8. package/lib/cn/ChineseResidentIdNo.js +55 -41
  9. package/lib/cn/ChineseResidentIdNo.js.map +1 -1
  10. package/lib/cn/ChineseResidentIdNo.mod.js +6 -1
  11. package/lib/cn/ChineseResidentIdNo.test.js +22 -21
  12. package/lib/cn/DivisionCode.js +220 -235
  13. package/lib/cn/DivisionCode.mod.js +6 -1
  14. package/lib/cn/DivisionCode.test.js +92 -121
  15. package/lib/cn/Mod11.js +18 -37
  16. package/lib/cn/Mod11.js.map +1 -1
  17. package/lib/cn/Mod31.js +23 -41
  18. package/lib/cn/UnifiedSocialCreditCode.js +143 -137
  19. package/lib/cn/UnifiedSocialCreditCode.mod.js +6 -1
  20. package/lib/cn/UnifiedSocialCreditCode.test.js +21 -15
  21. package/lib/cn/formatChineseAmount.js +46 -71
  22. package/lib/cn/index.js +6 -6
  23. package/lib/cn/mod.js +5 -3
  24. package/lib/cn/parseChineseNumber.js +81 -85
  25. package/lib/cn/parseChineseNumber.test.js +183 -261
  26. package/lib/cn/pinyin/cartesianProduct.js +19 -19
  27. package/lib/cn/pinyin/cartesianProduct.test.js +78 -178
  28. package/lib/cn/pinyin/loader.js +13 -11
  29. package/lib/cn/pinyin/preload.js +2 -1
  30. package/lib/cn/pinyin/toPinyin.test.js +149 -161
  31. package/lib/cn/pinyin/toPinyinPure.js +28 -23
  32. package/lib/cn/pinyin/transform.js +11 -11
  33. package/lib/cn/types.d.js +2 -2
  34. package/lib/consola/createStandardConsolaReporter.js +14 -15
  35. package/lib/consola/formatLogObject.js +149 -133
  36. package/lib/consola/formatLogObject.js.map +1 -1
  37. package/lib/consola/formatLogObject.test.js +167 -178
  38. package/lib/consola/index.js +2 -2
  39. package/lib/data/formatSort.js +14 -12
  40. package/lib/data/formatSort.test.js +33 -33
  41. package/lib/data/index.js +3 -3
  42. package/lib/data/maybeNumber.js +23 -23
  43. package/lib/data/maybeNumber.js.map +1 -1
  44. package/lib/data/parseSort.js +75 -68
  45. package/lib/data/parseSort.test.js +196 -187
  46. package/lib/data/resolvePagination.js +38 -39
  47. package/lib/data/resolvePagination.test.js +228 -218
  48. package/lib/data/types.d.js +2 -2
  49. package/lib/data/types.d.js.map +1 -1
  50. package/lib/dayjs/dayjs.js +20 -20
  51. package/lib/dayjs/formatDuration.js +56 -56
  52. package/lib/dayjs/formatDuration.js.map +1 -1
  53. package/lib/dayjs/formatDuration.test.js +63 -77
  54. package/lib/dayjs/index.js +4 -4
  55. package/lib/dayjs/parseDuration.js +21 -26
  56. package/lib/dayjs/parseRelativeTime.js +65 -66
  57. package/lib/dayjs/parseRelativeTime.test.js +227 -243
  58. package/lib/dayjs/resolveRelativeTime.js +74 -144
  59. package/lib/dayjs/resolveRelativeTime.js.map +1 -1
  60. package/lib/dayjs/resolveRelativeTime.test.js +296 -307
  61. package/lib/decimal/index.js +1 -1
  62. package/lib/decimal/parseDecimal.js +12 -12
  63. package/lib/drain3/Drain.js +321 -0
  64. package/lib/drain3/Drain.js.map +1 -0
  65. package/lib/drain3/LogCluster.js +38 -0
  66. package/lib/drain3/LogCluster.js.map +1 -0
  67. package/lib/drain3/Node.js +39 -0
  68. package/lib/drain3/Node.js.map +1 -0
  69. package/lib/drain3/TemplateMiner.js +205 -0
  70. package/lib/drain3/TemplateMiner.js.map +1 -0
  71. package/lib/drain3/index.js +31 -0
  72. package/lib/drain3/index.js.map +1 -0
  73. package/lib/drain3/persistence/FilePersistence.js +24 -0
  74. package/lib/drain3/persistence/FilePersistence.js.map +1 -0
  75. package/lib/drain3/persistence/MemoryPersistence.js +18 -0
  76. package/lib/drain3/persistence/MemoryPersistence.js.map +1 -0
  77. package/lib/drain3/persistence/PersistenceHandler.js +5 -0
  78. package/lib/drain3/persistence/PersistenceHandler.js.map +1 -0
  79. package/lib/drain3/types.js +7 -0
  80. package/lib/drain3/types.js.map +1 -0
  81. package/lib/emittery/emitter.js +7 -7
  82. package/lib/emittery/index.js +1 -1
  83. package/lib/foundation/schema/SexType.js +15 -12
  84. package/lib/foundation/schema/index.js +1 -1
  85. package/lib/foundation/schema/parseSexType.js +15 -16
  86. package/lib/foundation/schema/types.js +8 -6
  87. package/lib/fs/FileSystemError.js +18 -18
  88. package/lib/fs/IFileSystem.d.js +2 -2
  89. package/lib/fs/IFileSystem.d.js.map +1 -1
  90. package/lib/fs/MemoryFileSystem.test.js +172 -181
  91. package/lib/fs/createBrowserFileSystem.js +222 -233
  92. package/lib/fs/createBrowserFileSystem.js.map +1 -1
  93. package/lib/fs/createMemoryFileSystem.js +473 -510
  94. package/lib/fs/createMemoryFileSystem.js.map +1 -1
  95. package/lib/fs/createSandboxFileSystem.js +102 -101
  96. package/lib/fs/createSandboxFileSystem.js.map +1 -1
  97. package/lib/fs/createWebDavFileSystem.js +162 -132
  98. package/lib/fs/createWebDavFileSystem.js.map +1 -1
  99. package/lib/fs/createWebFileSystem.js +202 -0
  100. package/lib/fs/createWebFileSystem.js.map +1 -0
  101. package/lib/fs/findMimeType.js +14 -14
  102. package/lib/fs/findMimeType.js.map +1 -1
  103. package/lib/fs/index.js +7 -7
  104. package/lib/fs/index.js.map +1 -1
  105. package/lib/fs/minio/createMinioFileSystem.js +977 -0
  106. package/lib/fs/minio/createMinioFileSystem.js.map +1 -0
  107. package/lib/fs/minio/index.js +2 -0
  108. package/lib/fs/minio/index.js.map +1 -0
  109. package/lib/fs/orpc/FileSystemContract.js +57 -57
  110. package/lib/fs/orpc/createContractClientFileSystem.js +88 -88
  111. package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -1
  112. package/lib/fs/orpc/index.js +2 -2
  113. package/lib/fs/orpc/server/createFileSystemContractImpl.js +62 -60
  114. package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -1
  115. package/lib/fs/orpc/server/index.js +1 -1
  116. package/lib/fs/s3/createS3MiniFileSystem.js +756 -689
  117. package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -1
  118. package/lib/fs/s3/index.js +1 -1
  119. package/lib/fs/s3/s3mini.test.js +524 -553
  120. package/lib/fs/scandir.js +56 -56
  121. package/lib/fs/server/createDatabaseFileSystem.js +834 -741
  122. package/lib/fs/server/createDatabaseFileSystem.js.map +1 -1
  123. package/lib/fs/server/createNodeFileSystem.js +407 -380
  124. package/lib/fs/server/createNodeFileSystem.js.map +1 -1
  125. package/lib/fs/server/dbfs.test.js +201 -214
  126. package/lib/fs/server/index.js +1 -1
  127. package/lib/fs/server/loadTestDatabase.js +40 -43
  128. package/lib/fs/tests/runFileSystemTest.js +352 -315
  129. package/lib/fs/tests/runFileSystemTest.js.map +1 -1
  130. package/lib/fs/types.js +17 -20
  131. package/lib/fs/utils/getFileUrl.js +24 -30
  132. package/lib/fs/utils.js +17 -17
  133. package/lib/fs/utils.js.map +1 -1
  134. package/lib/fs/webdav/index.js +2 -0
  135. package/lib/fs/webdav/index.js.map +1 -0
  136. package/lib/index.js +2 -2
  137. package/lib/jsonschema/JsonSchema.js +216 -155
  138. package/lib/jsonschema/JsonSchema.js.map +1 -1
  139. package/lib/jsonschema/JsonSchema.test.js +123 -124
  140. package/lib/jsonschema/forEachJsonSchema.js +41 -41
  141. package/lib/jsonschema/forEachJsonSchema.js.map +1 -1
  142. package/lib/jsonschema/index.js +2 -2
  143. package/lib/jsonschema/types.d.js +2 -2
  144. package/lib/jsonschema/types.d.js.map +1 -1
  145. package/lib/meta/defineFileType.js +32 -38
  146. package/lib/meta/defineInit.js +39 -35
  147. package/lib/meta/defineMetadata.js +37 -34
  148. package/lib/meta/defineMetadata.js.map +1 -1
  149. package/lib/meta/defineMetadata.test.js +13 -12
  150. package/lib/meta/index.js +3 -3
  151. package/lib/orpc/createOpenApiContractClient.js +26 -24
  152. package/lib/orpc/createOpenApiContractClient.js.map +1 -1
  153. package/lib/orpc/createRpcContractClient.js +37 -31
  154. package/lib/orpc/index.js +2 -2
  155. package/lib/orpc/resolveLinkPlugins.js +25 -25
  156. package/lib/password/PHC.js +187 -189
  157. package/lib/password/PHC.js.map +1 -1
  158. package/lib/password/PHC.test.js +517 -535
  159. package/lib/password/Password.js +85 -80
  160. package/lib/password/Password.test.js +330 -364
  161. package/lib/password/createArgon2PasswordAlgorithm.js +50 -51
  162. package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -1
  163. package/lib/password/createBase64PasswordAlgorithm.js +11 -11
  164. package/lib/password/createBase64PasswordAlgorithm.js.map +1 -1
  165. package/lib/password/createBcryptPasswordAlgorithm.js +20 -18
  166. package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -1
  167. package/lib/password/createPBKDF2PasswordAlgorithm.js +65 -52
  168. package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -1
  169. package/lib/password/createScryptPasswordAlgorithm.js +74 -63
  170. package/lib/password/createScryptPasswordAlgorithm.js.map +1 -1
  171. package/lib/password/index.js +5 -5
  172. package/lib/password/server/index.js +1 -1
  173. package/lib/resource/Identifiable.js +2 -2
  174. package/lib/resource/ListQuery.js +42 -42
  175. package/lib/resource/ListQuery.js.map +1 -1
  176. package/lib/resource/getTitleOfResource.js +5 -5
  177. package/lib/resource/index.js +2 -2
  178. package/lib/resource/index.js.map +1 -1
  179. package/lib/resource/schema/AnyResourceSchema.js +91 -89
  180. package/lib/resource/schema/BaseResourceSchema.js +26 -26
  181. package/lib/resource/schema/ResourceActionType.js +117 -115
  182. package/lib/resource/schema/ResourceStatus.js +94 -92
  183. package/lib/resource/schema/ResourceType.js +25 -23
  184. package/lib/resource/schema/index.js +5 -5
  185. package/lib/resource/schema/types.js +86 -55
  186. package/lib/resource/schema/types.test.js +16 -13
  187. package/lib/s3/formatS3Url.js +60 -60
  188. package/lib/s3/formatS3Url.js.map +1 -1
  189. package/lib/s3/formatS3Url.test.js +238 -261
  190. package/lib/s3/index.js +2 -2
  191. package/lib/s3/parseS3Url.js +61 -60
  192. package/lib/s3/parseS3Url.js.map +1 -1
  193. package/lib/s3/parseS3Url.test.js +270 -269
  194. package/lib/schema/SchemaRegistry.js +41 -42
  195. package/lib/schema/SchemaRegistry.js.map +1 -1
  196. package/lib/schema/SchemaRegistry.mod.js +1 -1
  197. package/lib/schema/TypeSchema.d.js +2 -2
  198. package/lib/schema/TypeSchema.d.js.map +1 -1
  199. package/lib/schema/createSchemaData.js +113 -67
  200. package/lib/schema/createSchemaData.js.map +1 -1
  201. package/lib/schema/findJsonSchemaByPath.js +28 -23
  202. package/lib/schema/findJsonSchemaByPath.js.map +1 -1
  203. package/lib/schema/formatZodError.js +113 -134
  204. package/lib/schema/formatZodError.js.map +1 -1
  205. package/lib/schema/formatZodError.test.js +192 -195
  206. package/lib/schema/getSchemaCache.js +7 -7
  207. package/lib/schema/getSchemaOptions.js +17 -16
  208. package/lib/schema/index.js +6 -6
  209. package/lib/schema/toJsonSchema.js +196 -190
  210. package/lib/schema/toJsonSchema.js.map +1 -1
  211. package/lib/schema/toJsonSchema.test.js +34 -26
  212. package/lib/schema/validate.js +106 -97
  213. package/lib/schema/validate.js.map +1 -1
  214. package/lib/tools/generateSchema.js +40 -40
  215. package/lib/tools/renderJsonSchemaToMarkdownDoc.js +74 -74
  216. package/lib/utils/buildBaseUrl.js +8 -8
  217. package/lib/utils/buildRedactorFormSchema.js +55 -54
  218. package/lib/utils/buildRedactorFormSchema.js.map +1 -1
  219. package/lib/utils/getEstimateProcessTime.js +24 -19
  220. package/lib/utils/index.js +3 -3
  221. package/lib/utils/resolveFeatureOptions.js +9 -9
  222. package/package.json +37 -18
  223. package/src/ai/qwen3vl/utils.ts +1 -1
  224. package/src/ai/vision/index.ts +2 -2
  225. package/src/cn/ChineseResidentIdNo.ts +1 -1
  226. package/src/cn/Mod11.ts +1 -1
  227. package/src/cn/__snapshots__/ChineseResidentIdNo.test.ts.snap +1 -1
  228. package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +0 -23
  229. package/src/cn/index.ts +1 -2
  230. package/src/cn/parseChineseNumber.test.ts +4 -4
  231. package/src/consola/formatLogObject.ts +6 -6
  232. package/src/consola/index.ts +1 -1
  233. package/src/data/index.ts +3 -4
  234. package/src/data/maybeNumber.ts +1 -1
  235. package/src/data/parseSort.test.ts +0 -1
  236. package/src/data/resolvePagination.ts +2 -2
  237. package/src/data/types.d.ts +2 -2
  238. package/src/dayjs/formatDuration.ts +10 -11
  239. package/src/dayjs/index.ts +1 -1
  240. package/src/dayjs/parseRelativeTime.ts +1 -1
  241. package/src/dayjs/resolveRelativeTime.ts +11 -14
  242. package/src/drain3/Drain.test.ts +378 -0
  243. package/src/drain3/Drain.ts +394 -0
  244. package/src/drain3/LogCluster.ts +46 -0
  245. package/src/drain3/Node.ts +53 -0
  246. package/src/drain3/TemplateMiner.ts +246 -0
  247. package/src/drain3/index.ts +34 -0
  248. package/src/drain3/persistence/FilePersistence.ts +24 -0
  249. package/src/drain3/persistence/MemoryPersistence.ts +23 -0
  250. package/src/drain3/persistence/PersistenceHandler.ts +19 -0
  251. package/src/drain3/types.ts +75 -0
  252. package/src/fs/IFileSystem.d.ts +1 -2
  253. package/src/fs/createBrowserFileSystem.ts +7 -5
  254. package/src/fs/createMemoryFileSystem.ts +9 -13
  255. package/src/fs/createSandboxFileSystem.ts +1 -1
  256. package/src/fs/createWebDavFileSystem.ts +30 -17
  257. package/src/fs/createWebFileSystem.ts +242 -0
  258. package/src/fs/findMimeType.ts +1 -4
  259. package/src/fs/index.ts +5 -5
  260. package/src/fs/minio/createMinioFileSystem.ts +1148 -0
  261. package/src/fs/minio/index.ts +1 -0
  262. package/src/fs/orpc/createContractClientFileSystem.ts +5 -5
  263. package/src/fs/orpc/server/createFileSystemContractImpl.ts +1 -1
  264. package/src/fs/s3/createS3MiniFileSystem.ts +120 -79
  265. package/src/fs/s3/s3fs.test.ts +441 -0
  266. package/src/fs/s3/s3mini.test.ts +2 -2
  267. package/src/fs/server/createDatabaseFileSystem.ts +78 -114
  268. package/src/fs/server/createNodeFileSystem.ts +32 -13
  269. package/src/fs/server/dbfs.test.ts +13 -8
  270. package/src/fs/server/index.ts +1 -0
  271. package/src/fs/server/loadTestDatabase.ts +8 -119
  272. package/src/fs/tests/runFileSystemTest.ts +29 -28
  273. package/src/fs/utils.ts +1 -1
  274. package/src/fs/webdav/index.ts +1 -0
  275. package/src/jsonschema/JsonSchema.ts +5 -5
  276. package/src/jsonschema/forEachJsonSchema.ts +1 -1
  277. package/src/jsonschema/index.ts +1 -1
  278. package/src/jsonschema/types.d.ts +1 -1
  279. package/src/meta/defineMetadata.ts +1 -1
  280. package/src/meta/index.ts +2 -3
  281. package/src/orm/createSqliteDialect.ts +17 -0
  282. package/src/orm/index.ts +1 -0
  283. package/src/orpc/createOpenApiContractClient.ts +3 -3
  284. package/src/orpc/index.ts +1 -1
  285. package/src/password/PHC.ts +3 -3
  286. package/src/password/createArgon2PasswordAlgorithm.ts +2 -2
  287. package/src/password/createBase64PasswordAlgorithm.ts +2 -2
  288. package/src/password/createBcryptPasswordAlgorithm.ts +4 -2
  289. package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
  290. package/src/password/createScryptPasswordAlgorithm.ts +4 -4
  291. package/src/password/index.ts +2 -2
  292. package/src/resource/ListQuery.ts +4 -1
  293. package/src/resource/index.ts +3 -3
  294. package/src/resource/schema/index.ts +4 -4
  295. package/src/s3/formatS3Url.test.ts +1 -1
  296. package/src/s3/formatS3Url.ts +2 -2
  297. package/src/s3/index.ts +1 -1
  298. package/src/s3/parseS3Url.ts +1 -1
  299. package/src/schema/SchemaRegistry.ts +2 -2
  300. package/src/schema/TypeSchema.d.ts +6 -6
  301. package/src/schema/createSchemaData.ts +5 -5
  302. package/src/schema/findJsonSchemaByPath.ts +5 -5
  303. package/src/schema/formatZodError.test.ts +2 -1
  304. package/src/schema/formatZodError.ts +50 -62
  305. package/src/schema/index.ts +5 -5
  306. package/src/schema/toJsonSchema.ts +6 -6
  307. package/src/schema/validate.ts +2 -2
  308. package/src/utils/buildRedactorFormSchema.ts +4 -4
  309. package/src/utils/formatNumber.ts +18 -0
  310. package/src/utils/formatPercent.ts +17 -0
  311. package/src/utils/index.ts +3 -3
  312. package/src/utils/resolveFeatureOptions.ts +1 -1
@@ -0,0 +1,977 @@
1
+ import { PassThrough, Readable } from 'node:stream';
2
+ import { parseS3Url } from '@wener/common/s3';
3
+ import { Client } from 'minio';
4
+ import { basename, dirname, normalize } from 'pathe';
5
+ export function createMinioFileSystem(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 minioClient;
15
+ let bucket;
16
+ if (client) {
17
+ minioClient = client;
18
+ bucket = parsed.bucket || '';
19
+ } else {
20
+ bucket = parsed.bucket || '';
21
+ // Import Minio dynamically to avoid requiring it as a dependency
22
+ minioClient = new Client({
23
+ endPoint: parsed.endpoint,
24
+ port: parsed.port,
25
+ useSSL: parsed.useSsl ?? true,
26
+ accessKey: parsed.accessKeyId || '',
27
+ secretKey: parsed.secretAccessKey || '',
28
+ region: parsed.region,
29
+ pathStyle: parsed.pathStyle,
30
+ });
31
+ }
32
+ // Normalize prefix: remove leading/trailing slashes
33
+ const normalizedPrefix = prefix ? prefix.replace(/^\/+/, '').replace(/\/+$/, '') : '';
34
+ return new MinioFS(minioClient, bucket, normalizedPrefix);
35
+ }
36
+ let MinioFS = class MinioFS {
37
+ client;
38
+ bucket;
39
+ prefix;
40
+ constructor(client, bucket, prefix = '') {
41
+ this.client = client;
42
+ this.bucket = bucket;
43
+ this.prefix = prefix;
44
+ }
45
+ /**
46
+ * Normalize path to S3 key format (remove leading slash, handle relative paths)
47
+ * and prepend the prefix if one is set
48
+ */ normalizeKey(path) {
49
+ if (!path || path === '/') {
50
+ return this.prefix;
51
+ }
52
+ const normalized = normalize(path).replace(/^\/+/, '').replace(/\\/g, '/');
53
+ // Prepend prefix if set
54
+ if (this.prefix) {
55
+ return this.prefix + '/' + normalized;
56
+ }
57
+ return normalized;
58
+ }
59
+ /**
60
+ * Remove prefix from S3 key to get the file system path
61
+ */ stripPrefix(key) {
62
+ if (!key) {
63
+ return '/';
64
+ }
65
+ // If key is just the prefix (or empty after stripping), return root
66
+ if (this.prefix && key === this.prefix) {
67
+ return '/';
68
+ }
69
+ if (!this.prefix || !key.startsWith(this.prefix + '/')) {
70
+ return key.startsWith('/') ? key : '/' + key;
71
+ }
72
+ const withoutPrefix = key.slice(this.prefix.length);
73
+ return withoutPrefix || '/';
74
+ }
75
+ /**
76
+ * Convert S3 key back to file system path (strip prefix and add leading slash)
77
+ */ keyToPath(key) {
78
+ if (!key) {
79
+ return '/';
80
+ }
81
+ return this.stripPrefix(key);
82
+ }
83
+ /**
84
+ * Get directory path from a key
85
+ */ getDirectory(key) {
86
+ if (!key) {
87
+ return '/';
88
+ }
89
+ const dir = dirname(key).replace(/\\/g, '/');
90
+ return dir === '.' ? '/' : '/' + dir;
91
+ }
92
+ /**
93
+ * Check if a key represents a directory (ends with /)
94
+ */ isDirectoryKey(key) {
95
+ return key.endsWith('/');
96
+ }
97
+ /**
98
+ * Convert MinIO object metadata to IFileStat
99
+ */ toFileStat(key, obj) {
100
+ const isDir = this.isDirectoryKey(key);
101
+ const path = this.keyToPath(key);
102
+ const directory = this.getDirectory(key);
103
+ return {
104
+ directory,
105
+ path,
106
+ name: isDir ? basename(key.slice(0, -1)) || basename(directory) || '/' : basename(key),
107
+ kind: isDir ? 'directory' : 'file',
108
+ mtime: obj.lastModified ? new Date(obj.lastModified).getTime() : Date.now(),
109
+ size: obj.size || 0,
110
+ meta: {
111
+ ...(obj.etag
112
+ ? {
113
+ etag: obj.etag.replace(/"/g, ''),
114
+ }
115
+ : {}),
116
+ },
117
+ };
118
+ }
119
+ checkAborted(signal) {
120
+ if (signal?.aborted) {
121
+ throw new Error('The operation was aborted');
122
+ }
123
+ }
124
+ async readdir(dir, options = {}) {
125
+ const { glob, recursive, depth = 1, kind, hidden = true, signal } = options;
126
+ this.checkAborted(signal);
127
+ const dirPrefix = this.normalizeKey(dir);
128
+ const prefixWithSlash = dirPrefix ? (dirPrefix.endsWith('/') ? dirPrefix : dirPrefix + '/') : '';
129
+ try {
130
+ // MinIO listObjects supports recursive option
131
+ // When recursive=false, MinIO uses delimiter='/' internally to return CommonPrefixes
132
+ const objects = [];
133
+ const commonPrefixes = [];
134
+ // MinIO listObjects returns a stream
135
+ // When recursive=false, it returns both objects and prefixes (CommonPrefixes)
136
+ const objectStream = this.client.listObjects(this.bucket, prefixWithSlash, recursive);
137
+ await new Promise((resolve, reject) => {
138
+ objectStream.on('data', (obj) => {
139
+ if (obj.name) {
140
+ objects.push({
141
+ name: obj.name,
142
+ size: obj.size || 0,
143
+ lastModified: obj.lastModified || new Date(),
144
+ etag: obj.etag || '',
145
+ });
146
+ } else if (obj.prefix) {
147
+ // CommonPrefixes from MinIO
148
+ commonPrefixes.push(obj.prefix);
149
+ }
150
+ });
151
+ objectStream.on('end', () => {
152
+ resolve();
153
+ });
154
+ objectStream.on('error', (err) => {
155
+ reject(err);
156
+ });
157
+ if (signal) {
158
+ signal.addEventListener('abort', () => {
159
+ objectStream.destroy();
160
+ reject(new Error('The operation was aborted'));
161
+ });
162
+ }
163
+ });
164
+ let results = [];
165
+ // Process CommonPrefixes (directories) first
166
+ for (const prefix of commonPrefixes) {
167
+ this.checkAborted(signal);
168
+ // Skip if not under our prefix
169
+ if (prefixWithSlash && !prefix.startsWith(prefixWithSlash)) {
170
+ continue;
171
+ }
172
+ // Get relative path
173
+ const relativePrefix = prefixWithSlash ? prefix.slice(prefixWithSlash.length) : prefix;
174
+ const dirName = relativePrefix.replace(/\/$/, ''); // Remove trailing slash
175
+ if (!dirName) continue;
176
+ // For non-recursive, only show immediate children
177
+ if (!recursive && depth === 1) {
178
+ const firstSlash = dirName.indexOf('/');
179
+ if (firstSlash >= 0) {
180
+ continue;
181
+ }
182
+ }
183
+ const dirKey = prefix.endsWith('/') ? prefix : prefix + '/';
184
+ const stat = this.toFileStat(dirKey, {
185
+ name: dirKey,
186
+ size: 0,
187
+ lastModified: new Date(),
188
+ });
189
+ // Filter by hidden
190
+ if (!hidden && stat.name.startsWith('.')) {
191
+ continue;
192
+ }
193
+ // Filter by kind
194
+ if (kind && stat.kind !== kind) {
195
+ continue;
196
+ }
197
+ results.push(stat);
198
+ }
199
+ // Process objects (files) to convert to IFileStat
200
+ const seenDirs = new Set();
201
+ for (const obj of objects) {
202
+ this.checkAborted(signal);
203
+ const key = obj.name;
204
+ if (!key) continue;
205
+ // Skip if not under our prefix
206
+ if (prefixWithSlash && !key.startsWith(prefixWithSlash)) {
207
+ continue;
208
+ }
209
+ // Strip the prefix from the key for path conversion
210
+ const relativeKey = prefixWithSlash ? key.slice(prefixWithSlash.length) : key;
211
+ // Calculate depth: count the number of slashes in the relative path
212
+ // depth=1 means immediate children (no slashes), depth=2 means one level deep (one slash), etc.
213
+ const depthLevel = (relativeKey.match(/\//g) || []).length + 1;
214
+ // Filter by depth
215
+ if (depthLevel > depth) {
216
+ continue;
217
+ }
218
+ const isDir = this.isDirectoryKey(key);
219
+ // Track directories to avoid duplicates
220
+ if (isDir) {
221
+ const dirKey = key.slice(0, -1);
222
+ if (seenDirs.has(dirKey)) {
223
+ continue;
224
+ }
225
+ seenDirs.add(dirKey);
226
+ } else {
227
+ // For files in recursive mode, always include them
228
+ // For non-recursive mode, check if parent directory was already added
229
+ if (!recursive) {
230
+ const parentDir = dirname(key).replace(/\\/g, '/') + '/';
231
+ if (seenDirs.has(parentDir.slice(0, -1))) {
232
+ continue;
233
+ }
234
+ }
235
+ }
236
+ const stat = this.toFileStat(key, {
237
+ name: key,
238
+ size: obj.size || 0,
239
+ lastModified: obj.lastModified,
240
+ etag: obj.etag,
241
+ });
242
+ // Filter by hidden
243
+ if (!hidden && stat.name.startsWith('.')) {
244
+ continue;
245
+ }
246
+ // Filter by kind
247
+ if (kind && stat.kind !== kind) {
248
+ continue;
249
+ }
250
+ results.push(stat);
251
+ }
252
+ // Handle recursive with depth > 1
253
+ if (!recursive && depth > 1) {
254
+ const subdirs = results.filter((entry) => entry.kind === 'directory');
255
+ for (const subdir of subdirs) {
256
+ this.checkAborted(signal);
257
+ const maxDepth = depth - 1;
258
+ if (maxDepth > 0) {
259
+ const subEntries = await this.readdir(subdir.path, {
260
+ ...options,
261
+ depth: maxDepth,
262
+ });
263
+ results = [...results, ...subEntries];
264
+ }
265
+ }
266
+ }
267
+ // Handle glob filtering
268
+ if (glob) {
269
+ const { matcher } = await import('micromatch');
270
+ const match = matcher(glob);
271
+ results = results.filter((entry) => match(entry.path));
272
+ }
273
+ return results;
274
+ } catch (error) {
275
+ if (error.code === 'NoSuchKey' || error.message?.includes('404') || error.code === 'NotFound') {
276
+ throw new Error(`Directory not found: ${dir}`);
277
+ }
278
+ throw error;
279
+ }
280
+ }
281
+ async stat(entry, options = {}) {
282
+ const { signal } = options;
283
+ this.checkAborted(signal);
284
+ const key = this.normalizeKey(entry);
285
+ if (!key) {
286
+ // Root directory
287
+ return {
288
+ directory: '/',
289
+ path: '/',
290
+ name: '/',
291
+ kind: 'directory',
292
+ mtime: Date.now(),
293
+ size: 0,
294
+ meta: {},
295
+ };
296
+ }
297
+ try {
298
+ // Try to get object stat
299
+ const stat = await this.client.statObject(this.bucket, key);
300
+ return this.toFileStat(key, {
301
+ name: key,
302
+ size: stat.size,
303
+ lastModified: stat.lastModified,
304
+ etag: stat.etag,
305
+ });
306
+ } catch (error) {
307
+ // If object not found, try checking if it's a directory
308
+ if (error.code === 'NotFound' || error.code === 'NoSuchKey') {
309
+ const dirKey = key.endsWith('/') ? key : key + '/';
310
+ try {
311
+ // List objects with this prefix to check if it's a directory
312
+ const objectStream = this.client.listObjects(this.bucket, dirKey, false);
313
+ let hasObjects = false;
314
+ await new Promise((resolve, reject) => {
315
+ objectStream.on('data', () => {
316
+ hasObjects = true;
317
+ objectStream.destroy();
318
+ resolve();
319
+ });
320
+ objectStream.on('end', () => {
321
+ resolve();
322
+ });
323
+ objectStream.on('error', reject);
324
+ if (signal) {
325
+ signal.addEventListener('abort', () => {
326
+ objectStream.destroy();
327
+ reject(new Error('The operation was aborted'));
328
+ });
329
+ }
330
+ });
331
+ if (hasObjects) {
332
+ // It's a directory
333
+ return {
334
+ directory: this.getDirectory(key),
335
+ path: this.keyToPath(key),
336
+ name: basename(key.replace(/\/$/, '')) || '/',
337
+ kind: 'directory',
338
+ mtime: Date.now(),
339
+ size: 0,
340
+ meta: {},
341
+ };
342
+ }
343
+ } catch {
344
+ // Ignore listing errors
345
+ }
346
+ throw new Error(`File not found: ${entry}`);
347
+ }
348
+ throw error;
349
+ }
350
+ }
351
+ async mkdir(path, options = {}) {
352
+ const { recursive = false, signal } = options;
353
+ this.checkAborted(signal);
354
+ // In S3, directories don't actually exist - they're just prefixes
355
+ // Optionally create a marker object (empty object with trailing slash)
356
+ const key = this.normalizeKey(path);
357
+ if (!key) {
358
+ return; // Root directory, nothing to do
359
+ }
360
+ // Ensure it ends with / to indicate directory
361
+ const dirKey = key.endsWith('/') ? key : key + '/';
362
+ // Try to create a marker object (0-byte object)
363
+ try {
364
+ const stream = new PassThrough();
365
+ stream.end();
366
+ await this.client.putObject(this.bucket, dirKey, stream, 0, {
367
+ 'Content-Type': 'application/x-directory',
368
+ });
369
+ } catch (error) {
370
+ // If it already exists or we don't have permission, that's okay for mkdir
371
+ if (error.code !== 'NoSuchBucket' && error.code !== 'AccessDenied') {
372
+ // Ignore other errors for mkdir
373
+ }
374
+ }
375
+ }
376
+ async readFile(path, options = {}) {
377
+ const { encoding = 'binary', signal, onDownloadProgress } = options;
378
+ this.checkAborted(signal);
379
+ const key = this.normalizeKey(path);
380
+ if (!key) {
381
+ throw new Error('Cannot read root directory');
382
+ }
383
+ try {
384
+ // MinIO getObject returns Promise<Readable>
385
+ const nodeStream = await this.client.getObject(this.bucket, key);
386
+ const chunks = [];
387
+ let loaded = 0;
388
+ let total = 0;
389
+ // Get object size for progress if available
390
+ try {
391
+ const stat = await this.client.statObject(this.bucket, key);
392
+ total = stat.size;
393
+ } catch {
394
+ // Ignore if stat fails
395
+ }
396
+ return new Promise((resolve, reject) => {
397
+ nodeStream.on('data', (chunk) => {
398
+ chunks.push(chunk);
399
+ loaded += chunk.length;
400
+ if (onDownloadProgress) {
401
+ onDownloadProgress({
402
+ loaded,
403
+ total: total || -1,
404
+ });
405
+ }
406
+ });
407
+ nodeStream.on('end', () => {
408
+ const buffer = Buffer.concat(chunks);
409
+ if (encoding === 'text') {
410
+ resolve(buffer.toString('utf-8'));
411
+ } else {
412
+ resolve(new Uint8Array(buffer));
413
+ }
414
+ });
415
+ nodeStream.on('error', reject);
416
+ if (signal) {
417
+ signal.addEventListener('abort', () => {
418
+ if (nodeStream.destroy) {
419
+ nodeStream.destroy(new Error('The operation was aborted'));
420
+ }
421
+ reject(new Error('The operation was aborted'));
422
+ });
423
+ }
424
+ });
425
+ } catch (error) {
426
+ if (error.code === 'NoSuchKey' || error.code === 'NotFound') {
427
+ throw new Error(`File not found: ${path}`);
428
+ }
429
+ throw error;
430
+ }
431
+ }
432
+ async writeFile(path, data, options = {}) {
433
+ const { signal, overwrite = true, onUploadProgress } = options;
434
+ this.checkAborted(signal);
435
+ const key = this.normalizeKey(path);
436
+ if (!key) {
437
+ throw new Error('Cannot write to root directory');
438
+ }
439
+ // Check if file exists and overwrite is false
440
+ if (!overwrite) {
441
+ const exists = await this.exists(path);
442
+ if (exists) {
443
+ throw new Error(`File already exists: ${path}`);
444
+ }
445
+ }
446
+ // Convert data to stream or buffer
447
+ let stream;
448
+ let size;
449
+ if (data instanceof Readable) {
450
+ stream = data;
451
+ size = 0; // Unknown size
452
+ } else if (data instanceof ReadableStream) {
453
+ // Convert Web ReadableStream to Node Readable
454
+ stream = Readable.fromWeb(data);
455
+ size = 0;
456
+ } else {
457
+ let buffer;
458
+ if (data instanceof ArrayBuffer) {
459
+ buffer = Buffer.from(data);
460
+ } else if (Buffer.isBuffer(data)) {
461
+ buffer = data;
462
+ } else if (typeof data === 'string') {
463
+ buffer = Buffer.from(data, 'utf-8');
464
+ } else {
465
+ // ArrayBufferView
466
+ buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
467
+ }
468
+ size = buffer.length;
469
+ stream = new Readable();
470
+ stream.push(buffer);
471
+ stream.push(null);
472
+ }
473
+ // Wrap stream for progress tracking if needed
474
+ if (onUploadProgress) {
475
+ let loaded = 0;
476
+ const progressStream = new PassThrough();
477
+ stream.on('data', (chunk) => {
478
+ loaded += chunk.length;
479
+ onUploadProgress({
480
+ loaded,
481
+ total: size || -1,
482
+ });
483
+ });
484
+ stream.pipe(progressStream);
485
+ stream = progressStream;
486
+ }
487
+ try {
488
+ await this.client.putObject(this.bucket, key, stream, size);
489
+ } catch (error) {
490
+ throw error;
491
+ }
492
+ }
493
+ async rm(path, options = {}) {
494
+ const { recursive = false, force = false, signal } = options;
495
+ this.checkAborted(signal);
496
+ const key = this.normalizeKey(path);
497
+ if (!key) {
498
+ throw new Error('Cannot remove root directory');
499
+ }
500
+ try {
501
+ if (recursive) {
502
+ // List all objects with this prefix
503
+ const prefix = key.endsWith('/') ? key : key + '/';
504
+ const objectStream = this.client.listObjects(this.bucket, prefix, true);
505
+ const keys = [];
506
+ await new Promise((resolve, reject) => {
507
+ objectStream.on('data', (obj) => {
508
+ if (obj.name) {
509
+ keys.push(obj.name);
510
+ }
511
+ });
512
+ objectStream.on('end', () => {
513
+ resolve();
514
+ });
515
+ objectStream.on('error', reject);
516
+ if (signal) {
517
+ signal.addEventListener('abort', () => {
518
+ objectStream.destroy();
519
+ reject(new Error('The operation was aborted'));
520
+ });
521
+ }
522
+ });
523
+ // Delete all objects
524
+ if (keys.length > 0) {
525
+ await this.client.removeObjects(this.bucket, keys);
526
+ }
527
+ // Also delete the marker object if it exists
528
+ const markerKey = prefix;
529
+ try {
530
+ await this.client.removeObject(this.bucket, markerKey);
531
+ } catch {
532
+ // Ignore if marker doesn't exist
533
+ }
534
+ } else {
535
+ // Delete single object
536
+ try {
537
+ await this.client.removeObject(this.bucket, key);
538
+ } catch (error) {
539
+ // Check if it's a directory with files
540
+ if (error.code === 'NoSuchKey' || error.code === 'NotFound') {
541
+ const prefix = key.endsWith('/') ? key : key + '/';
542
+ const objectStream = this.client.listObjects(this.bucket, prefix, false);
543
+ let hasObjects = false;
544
+ await new Promise((resolve) => {
545
+ objectStream.on('data', () => {
546
+ hasObjects = true;
547
+ objectStream.destroy();
548
+ resolve();
549
+ });
550
+ objectStream.on('end', () => {
551
+ resolve();
552
+ });
553
+ objectStream.on('error', () => {
554
+ resolve();
555
+ });
556
+ });
557
+ if (hasObjects) {
558
+ if (!force) {
559
+ throw new Error('Directory not empty');
560
+ }
561
+ // If force, delete recursively
562
+ const recursiveStream = this.client.listObjects(this.bucket, prefix, true);
563
+ const keys = [];
564
+ await new Promise((resolve) => {
565
+ recursiveStream.on('data', (obj) => {
566
+ if (obj.name) {
567
+ keys.push(obj.name);
568
+ }
569
+ });
570
+ recursiveStream.on('end', () => resolve());
571
+ recursiveStream.on('error', () => resolve());
572
+ });
573
+ if (keys.length > 0) {
574
+ await this.client.removeObjects(this.bucket, keys);
575
+ }
576
+ // Also try to remove the marker
577
+ try {
578
+ await this.client.removeObject(this.bucket, prefix);
579
+ } catch {
580
+ // Ignore
581
+ }
582
+ } else {
583
+ // File doesn't exist
584
+ if (!force) {
585
+ throw new Error('File not found');
586
+ }
587
+ // If force, just return without error
588
+ return;
589
+ }
590
+ } else if (!force) {
591
+ throw error;
592
+ }
593
+ }
594
+ }
595
+ } catch (error) {
596
+ if (force && (error.code === 'NoSuchKey' || error.code === 'NotFound')) {
597
+ return;
598
+ }
599
+ if (force && error.message === 'File not found') {
600
+ return;
601
+ }
602
+ throw error;
603
+ }
604
+ }
605
+ async rename(oldPath, newPath, options = {}) {
606
+ const { signal, overwrite = false } = options;
607
+ this.checkAborted(signal);
608
+ const oldKey = this.normalizeKey(oldPath);
609
+ const newKey = this.normalizeKey(newPath);
610
+ if (!oldKey) {
611
+ throw new Error('Cannot rename root directory');
612
+ }
613
+ // Check if target exists and overwrite is false
614
+ if (!overwrite) {
615
+ const exists = await this.exists(newPath);
616
+ if (exists) {
617
+ throw new Error(`Destination already exists: ${newPath}`);
618
+ }
619
+ }
620
+ try {
621
+ // Check if it's a directory (has objects with prefix)
622
+ const isDir = oldKey.endsWith('/');
623
+ const prefix = isDir ? oldKey : oldKey + '/';
624
+ const objectStream = this.client.listObjects(this.bucket, prefix, true);
625
+ const objects = [];
626
+ await new Promise((resolve, reject) => {
627
+ objectStream.on('data', (obj) => {
628
+ if (obj.name) {
629
+ objects.push({
630
+ name: obj.name,
631
+ });
632
+ }
633
+ });
634
+ objectStream.on('end', () => {
635
+ resolve();
636
+ });
637
+ objectStream.on('error', reject);
638
+ if (signal) {
639
+ signal.addEventListener('abort', () => {
640
+ objectStream.destroy();
641
+ reject(new Error('The operation was aborted'));
642
+ });
643
+ }
644
+ });
645
+ if (objects.length > 0) {
646
+ // It's a directory or has multiple objects, move all
647
+ const newPrefix = newKey.endsWith('/') ? newKey : newKey + '/';
648
+ // Move all objects
649
+ await Promise.all(
650
+ objects.map(async (obj) => {
651
+ const objKey = obj.name;
652
+ if (!objKey) return;
653
+ const relativeKey = objKey.slice(prefix.length);
654
+ const newObjKey = newPrefix + relativeKey;
655
+ // Copy then delete
656
+ // MinIO copyObject signature: copyObject(bucketName, objectName, sourceObject)
657
+ // sourceObject should be a string in format "bucket/object"
658
+ await this.client.copyObject(this.bucket, newObjKey, `${this.bucket}/${objKey}`);
659
+ await this.client.removeObject(this.bucket, objKey);
660
+ }),
661
+ );
662
+ // Move marker object if it exists (skip if it's a directory marker with data)
663
+ try {
664
+ // Check if it's a directory marker (ends with /)
665
+ if (prefix.endsWith('/')) {
666
+ // Try to copy the marker, but skip if it fails due to "contains data payload"
667
+ try {
668
+ await this.client.copyObject(this.bucket, newPrefix, `${this.bucket}/${prefix}`);
669
+ await this.client.removeObject(this.bucket, prefix);
670
+ } catch (error) {
671
+ // If error is about data payload, just remove the old marker
672
+ // Directory markers are optional in S3
673
+ if (error.message?.includes('data payload') || error.code === 'InvalidRequest') {
674
+ await this.client.removeObject(this.bucket, prefix);
675
+ } else {
676
+ throw error;
677
+ }
678
+ }
679
+ } else {
680
+ await this.client.copyObject(this.bucket, newPrefix, `${this.bucket}/${prefix}`);
681
+ await this.client.removeObject(this.bucket, prefix);
682
+ }
683
+ } catch {
684
+ // Ignore if marker doesn't exist
685
+ }
686
+ } else {
687
+ // Single file - copy then delete
688
+ await this.client.copyObject(this.bucket, newKey, `${this.bucket}/${oldKey}`);
689
+ await this.client.removeObject(this.bucket, oldKey);
690
+ }
691
+ } catch (error) {
692
+ if (error.code === 'NoSuchKey' || error.code === 'NotFound') {
693
+ throw new Error(`Source file not found: ${oldPath}`);
694
+ }
695
+ throw error;
696
+ }
697
+ }
698
+ async exists(path) {
699
+ try {
700
+ const key = this.normalizeKey(path);
701
+ if (!key) {
702
+ return true; // Root always exists
703
+ }
704
+ await this.client.statObject(this.bucket, key);
705
+ return true;
706
+ } catch (error) {
707
+ if (error.code === 'NotFound' || error.code === 'NoSuchKey') {
708
+ // Check if it's a directory
709
+ const key = this.normalizeKey(path);
710
+ const dirKey = key.endsWith('/') ? key : key + '/';
711
+ try {
712
+ const objectStream = this.client.listObjects(this.bucket, dirKey, false);
713
+ let hasObjects = false;
714
+ await new Promise((resolve) => {
715
+ objectStream.on('data', () => {
716
+ hasObjects = true;
717
+ objectStream.destroy();
718
+ resolve();
719
+ });
720
+ objectStream.on('end', () => {
721
+ resolve();
722
+ });
723
+ objectStream.on('error', () => {
724
+ resolve();
725
+ });
726
+ });
727
+ return hasObjects;
728
+ } catch {
729
+ return false;
730
+ }
731
+ }
732
+ return false;
733
+ }
734
+ }
735
+ async copy(src, dest, options = {}) {
736
+ const { signal, overwrite = true, shallow = false } = options;
737
+ this.checkAborted(signal);
738
+ const srcKey = this.normalizeKey(src);
739
+ const destKey = this.normalizeKey(dest);
740
+ if (!srcKey) {
741
+ throw new Error('Cannot copy root directory');
742
+ }
743
+ // Check if source exists
744
+ try {
745
+ const srcStat = await this.stat(src);
746
+ // Check if destination exists and overwrite is false
747
+ if (!overwrite) {
748
+ const exists = await this.exists(dest);
749
+ if (exists) {
750
+ throw new Error(`Destination already exists: ${dest}`);
751
+ }
752
+ }
753
+ if (srcStat.kind === 'directory') {
754
+ // Copy directory recursively
755
+ const srcPrefix = srcKey.endsWith('/') ? srcKey : srcKey + '/';
756
+ const destPrefix = destKey.endsWith('/') ? destKey : destKey + '/';
757
+ const objectStream = this.client.listObjects(this.bucket, srcPrefix, !shallow);
758
+ const objects = [];
759
+ await new Promise((resolve, reject) => {
760
+ objectStream.on('data', (obj) => {
761
+ if (obj.name) {
762
+ objects.push({
763
+ name: obj.name,
764
+ });
765
+ }
766
+ });
767
+ objectStream.on('end', () => {
768
+ resolve();
769
+ });
770
+ objectStream.on('error', reject);
771
+ if (signal) {
772
+ signal.addEventListener('abort', () => {
773
+ objectStream.destroy();
774
+ reject(new Error('The operation was aborted'));
775
+ });
776
+ }
777
+ });
778
+ // Copy all objects
779
+ await Promise.all(
780
+ objects.map(async (obj) => {
781
+ const objKey = obj.name;
782
+ if (!objKey) return;
783
+ const relativeKey = objKey.slice(srcPrefix.length);
784
+ const newObjKey = destPrefix + relativeKey;
785
+ await this.client.copyObject(this.bucket, newObjKey, `${this.bucket}/${objKey}`);
786
+ }),
787
+ );
788
+ // Copy marker object if it exists (skip if it's a directory marker with data)
789
+ try {
790
+ // Check if it's a directory marker (ends with /)
791
+ if (srcPrefix.endsWith('/')) {
792
+ // Try to copy the marker, but skip if it fails due to "contains data payload"
793
+ try {
794
+ await this.client.copyObject(this.bucket, destPrefix, `${this.bucket}/${srcPrefix}`);
795
+ } catch (error) {
796
+ // If error is about data payload, just skip copying the marker
797
+ // Directory markers are optional in S3
798
+ if (!error.message?.includes('data payload') && error.code !== 'InvalidRequest') {
799
+ throw error;
800
+ }
801
+ }
802
+ } else {
803
+ await this.client.copyObject(this.bucket, destPrefix, `${this.bucket}/${srcPrefix}`);
804
+ }
805
+ } catch {
806
+ // Ignore if marker doesn't exist
807
+ }
808
+ } else {
809
+ // Copy single file
810
+ await this.client.copyObject(this.bucket, destKey, `${this.bucket}/${srcKey}`);
811
+ }
812
+ } catch (error) {
813
+ if (error.code === 'NoSuchKey' || error.code === 'NotFound') {
814
+ throw new Error(`Source file not found: ${src}`);
815
+ }
816
+ throw error;
817
+ }
818
+ }
819
+ createReadStream(path, options = {}) {
820
+ const key = this.normalizeKey(path);
821
+ if (!key) {
822
+ throw new Error('Cannot read root directory');
823
+ }
824
+ const { range, signal } = options;
825
+ // MinIO getObject and getPartialObject both return Promise<Readable>
826
+ // Create a Readable stream that waits for the promise to resolve
827
+ const client = this.client;
828
+ const bucket = this.bucket;
829
+ const readable = new Readable({
830
+ async read() {
831
+ // Only initialize once
832
+ if (readable._initialized) {
833
+ return;
834
+ }
835
+ readable._initialized = true;
836
+ try {
837
+ let nodeStream;
838
+ if (range) {
839
+ // Use getPartialObject for range requests
840
+ // getPartialObject's length parameter: number of bytes to read (not end position)
841
+ // So for range {start: 0, end: 4}, we need length = end - start + 1 = 5
842
+ const length = range.end !== undefined ? range.end - range.start + 1 : undefined;
843
+ nodeStream = await client.getPartialObject(bucket, key, range.start, length);
844
+ } else {
845
+ nodeStream = await client.getObject(bucket, key);
846
+ }
847
+ // Pipe the actual stream to our readable
848
+ nodeStream.on('data', (chunk) => {
849
+ if (!readable.push(chunk)) {
850
+ // If push returns false, the stream is backpressured
851
+ nodeStream.pause();
852
+ }
853
+ });
854
+ nodeStream.on('end', () => {
855
+ readable.push(null);
856
+ });
857
+ nodeStream.on('error', (err) => {
858
+ readable.emit('error', err);
859
+ });
860
+ if (signal) {
861
+ signal.addEventListener('abort', () => {
862
+ if (nodeStream.destroy) {
863
+ nodeStream.destroy(new Error('The operation was aborted'));
864
+ }
865
+ readable.emit('error', new Error('The operation was aborted'));
866
+ });
867
+ }
868
+ } catch (error) {
869
+ readable.emit('error', error);
870
+ }
871
+ },
872
+ });
873
+ return readable;
874
+ }
875
+ createReadableStream(path, options = {}) {
876
+ const key = this.normalizeKey(path);
877
+ if (!key) {
878
+ throw new Error('Cannot read root directory');
879
+ }
880
+ const { range, signal } = options;
881
+ // MinIO getObject and getPartialObject both return Promise<Readable>
882
+ const client = this.client;
883
+ const bucket = this.bucket;
884
+ return new ReadableStream({
885
+ async start(controller) {
886
+ try {
887
+ let nodeStream;
888
+ if (range) {
889
+ // Use getPartialObject for range requests
890
+ // getPartialObject's length parameter: number of bytes to read (not end position)
891
+ // So for range {start: 0, end: 4}, we need length = end - start + 1 = 5
892
+ const length = range.end !== undefined ? range.end - range.start + 1 : undefined;
893
+ nodeStream = await client.getPartialObject(bucket, key, range.start, length);
894
+ } else {
895
+ nodeStream = await client.getObject(bucket, key);
896
+ }
897
+ nodeStream.on('data', (chunk) => {
898
+ controller.enqueue(chunk);
899
+ });
900
+ nodeStream.on('end', () => {
901
+ controller.close();
902
+ });
903
+ nodeStream.on('error', (err) => {
904
+ controller.error(err);
905
+ });
906
+ if (signal) {
907
+ signal.addEventListener('abort', () => {
908
+ if (nodeStream.destroy) {
909
+ nodeStream.destroy(new Error('The operation was aborted'));
910
+ }
911
+ controller.error(new Error('The operation was aborted'));
912
+ });
913
+ }
914
+ } catch (error) {
915
+ controller.error(error);
916
+ }
917
+ },
918
+ });
919
+ }
920
+ createWriteStream(path, options) {
921
+ throw new Error('Not implemented');
922
+ }
923
+ createWritableStream(path, options = {}) {
924
+ const key = this.normalizeKey(path);
925
+ if (!key) {
926
+ throw new Error('Cannot write to root directory');
927
+ }
928
+ const { signal, overwrite = true } = options;
929
+ this.checkAborted(signal);
930
+ // Create a WritableStream that buffers data and uploads when done
931
+ const buffer = [];
932
+ let controller;
933
+ const client = this.client;
934
+ const bucket = this.bucket;
935
+ const checkAborted = this.checkAborted.bind(this);
936
+ return new WritableStream({
937
+ start(ctrl) {
938
+ controller = ctrl;
939
+ },
940
+ async write(chunk) {
941
+ buffer.push(chunk);
942
+ },
943
+ async close() {
944
+ try {
945
+ checkAborted(signal);
946
+ const data = Buffer.concat(buffer.map((chunk) => Buffer.from(chunk)));
947
+ const stream = new Readable();
948
+ stream.push(data);
949
+ stream.push(null);
950
+ await client.putObject(bucket, key, stream, data.length);
951
+ // Controller closes automatically when close() completes successfully
952
+ } catch (error) {
953
+ controller.error(error);
954
+ }
955
+ },
956
+ abort(reason) {
957
+ buffer.length = 0;
958
+ controller.error(reason);
959
+ },
960
+ });
961
+ }
962
+ getUrl(path, options) {
963
+ if (typeof path === 'object' && path?.kind !== 'file') {
964
+ return;
965
+ }
966
+ const key = typeof path === 'string' ? this.normalizeKey(path) : this.normalizeKey(path.path);
967
+ if (!key) {
968
+ return;
969
+ }
970
+ // MinIO supports presigned URLs, but getUrl is synchronous
971
+ // We can't generate presigned URLs synchronously, so return undefined
972
+ // For presigned URLs, users should use a separate method or await the promise
973
+ return undefined;
974
+ }
975
+ };
976
+
977
+ //# sourceMappingURL=createMinioFileSystem.js.map