@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.
Files changed (351) hide show
  1. package/lib/ai/qwen3vl/index.js +2 -0
  2. package/lib/ai/qwen3vl/index.js.map +1 -0
  3. package/lib/ai/qwen3vl/utils.js +31 -0
  4. package/lib/ai/qwen3vl/utils.js.map +1 -0
  5. package/lib/ai/vision/DocLayoutElementTypeSchema.js +28 -0
  6. package/lib/ai/vision/DocLayoutElementTypeSchema.js.map +1 -0
  7. package/lib/ai/vision/ImageAnnotationSchema.js +50 -0
  8. package/lib/ai/vision/ImageAnnotationSchema.js.map +1 -0
  9. package/lib/ai/vision/index.js +3 -0
  10. package/lib/ai/vision/index.js.map +1 -0
  11. package/lib/ai/vision/resolveImageAnnotation.js +105 -0
  12. package/lib/ai/vision/resolveImageAnnotation.js.map +1 -0
  13. package/lib/cn/ChineseResidentIdNo.js +21 -14
  14. package/lib/cn/ChineseResidentIdNo.js.map +1 -0
  15. package/lib/cn/ChineseResidentIdNo.test.js +1 -1
  16. package/lib/cn/DivisionCode.js +30 -25
  17. package/lib/cn/DivisionCode.js.map +1 -0
  18. package/lib/cn/DivisionCode.test.js +1 -1
  19. package/lib/cn/Mod11.js +38 -81
  20. package/lib/cn/Mod11.js.map +1 -0
  21. package/lib/cn/Mod31.js +41 -90
  22. package/lib/cn/Mod31.js.map +1 -0
  23. package/lib/cn/UnifiedSocialCreditCode.js +43 -34
  24. package/lib/cn/UnifiedSocialCreditCode.js.map +1 -0
  25. package/lib/cn/UnifiedSocialCreditCode.test.js +1 -1
  26. package/lib/cn/formatChineseAmount.js +77 -0
  27. package/lib/cn/formatChineseAmount.js.map +1 -0
  28. package/lib/cn/index.js +7 -1
  29. package/lib/cn/index.js.map +1 -0
  30. package/lib/cn/parseChineseNumber.js +94 -0
  31. package/lib/cn/parseChineseNumber.js.map +1 -0
  32. package/lib/cn/parseChineseNumber.test.js +278 -0
  33. package/lib/cn/pinyin/cartesianProduct.js +22 -0
  34. package/lib/cn/pinyin/cartesianProduct.js.map +1 -0
  35. package/lib/cn/pinyin/cartesianProduct.test.js +179 -0
  36. package/lib/cn/pinyin/data.json +23573 -0
  37. package/lib/cn/pinyin/loader.js +14 -0
  38. package/lib/cn/pinyin/loader.js.map +1 -0
  39. package/lib/cn/pinyin/preload.js +3 -0
  40. package/lib/cn/pinyin/preload.js.map +1 -0
  41. package/lib/cn/pinyin/toPinyin.test.js +167 -0
  42. package/lib/cn/pinyin/toPinyinPure.js +33 -0
  43. package/lib/cn/pinyin/toPinyinPure.js.map +1 -0
  44. package/lib/cn/pinyin/transform.js +14 -0
  45. package/lib/cn/pinyin/transform.js.map +1 -0
  46. package/lib/cn/types.d.js +2 -0
  47. package/lib/cn/types.d.js.map +1 -0
  48. package/lib/consola/createStandardConsolaReporter.js +6 -6
  49. package/lib/consola/createStandardConsolaReporter.js.map +1 -0
  50. package/lib/consola/formatLogObject.js +65 -145
  51. package/lib/consola/formatLogObject.js.map +1 -0
  52. package/lib/consola/formatLogObject.test.js +184 -0
  53. package/lib/consola/index.js +1 -0
  54. package/lib/consola/index.js.map +1 -0
  55. package/lib/data/formatSort.js +6 -5
  56. package/lib/data/formatSort.js.map +1 -0
  57. package/lib/data/index.js +1 -0
  58. package/lib/data/index.js.map +1 -0
  59. package/lib/data/maybeNumber.js +5 -7
  60. package/lib/data/maybeNumber.js.map +1 -0
  61. package/lib/data/parseSort.js +22 -28
  62. package/lib/data/parseSort.js.map +1 -0
  63. package/lib/data/resolvePagination.js +13 -17
  64. package/lib/data/resolvePagination.js.map +1 -0
  65. package/lib/data/types.d.js +2 -0
  66. package/lib/data/types.d.js.map +1 -0
  67. package/lib/dayjs/dayjs.js +21 -19
  68. package/lib/dayjs/dayjs.js.map +1 -0
  69. package/lib/dayjs/formatDuration.js +15 -14
  70. package/lib/dayjs/formatDuration.js.map +1 -0
  71. package/lib/dayjs/index.js +2 -0
  72. package/lib/dayjs/index.js.map +1 -0
  73. package/lib/dayjs/parseDuration.js +5 -8
  74. package/lib/dayjs/parseDuration.js.map +1 -0
  75. package/lib/dayjs/parseRelativeTime.js +90 -0
  76. package/lib/dayjs/parseRelativeTime.js.map +1 -0
  77. package/lib/dayjs/parseRelativeTime.test.js +247 -0
  78. package/lib/dayjs/resolveRelativeTime.js +158 -0
  79. package/lib/dayjs/resolveRelativeTime.js.map +1 -0
  80. package/lib/dayjs/resolveRelativeTime.test.js +310 -0
  81. package/lib/decimal/index.js +1 -0
  82. package/lib/decimal/index.js.map +1 -0
  83. package/lib/decimal/parseDecimal.js +3 -1
  84. package/lib/decimal/parseDecimal.js.map +1 -0
  85. package/lib/emittery/emitter.js +10 -0
  86. package/lib/emittery/emitter.js.map +1 -0
  87. package/lib/emittery/index.js +2 -0
  88. package/lib/emittery/index.js.map +1 -0
  89. package/lib/foundation/schema/SexType.js +5 -3
  90. package/lib/foundation/schema/SexType.js.map +1 -0
  91. package/lib/foundation/schema/index.js +1 -0
  92. package/lib/foundation/schema/index.js.map +1 -0
  93. package/lib/foundation/schema/parseSexType.js +1 -0
  94. package/lib/foundation/schema/parseSexType.js.map +1 -0
  95. package/lib/foundation/schema/types.js +4 -2
  96. package/lib/foundation/schema/types.js.map +1 -0
  97. package/lib/fs/FileSystemError.js +23 -0
  98. package/lib/fs/FileSystemError.js.map +1 -0
  99. package/lib/fs/IFileSystem.d.js +3 -0
  100. package/lib/fs/IFileSystem.d.js.map +1 -0
  101. package/lib/fs/MemoryFileSystem.test.js +188 -0
  102. package/lib/fs/createBrowserFileSystem.js +248 -0
  103. package/lib/fs/createBrowserFileSystem.js.map +1 -0
  104. package/lib/fs/createMemoryFileSystem.js +516 -0
  105. package/lib/fs/createMemoryFileSystem.js.map +1 -0
  106. package/lib/fs/createSandboxFileSystem.js +108 -0
  107. package/lib/fs/createSandboxFileSystem.js.map +1 -0
  108. package/lib/fs/createWebDavFileSystem.js +137 -0
  109. package/lib/fs/createWebDavFileSystem.js.map +1 -0
  110. package/lib/fs/findMimeType.js +17 -0
  111. package/lib/fs/findMimeType.js.map +1 -0
  112. package/lib/fs/index.js +8 -0
  113. package/lib/fs/index.js.map +1 -0
  114. package/lib/fs/orpc/FileSystemContract.js +93 -0
  115. package/lib/fs/orpc/FileSystemContract.js.map +1 -0
  116. package/lib/fs/orpc/createContractClientFileSystem.js +93 -0
  117. package/lib/fs/orpc/createContractClientFileSystem.js.map +1 -0
  118. package/lib/fs/orpc/index.js +3 -0
  119. package/lib/fs/orpc/index.js.map +1 -0
  120. package/lib/fs/orpc/server/createFileSystemContractImpl.js +63 -0
  121. package/lib/fs/orpc/server/createFileSystemContractImpl.js.map +1 -0
  122. package/lib/fs/orpc/server/index.js +2 -0
  123. package/lib/fs/orpc/server/index.js.map +1 -0
  124. package/lib/fs/s3/createS3MiniFileSystem.js +705 -0
  125. package/lib/fs/s3/createS3MiniFileSystem.js.map +1 -0
  126. package/lib/fs/s3/index.js +2 -0
  127. package/lib/fs/s3/index.js.map +1 -0
  128. package/lib/fs/s3/s3mini.test.js +584 -0
  129. package/lib/fs/scandir.js +59 -0
  130. package/lib/fs/scandir.js.map +1 -0
  131. package/lib/fs/server/createDatabaseFileSystem.js +750 -0
  132. package/lib/fs/server/createDatabaseFileSystem.js.map +1 -0
  133. package/lib/fs/server/createNodeFileSystem.js +401 -0
  134. package/lib/fs/server/createNodeFileSystem.js.map +1 -0
  135. package/lib/fs/server/dbfs.test.js +221 -0
  136. package/lib/fs/server/index.js +2 -0
  137. package/lib/fs/server/index.js.map +1 -0
  138. package/lib/fs/server/loadTestDatabase.js +127 -0
  139. package/lib/fs/server/loadTestDatabase.js.map +1 -0
  140. package/lib/fs/tests/runFileSystemTest.js +318 -0
  141. package/lib/fs/tests/runFileSystemTest.js.map +1 -0
  142. package/lib/fs/types.js +27 -0
  143. package/lib/fs/types.js.map +1 -0
  144. package/lib/fs/utils/getFileUrl.js +35 -0
  145. package/lib/fs/utils/getFileUrl.js.map +1 -0
  146. package/lib/fs/utils.js +22 -0
  147. package/lib/fs/utils.js.map +1 -0
  148. package/lib/index.js +1 -0
  149. package/lib/index.js.map +1 -0
  150. package/lib/jsonschema/JsonSchema.js +146 -172
  151. package/lib/jsonschema/JsonSchema.js.map +1 -0
  152. package/lib/jsonschema/forEachJsonSchema.js +44 -0
  153. package/lib/jsonschema/forEachJsonSchema.js.map +1 -0
  154. package/lib/jsonschema/index.js +2 -0
  155. package/lib/jsonschema/index.js.map +1 -0
  156. package/lib/jsonschema/types.d.js +2 -0
  157. package/lib/jsonschema/types.d.js.map +1 -0
  158. package/lib/meta/defineFileType.js +20 -103
  159. package/lib/meta/defineFileType.js.map +1 -0
  160. package/lib/meta/defineInit.js +31 -250
  161. package/lib/meta/defineInit.js.map +1 -0
  162. package/lib/meta/defineMetadata.js +24 -140
  163. package/lib/meta/defineMetadata.js.map +1 -0
  164. package/lib/meta/index.js +1 -0
  165. package/lib/meta/index.js.map +1 -0
  166. package/lib/orpc/createOpenApiContractClient.js +27 -0
  167. package/lib/orpc/createOpenApiContractClient.js.map +1 -0
  168. package/lib/orpc/createRpcContractClient.js +34 -0
  169. package/lib/orpc/createRpcContractClient.js.map +1 -0
  170. package/lib/orpc/index.js +3 -0
  171. package/lib/orpc/index.js.map +1 -0
  172. package/lib/orpc/resolveLinkPlugins.js +28 -0
  173. package/lib/orpc/resolveLinkPlugins.js.map +1 -0
  174. package/lib/password/PHC.js +63 -87
  175. package/lib/password/PHC.js.map +1 -0
  176. package/lib/password/PHC.test.js +11 -3
  177. package/lib/password/Password.js +29 -294
  178. package/lib/password/Password.js.map +1 -0
  179. package/lib/password/Password.test.js +35 -22
  180. package/lib/password/createArgon2PasswordAlgorithm.js +35 -191
  181. package/lib/password/createArgon2PasswordAlgorithm.js.map +1 -0
  182. package/lib/password/createBase64PasswordAlgorithm.js +8 -141
  183. package/lib/password/createBase64PasswordAlgorithm.js.map +1 -0
  184. package/lib/password/createBcryptPasswordAlgorithm.js +13 -168
  185. package/lib/password/createBcryptPasswordAlgorithm.js.map +1 -0
  186. package/lib/password/createPBKDF2PasswordAlgorithm.js +46 -228
  187. package/lib/password/createPBKDF2PasswordAlgorithm.js.map +1 -0
  188. package/lib/password/createScryptPasswordAlgorithm.js +55 -211
  189. package/lib/password/createScryptPasswordAlgorithm.js.map +1 -0
  190. package/lib/password/index.js +1 -0
  191. package/lib/password/index.js.map +1 -0
  192. package/lib/password/server/index.js +1 -0
  193. package/lib/password/server/index.js.map +1 -0
  194. package/lib/resource/Identifiable.js +2 -0
  195. package/lib/resource/Identifiable.js.map +1 -0
  196. package/lib/resource/ListQuery.js +21 -93
  197. package/lib/resource/ListQuery.js.map +1 -0
  198. package/lib/resource/getTitleOfResource.js +3 -5
  199. package/lib/resource/getTitleOfResource.js.map +1 -0
  200. package/lib/resource/index.js +1 -0
  201. package/lib/resource/index.js.map +1 -0
  202. package/lib/resource/schema/AnyResourceSchema.js +2 -1
  203. package/lib/resource/schema/AnyResourceSchema.js.map +1 -0
  204. package/lib/resource/schema/BaseResourceSchema.js +2 -1
  205. package/lib/resource/schema/BaseResourceSchema.js.map +1 -0
  206. package/lib/resource/schema/ResourceActionType.js +6 -4
  207. package/lib/resource/schema/ResourceActionType.js.map +1 -0
  208. package/lib/resource/schema/ResourceStatus.js +5 -3
  209. package/lib/resource/schema/ResourceStatus.js.map +1 -0
  210. package/lib/resource/schema/ResourceType.js +5 -3
  211. package/lib/resource/schema/ResourceType.js.map +1 -0
  212. package/lib/resource/schema/index.js +1 -0
  213. package/lib/resource/schema/index.js.map +1 -0
  214. package/lib/resource/schema/types.js +16 -20
  215. package/lib/resource/schema/types.js.map +1 -0
  216. package/lib/s3/formatS3Url.js +65 -0
  217. package/lib/s3/formatS3Url.js.map +1 -0
  218. package/lib/s3/formatS3Url.test.js +262 -0
  219. package/lib/s3/index.js +3 -0
  220. package/lib/s3/index.js.map +1 -0
  221. package/lib/s3/parseS3Url.js +65 -0
  222. package/lib/s3/parseS3Url.js.map +1 -0
  223. package/lib/s3/parseS3Url.test.js +270 -0
  224. package/lib/schema/SchemaRegistry.js +38 -38
  225. package/lib/schema/SchemaRegistry.js.map +1 -0
  226. package/lib/schema/TypeSchema.d.js +2 -0
  227. package/lib/schema/TypeSchema.d.js.map +1 -0
  228. package/lib/schema/createSchemaData.js +26 -125
  229. package/lib/schema/createSchemaData.js.map +1 -0
  230. package/lib/schema/findJsonSchemaByPath.js +13 -36
  231. package/lib/schema/findJsonSchemaByPath.js.map +1 -0
  232. package/lib/schema/formatZodError.js +140 -0
  233. package/lib/schema/formatZodError.js.map +1 -0
  234. package/lib/schema/formatZodError.test.js +196 -0
  235. package/lib/schema/getSchemaCache.js +5 -5
  236. package/lib/schema/getSchemaCache.js.map +1 -0
  237. package/lib/schema/getSchemaOptions.js +8 -11
  238. package/lib/schema/getSchemaOptions.js.map +1 -0
  239. package/lib/schema/index.js +2 -1
  240. package/lib/schema/index.js.map +1 -0
  241. package/lib/schema/toJsonSchema.js +47 -290
  242. package/lib/schema/toJsonSchema.js.map +1 -0
  243. package/lib/schema/validate.js +33 -45
  244. package/lib/schema/validate.js.map +1 -0
  245. package/lib/tools/generateSchema.js +39 -197
  246. package/lib/tools/generateSchema.js.map +1 -0
  247. package/lib/tools/renderJsonSchemaToMarkdownDoc.js +55 -143
  248. package/lib/tools/renderJsonSchemaToMarkdownDoc.js.map +1 -0
  249. package/lib/utils/buildBaseUrl.js +13 -0
  250. package/lib/utils/buildBaseUrl.js.map +1 -0
  251. package/lib/utils/buildRedactorFormSchema.js +59 -0
  252. package/lib/utils/buildRedactorFormSchema.js.map +1 -0
  253. package/lib/utils/getEstimateProcessTime.js +12 -11
  254. package/lib/utils/getEstimateProcessTime.js.map +1 -0
  255. package/lib/utils/index.js +3 -0
  256. package/lib/utils/index.js.map +1 -0
  257. package/lib/utils/resolveFeatureOptions.js +12 -0
  258. package/lib/utils/resolveFeatureOptions.js.map +1 -0
  259. package/package.json +61 -13
  260. package/src/ai/qwen3vl/index.ts +1 -0
  261. package/src/ai/qwen3vl/utils.ts +36 -0
  262. package/src/ai/vision/DocLayoutElementTypeSchema.ts +30 -0
  263. package/src/ai/vision/ImageAnnotationSchema.ts +60 -0
  264. package/src/ai/vision/index.ts +2 -0
  265. package/src/ai/vision/resolveImageAnnotation.ts +135 -0
  266. package/src/cn/ChineseResidentIdNo.test.ts +1 -1
  267. package/src/cn/ChineseResidentIdNo.ts +8 -0
  268. package/src/cn/DivisionCode.test.ts +1 -1
  269. package/src/cn/DivisionCode.ts +8 -0
  270. package/src/cn/UnifiedSocialCreditCode.test.ts +1 -1
  271. package/src/cn/UnifiedSocialCreditCode.ts +15 -0
  272. package/src/cn/__snapshots__/UnifiedSocialCreditCode.test.ts.snap +23 -0
  273. package/src/cn/formatChineseAmount.ts +61 -0
  274. package/src/cn/index.ts +7 -1
  275. package/src/cn/parseChineseNumber.test.ts +159 -0
  276. package/src/cn/parseChineseNumber.ts +97 -0
  277. package/src/cn/pinyin/cartesianProduct.test.ts +64 -0
  278. package/src/cn/pinyin/cartesianProduct.ts +24 -0
  279. package/src/cn/pinyin/data.json +23573 -0
  280. package/src/cn/pinyin/loader.ts +12 -0
  281. package/src/cn/pinyin/preload.ts +3 -0
  282. package/src/cn/pinyin/toPinyin.test.ts +12 -0
  283. package/src/cn/pinyin/toPinyinPure.ts +43 -0
  284. package/src/cn/pinyin/transform.ts +12 -0
  285. package/src/consola/formatLogObject.test.ts +27 -0
  286. package/src/consola/formatLogObject.ts +34 -6
  287. package/src/dayjs/dayjs.ts +18 -18
  288. package/src/dayjs/index.ts +3 -1
  289. package/src/dayjs/parseRelativeTime.test.ts +185 -0
  290. package/src/dayjs/parseRelativeTime.ts +115 -0
  291. package/src/dayjs/resolveRelativeTime.test.ts +357 -0
  292. package/src/dayjs/resolveRelativeTime.ts +167 -0
  293. package/src/emittery/emitter.ts +9 -0
  294. package/src/emittery/index.ts +1 -0
  295. package/src/fs/FileSystemError.ts +26 -0
  296. package/src/fs/IFileSystem.d.ts +102 -0
  297. package/src/fs/MemoryFileSystem.test.ts +37 -0
  298. package/src/fs/createBrowserFileSystem.ts +291 -0
  299. package/src/fs/createMemoryFileSystem.ts +604 -0
  300. package/src/fs/createSandboxFileSystem.ts +136 -0
  301. package/src/fs/createWebDavFileSystem.ts +172 -0
  302. package/src/fs/findMimeType.ts +23 -0
  303. package/src/fs/index.ts +8 -0
  304. package/src/fs/orpc/FileSystemContract.ts +92 -0
  305. package/src/fs/orpc/createContractClientFileSystem.ts +115 -0
  306. package/src/fs/orpc/index.ts +2 -0
  307. package/src/fs/orpc/server/createFileSystemContractImpl.ts +64 -0
  308. package/src/fs/orpc/server/index.ts +1 -0
  309. package/src/fs/s3/createS3MiniFileSystem.ts +830 -0
  310. package/src/fs/s3/index.ts +1 -0
  311. package/src/fs/s3/s3mini.test.ts +264 -0
  312. package/src/fs/scandir.ts +75 -0
  313. package/src/fs/server/createDatabaseFileSystem.ts +668 -0
  314. package/src/fs/server/createNodeFileSystem.ts +499 -0
  315. package/src/fs/server/dbfs.test.ts +47 -0
  316. package/src/fs/server/index.ts +1 -0
  317. package/src/fs/server/loadTestDatabase.ts +131 -0
  318. package/src/fs/tests/runFileSystemTest.ts +288 -0
  319. package/src/fs/types.ts +29 -0
  320. package/src/fs/utils/getFileUrl.ts +44 -0
  321. package/src/fs/utils.ts +23 -0
  322. package/src/jsonschema/JsonSchema.ts +118 -110
  323. package/src/jsonschema/forEachJsonSchema.ts +50 -0
  324. package/src/jsonschema/index.ts +1 -0
  325. package/src/orpc/createOpenApiContractClient.ts +52 -0
  326. package/src/orpc/createRpcContractClient.ts +50 -0
  327. package/src/orpc/index.ts +2 -0
  328. package/src/orpc/resolveLinkPlugins.ts +29 -0
  329. package/src/password/PHC.ts +3 -3
  330. package/src/password/Password.test.ts +1 -1
  331. package/src/password/createPBKDF2PasswordAlgorithm.ts +2 -2
  332. package/src/resource/schema/AnyResourceSchema.ts +16 -2
  333. package/src/s3/formatS3Url.test.ts +254 -0
  334. package/src/s3/formatS3Url.ts +84 -0
  335. package/src/s3/index.ts +2 -0
  336. package/src/s3/parseS3Url.test.ts +258 -0
  337. package/src/s3/parseS3Url.ts +88 -0
  338. package/src/schema/SchemaRegistry.ts +35 -33
  339. package/src/schema/formatZodError.test.ts +196 -0
  340. package/src/schema/formatZodError.ts +151 -0
  341. package/src/schema/getSchemaOptions.ts +2 -2
  342. package/src/schema/index.ts +1 -1
  343. package/src/utils/buildBaseUrl.ts +12 -0
  344. package/src/utils/buildRedactorFormSchema.ts +85 -0
  345. package/src/utils/index.ts +4 -0
  346. package/src/utils/resolveFeatureOptions.ts +14 -0
  347. package/src/cn/ChineseResidentIdNo.mod.ts +0 -7
  348. package/src/cn/DivisionCode.mod.ts +0 -7
  349. package/src/cn/UnifiedSocialCreditCode.mod.ts +0 -7
  350. package/src/cn/mod.ts +0 -3
  351. package/src/schema/SchemaRegistry.mod.ts +0 -1
@@ -0,0 +1 @@
1
+ export { createS3MiniFileSystem } from './createS3MiniFileSystem';
@@ -0,0 +1,264 @@
1
+ import { S3mini } from 's3mini';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'vitest';
3
+
4
+ /**
5
+ * Tests for s3mini and S3 filesystem behavior
6
+ *
7
+ * Uses MinIO Play server for integration testing:
8
+ * - Endpoint: play.min.io
9
+ * - Access Key: Q3AM3UQ867SPQQA43P2F
10
+ * - Secret Key: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
11
+ *
12
+ * Note: s3mini expects bucket name to be part of the endpoint URL.
13
+ * e.g., https://play.min.io/bucket-name
14
+ *
15
+ * s3mini does not support anonymous access to public S3 buckets.
16
+ */
17
+
18
+ // Set to true to run network tests
19
+ const RUN_NETWORK_TESTS = process.env.RUN_S3_NETWORK_TESTS === 'true';
20
+
21
+ // Generate unique bucket name for this test run
22
+ const TEST_BUCKET = `s3mini-test-${Date.now()}`;
23
+
24
+ describe('s3mini with MinIO Play', () => {
25
+ let client: S3mini;
26
+
27
+ beforeAll(async () => {
28
+ if (!RUN_NETWORK_TESTS) return;
29
+
30
+ // s3mini expects bucket name in the endpoint URL
31
+ client = new S3mini({
32
+ endpoint: `https://play.min.io/${TEST_BUCKET}`,
33
+ accessKeyId: 'Q3AM3UQ867SPQQA43P2F',
34
+ secretAccessKey: 'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
35
+ region: 'us-east-1',
36
+ });
37
+
38
+ // Create test bucket
39
+ try {
40
+ await client.createBucket();
41
+ console.log(`Created bucket: ${TEST_BUCKET}`);
42
+ } catch (e) {
43
+ console.log('Bucket creation error:', e);
44
+ }
45
+ });
46
+
47
+ afterAll(async () => {
48
+ if (!RUN_NETWORK_TESTS || !client) return;
49
+
50
+ // Cleanup: delete all objects and bucket
51
+ try {
52
+ const objects = await client.listObjects('/', '', 1000);
53
+ if (objects && objects.length > 0) {
54
+ const keys = objects.map((o) => o.Key).filter(Boolean) as string[];
55
+ if (keys.length > 0) {
56
+ await client.deleteObjects(keys);
57
+ }
58
+ }
59
+ // Note: s3mini doesn't have deleteBucket - we'll leave the bucket
60
+ // MinIO Play auto-cleans old buckets
61
+ } catch (e) {
62
+ console.log('Cleanup error:', e);
63
+ }
64
+ });
65
+
66
+ describe.skipIf(!RUN_NETWORK_TESTS)('listObjects with delimiter', () => {
67
+ test('should list objects and detect directories', async () => {
68
+ // Create test structure
69
+ await client.putObject('file1.txt', 'content1');
70
+ await client.putObject('file2.txt', 'content2');
71
+ await client.putObject('dir1/', ''); // Directory marker
72
+ await client.putObject('dir1/nested.txt', 'nested content');
73
+ await client.putObject('dir2/', ''); // Another directory marker
74
+
75
+ // List with delimiter
76
+ const objects = await client.listObjects('/', '', 100, {
77
+ delimiter: '/',
78
+ });
79
+
80
+ console.log('MinIO Play listing with delimiter:', JSON.stringify(objects, null, 2));
81
+
82
+ expect(objects).toBeDefined();
83
+ expect(Array.isArray(objects)).toBe(true);
84
+
85
+ if (objects && objects.length > 0) {
86
+ // Check for files and directories
87
+ const dirs = objects.filter((o) => o.Key?.endsWith('/'));
88
+ const files = objects.filter((o) => !o.Key?.endsWith('/'));
89
+
90
+ console.log(`Found ${dirs.length} directories and ${files.length} files`);
91
+ console.log(
92
+ 'Directories:',
93
+ dirs.map((d) => d.Key),
94
+ );
95
+ console.log(
96
+ 'Files:',
97
+ files.map((f) => f.Key),
98
+ );
99
+
100
+ // Directories should have Size: 0
101
+ for (const dir of dirs) {
102
+ expect(Number(dir.Size)).toBe(0);
103
+ expect(dir.Key).toMatch(/\/$/);
104
+ }
105
+
106
+ // Files should have Size > 0
107
+ for (const file of files) {
108
+ expect(Number(file.Size)).toBeGreaterThan(0);
109
+ expect(file.Key).not.toMatch(/\/$/);
110
+ }
111
+ }
112
+ });
113
+
114
+ test('should list nested directory contents', async () => {
115
+ // List dir1/ contents
116
+ const objects = await client.listObjects('/', 'dir1/', 100, {
117
+ delimiter: '/',
118
+ });
119
+
120
+ console.log('MinIO Play dir1/ listing:', JSON.stringify(objects, null, 2));
121
+
122
+ expect(objects).toBeDefined();
123
+ expect(Array.isArray(objects)).toBe(true);
124
+
125
+ // Should find nested.txt
126
+ const files = objects?.filter((o) => !o.Key?.endsWith('/')) || [];
127
+ expect(files.some((f) => f.Key?.includes('nested.txt'))).toBe(true);
128
+ });
129
+ });
130
+ });
131
+
132
+ /**
133
+ * Unit tests for S3 directory detection logic (no network required)
134
+ *
135
+ * When using delimiter: '/' with listObjects, S3 returns:
136
+ * - Files: Key without trailing /, Size > 0
137
+ * - Directories: Key with trailing /, Size: 0
138
+ */
139
+ describe('s3mini listObjects behavior', () => {
140
+ test('directory detection logic - directories end with / and have Size: 0', () => {
141
+ // Mock response similar to user's example
142
+ const mockResponse = [
143
+ {
144
+ Key: 'fusion/README.md',
145
+ LastModified: '2025-12-18T01:51:09.000Z',
146
+ ETag: '"adc69293e8fd256b2609664f1e11cb53"',
147
+ Size: 6,
148
+ StorageClass: 'STANDARD',
149
+ },
150
+ {
151
+ Key: 'fusion/home/',
152
+ Size: 0,
153
+ LastModified: new Date('1970-01-01T00:00:00.000Z'),
154
+ ETag: '',
155
+ StorageClass: '',
156
+ },
157
+ {
158
+ Key: 'fusion/mcp/',
159
+ Size: 0,
160
+ LastModified: new Date('1970-01-01T00:00:00.000Z'),
161
+ ETag: '',
162
+ StorageClass: '',
163
+ },
164
+ ];
165
+
166
+ // Test directory detection logic
167
+ for (const obj of mockResponse) {
168
+ const isDir = obj.Key.endsWith('/');
169
+
170
+ if (isDir) {
171
+ expect(obj.Size).toBe(0);
172
+ expect(obj.Key).toMatch(/\/$/);
173
+ } else {
174
+ expect(obj.Size).toBeGreaterThan(0);
175
+ expect(obj.Key).not.toMatch(/\/$/);
176
+ }
177
+ }
178
+
179
+ // Count directories and files
180
+ const dirs = mockResponse.filter((o) => o.Key.endsWith('/'));
181
+ const files = mockResponse.filter((o) => !o.Key.endsWith('/'));
182
+
183
+ expect(dirs).toHaveLength(2);
184
+ expect(files).toHaveLength(1);
185
+ });
186
+
187
+ test('readdir should properly identify directories from S3 response', () => {
188
+ const s3Response = [
189
+ { Key: 'fusion/README.md', Size: 6, LastModified: new Date(), ETag: '"abc"' },
190
+ { Key: 'fusion/home/', Size: 0, LastModified: new Date(0), ETag: '' },
191
+ { Key: 'fusion/mcp/', Size: 0, LastModified: new Date(0), ETag: '' },
192
+ ];
193
+
194
+ const results = s3Response.map((obj) => {
195
+ const key = obj.Key;
196
+ const isDir = key.endsWith('/');
197
+ const name = isDir ? key.slice(0, -1).split('/').pop() : key.split('/').pop();
198
+
199
+ return {
200
+ name,
201
+ kind: isDir ? 'directory' : 'file',
202
+ size: obj.Size,
203
+ };
204
+ });
205
+
206
+ expect(results).toEqual([
207
+ { name: 'README.md', kind: 'file', size: 6 },
208
+ { name: 'home', kind: 'directory', size: 0 },
209
+ { name: 'mcp', kind: 'directory', size: 0 },
210
+ ]);
211
+ });
212
+
213
+ test('should handle nested paths correctly', () => {
214
+ const s3Response = [
215
+ { Key: 'data/2024/01/file.csv', Size: 1024 },
216
+ { Key: 'data/2024/02/', Size: 0 },
217
+ { Key: 'data/config.json', Size: 256 },
218
+ ];
219
+
220
+ const prefix = 'data/';
221
+ const results = s3Response.map((obj) => {
222
+ const key = obj.Key;
223
+ const relativeKey = key.startsWith(prefix) ? key.slice(prefix.length) : key;
224
+ const isDir = key.endsWith('/');
225
+
226
+ const parts = relativeKey.split('/').filter(Boolean);
227
+ const name = parts[0];
228
+
229
+ return {
230
+ key,
231
+ relativeKey,
232
+ name,
233
+ isDir,
234
+ };
235
+ });
236
+
237
+ // For non-recursive listing, only want immediate children
238
+ const immediateChildren = results.filter((r) => {
239
+ const keyWithoutTrailingSlash = r.relativeKey.replace(/\/$/, '');
240
+ return !keyWithoutTrailingSlash.includes('/');
241
+ });
242
+
243
+ expect(immediateChildren).toHaveLength(1);
244
+ expect(immediateChildren[0].name).toBe('config.json');
245
+ });
246
+
247
+ test('should correctly strip prefix and build file stat', () => {
248
+ const prefix = 'myapp/data';
249
+ const normalizedPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
250
+
251
+ const stripPrefix = (key: string): string => {
252
+ if (!normalizedPrefix || !key.startsWith(normalizedPrefix + '/')) {
253
+ return key.startsWith('/') ? key : '/' + key;
254
+ }
255
+ const withoutPrefix = key.slice(normalizedPrefix.length);
256
+ return withoutPrefix || '/';
257
+ };
258
+
259
+ expect(stripPrefix('myapp/data/file.txt')).toBe('/file.txt');
260
+ expect(stripPrefix('myapp/data/subdir/')).toBe('/subdir/');
261
+ expect(stripPrefix('myapp/data/')).toBe('/');
262
+ expect(stripPrefix('other/path')).toBe('/other/path');
263
+ });
264
+ });
@@ -0,0 +1,75 @@
1
+ import pathe from 'pathe';
2
+ import type { IFileStat } from './IFileSystem';
3
+
4
+ export type ScandirOptions = {
5
+ readdir: (path: string) => Promise<IFileStat[]>;
6
+ path: string;
7
+ signal?: AbortSignal;
8
+ cursor?: string;
9
+ depth?: number;
10
+ limit?: number;
11
+ strategy?: 'depth' | 'breadth';
12
+ filter?: (file: IFileStat) => boolean;
13
+ };
14
+
15
+ export async function* scandir(options: ScandirOptions): AsyncGenerator<IFileStat> {
16
+ const { readdir, path: startPath, signal, depth = 1, limit, strategy = 'depth', filter = () => true } = options;
17
+ let cursor = options.cursor;
18
+ if (cursor && !cursor.startsWith('/')) {
19
+ cursor = pathe.join(startPath, cursor);
20
+ }
21
+ if (cursor && !cursor.startsWith(startPath)) {
22
+ throw new Error(`Cursor path "${cursor}" is not within the start path "${startPath}"`);
23
+ }
24
+
25
+ if (signal?.aborted) return;
26
+
27
+ // Collection to manage which directories to visit next.
28
+ // For 'depth'-first, it acts as a Stack (LIFO).
29
+ // For 'breadth'-first, it acts as a Queue (FIFO).
30
+ const collection: { path: string; level: number }[] = [{ path: startPath, level: 1 }];
31
+
32
+ let yielded = 0;
33
+ let cursorFound = !cursor; // If no cursor is provided, we can start yielding immediately.
34
+
35
+ while (collection.length > 0) {
36
+ if (signal?.aborted) return;
37
+ if (limit && yielded >= limit) return;
38
+
39
+ // Get the next directory to process based on the strategy.
40
+ const { path: currentPath, level: currentLevel } =
41
+ strategy === 'depth'
42
+ ? collection.pop()! // LIFO for depth-first
43
+ : collection.shift()!; // FIFO for breadth-first
44
+
45
+ let entries: IFileStat[];
46
+ try {
47
+ entries = await readdir(currentPath);
48
+ } catch (error) {
49
+ // Could not read directory, skip it. You might want to log this error.
50
+ console.warn(`scandir: Could not read directory ${currentPath}`, error);
51
+ continue;
52
+ }
53
+
54
+ for (const file of entries) {
55
+ if (signal?.aborted) return;
56
+
57
+ if (!cursorFound) {
58
+ if (file.path === cursor) {
59
+ cursorFound = true;
60
+ }
61
+ continue;
62
+ }
63
+
64
+ if (filter(file)) {
65
+ yield file;
66
+ yielded++;
67
+ if (limit && yielded >= limit) return;
68
+ }
69
+
70
+ if (file.kind === 'directory' && currentLevel < depth) {
71
+ collection.push({ path: file.path, level: currentLevel + 1 });
72
+ }
73
+ }
74
+ }
75
+ }