@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
@@ -1,21 +1,8 @@
1
1
  import * as crypto from 'node:crypto';
2
2
  import { basename, dirname, join, normalize } from 'node:path';
3
- import {
4
- Cascade,
5
- Collection,
6
- Entity,
7
- ManyToOne,
8
- OneToMany,
9
- OneToOne,
10
- Property,
11
- types,
12
- Unique,
13
- type Opt,
14
- type Rel,
15
- } from '@mikro-orm/core';
16
- import type { EntityManager } from '@mikro-orm/knex';
17
- import { TenantBaseEntity } from '@wener/server/entity';
18
- import { getEntityManager } from '@wener/server/mikro-orm';
3
+ import { BaseEntity, Cascade, Collection, type Opt, type Rel } from '@mikro-orm/core';
4
+ import { Entity, ManyToOne, OneToMany, OneToOne, PrimaryKey, Property, Unique } from '@mikro-orm/decorators/legacy';
5
+ import type { EntityManager } from '@mikro-orm/sql';
19
6
  import type { CopyOptions } from 'fs-extra';
20
7
  import { FileSystemError, FileSystemErrorCode } from '../FileSystemError';
21
8
  import type {
@@ -49,11 +36,13 @@ class DBFS implements IFileSystem {
49
36
  options: IDatabaseFileSystemOptions;
50
37
 
51
38
  constructor(options: Partial<IDatabaseFileSystemOptions> = {}) {
39
+ if (!options.getEntityManager) {
40
+ throw new Error('getEntityManager is required for DBFS');
41
+ }
52
42
  this.options = {
53
- getEntityManager: () => getEntityManager<EntityManager>().fork(),
54
43
  smallFileThreshold: 512,
55
44
  ...options,
56
- };
45
+ } as IDatabaseFileSystemOptions;
57
46
  }
58
47
 
59
48
  get em() {
@@ -87,7 +76,7 @@ class DBFS implements IFileSystem {
87
76
  mtime: now,
88
77
  });
89
78
  try {
90
- await em.persistAndFlush(rootDir);
79
+ await em.persist(rootDir).flush();
91
80
  return rootDir;
92
81
  } catch (error: any) {
93
82
  // If root already exists (race condition), fetch and return it
@@ -169,7 +158,7 @@ class DBFS implements IFileSystem {
169
158
  mtime: now,
170
159
  });
171
160
  try {
172
- await em.persistAndFlush(rootDir);
161
+ await em.persist(rootDir).flush();
173
162
  } catch (error: any) {
174
163
  // If root already exists (race condition), ignore the error
175
164
  if (!error.message?.includes('UNIQUE constraint') && !error.message?.includes('duplicate')) {
@@ -188,7 +177,6 @@ class DBFS implements IFileSystem {
188
177
 
189
178
  if (!parentNode) {
190
179
  if (options.recursive) {
191
- // 递归创建父目录
192
180
  await this._mkdirInTransaction(parentPath, options, em);
193
181
  parentNode = await this._getNodeByPath(parentPath, em);
194
182
  } else {
@@ -206,13 +194,11 @@ class DBFS implements IFileSystem {
206
194
 
207
195
  if (existing) {
208
196
  if (existing.kind === FileKind.directory) {
209
- // Directory already exists, return silently
210
197
  return;
211
198
  }
212
199
  throw new FileSystemError(`A file with the same name already exists: ${path}`, FileSystemErrorCode.EEXIST);
213
200
  }
214
201
 
215
- // Create directory using EntityManager
216
202
  const now = new Date();
217
203
  const newDir = em.create(FileNodeMetaEntity, {
218
204
  tid: parentNode.tid,
@@ -225,7 +211,7 @@ class DBFS implements IFileSystem {
225
211
  ctime: now,
226
212
  mtime: now,
227
213
  });
228
- await em.persistAndFlush(newDir);
214
+ await em.persist(newDir).flush();
229
215
  }
230
216
 
231
217
  readFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;
@@ -245,24 +231,22 @@ class DBFS implements IFileSystem {
245
231
  if (node.kind !== FileKind.file)
246
232
  throw new FileSystemError(`Path is not a file: ${path}`, FileSystemErrorCode.EISDIR);
247
233
 
248
- let buffer: Buffer;
234
+ let data: Buffer | Uint8Array;
249
235
  if (node.content) {
250
- // 小文件优化 - content is already loaded
251
- buffer = node.content;
236
+ data = node.content;
252
237
  } else {
253
- // Large file: load from file_node_content table using QueryBuilder
254
238
  const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
255
239
  fileContentQb.where({ node: node });
256
240
  const fileContent = await fileContentQb.getSingleResult();
257
241
  if (!fileContent) throw new FileSystemError('File content is missing', FileSystemErrorCode.ENOENT);
258
- buffer = fileContent.content;
242
+ data = fileContent.content;
259
243
  }
260
244
 
261
- return options?.encoding === 'text' ? buffer.toString('utf-8') : buffer;
245
+ const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
246
+ return options?.encoding === 'text' ? buf.toString('utf-8') : buf;
262
247
  }
263
248
 
264
249
  async writeFile(path: string, data: string | Buffer, options: WriteFileOptions = {}): Promise<void> {
265
- // Validate input
266
250
  if (!path || typeof path !== 'string') {
267
251
  throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
268
252
  }
@@ -278,33 +262,26 @@ class DBFS implements IFileSystem {
278
262
  const parentPath = dirname(path);
279
263
  const filename = basename(path);
280
264
 
281
- // Validate filename
282
265
  if (!filename) {
283
266
  throw new FileSystemError('filename cannot be empty', FileSystemErrorCode.EINVAL);
284
267
  }
285
268
 
286
- // 确保父目录存在 - create it within the transaction
287
269
  await this._mkdirInTransaction(parentPath, { recursive: true }, em);
288
270
  const parentNode = await this._getNodeByPath(parentPath, em);
289
271
  if (!parentNode) throw new FileSystemError('Failed to establish parent directory', FileSystemErrorCode.EINVAL);
290
272
 
291
- // Find existing node using QueryBuilder
292
273
  const nodeQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
293
274
  nodeQb.where({ parent: parentNode, filename });
294
275
  let node = await nodeQb.getSingleResult();
295
276
 
296
277
  if (node) {
297
- // 文件已存在
298
278
  if (!overwrite) throw new FileSystemError(`File already exists: ${path}`, FileSystemErrorCode.EEXIST);
299
279
  if (node.kind === FileKind.directory)
300
280
  throw new FileSystemError(`Cannot overwrite a directory with a file: ${path}`, FileSystemErrorCode.EISDIR);
301
281
 
302
- // 更新节点
303
282
  node.size = size;
304
283
  node.mtime = new Date();
305
- // ... 其他时间戳
306
284
  } else {
307
- // 新建文件
308
285
  const now = new Date();
309
286
  node = em.create(FileNodeMetaEntity, {
310
287
  tid: parentNode.tid,
@@ -319,49 +296,41 @@ class DBFS implements IFileSystem {
319
296
  });
320
297
  }
321
298
 
322
- // 处理文件内容
323
299
  if (size <= this.options.smallFileThreshold!) {
324
- // Small file: store in file_node_meta.content
325
300
  node.content = bufferData;
326
- // If there was large file content, delete it
327
- // Use QueryBuilder to avoid relationship issues
328
301
  const existingContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
329
302
  existingContentQb.where({ node: node });
330
303
  const existingContent = await existingContentQb.getSingleResult();
331
304
  if (existingContent) {
332
- await em.removeAndFlush(existingContent);
305
+ await em.remove(existingContent).flush();
333
306
  }
334
307
  } else {
335
- // Large file: store in file_node_content table
336
- node.content = undefined; // Clear small file content
308
+ node.content = undefined;
337
309
  const md5 = crypto.createHash('md5').update(bufferData).digest('hex');
338
310
  const sha256 = crypto.createHash('sha256').update(bufferData).digest('hex');
339
311
 
340
- // Check if fileContent already exists using QueryBuilder
341
312
  const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
342
313
  fileContentQb.where({ node: node });
343
314
  let fileContent = await fileContentQb.getSingleResult();
344
315
 
345
316
  if (fileContent) {
346
- // Update existing content
347
317
  fileContent.content = bufferData;
348
318
  fileContent.size = size;
349
319
  fileContent.md5 = md5;
350
320
  fileContent.sha256 = sha256;
351
321
  } else {
352
- // Create new content entity
353
322
  fileContent = em.create(FileNodeContentEntity, {
354
- node: node, // Use node relationship as primary key
323
+ node: node,
355
324
  tid: node.tid,
356
325
  content: bufferData,
357
326
  size: size,
358
327
  md5: md5,
359
328
  sha256: sha256,
360
- });
329
+ } as any);
361
330
  }
362
331
  }
363
332
 
364
- await em.persistAndFlush(node);
333
+ await em.persist(node).flush();
365
334
  });
366
335
  }
367
336
 
@@ -369,12 +338,11 @@ class DBFS implements IFileSystem {
369
338
  await this.em.transactional(async (em) => {
370
339
  const node = await this._getNodeByPath(path, em);
371
340
  if (!node) {
372
- if (options.force) return; // force=true, 不存在也算成功
341
+ if (options.force) return;
373
342
  throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
374
343
  }
375
344
 
376
345
  if (node.kind === FileKind.directory && !options.recursive) {
377
- // Check if directory has children using QueryBuilder
378
346
  const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
379
347
  childrenQb.where({ parent: node });
380
348
  childrenQb.select('id');
@@ -384,8 +352,7 @@ class DBFS implements IFileSystem {
384
352
  }
385
353
  }
386
354
 
387
- // 使用 orphanRemoval: true, ORM会自动处理子节点和内容的删除
388
- await em.removeAndFlush(node);
355
+ await em.remove(node).flush();
389
356
  });
390
357
  }
391
358
 
@@ -407,8 +374,8 @@ class DBFS implements IFileSystem {
407
374
  if (existingDest) {
408
375
  if (!options.overwrite)
409
376
  throw new FileSystemError(`Destination path already exists: ${newPath}`, FileSystemErrorCode.EEXIST);
410
- if (node.id === existingDest.id) return; // 移动到原位置,什么都不做
411
- await em.removeAndFlush(existingDest);
377
+ if (node.id === existingDest.id) return;
378
+ await em.remove(existingDest).flush();
412
379
  }
413
380
 
414
381
  node.parent = newParentNode;
@@ -437,8 +404,7 @@ class DBFS implements IFileSystem {
437
404
  if (existingDest) {
438
405
  if (!options.overwrite)
439
406
  throw new FileSystemError(`Destination path already exists: ${destPath}`, FileSystemErrorCode.EEXIST);
440
- // Delete existing destination within the same transaction
441
- await em.removeAndFlush(existingDest);
407
+ await em.remove(existingDest).flush();
442
408
  }
443
409
 
444
410
  await this._copyNode(srcNode, destParentNode, destFilename, em);
@@ -461,17 +427,10 @@ class DBFS implements IFileSystem {
461
427
  throw new Error('WritableStream is not supported by DBFS yet.');
462
428
  }
463
429
 
464
- /**
465
- * 将路径字符串解析为数据库中的节点。这是大部分操作的基础。
466
- * @param pathStr 绝对路径, e.g., /home/user/file.txt
467
- * @param em EntityManager 实例
468
- * @returns 找到的节点或 null
469
- */
470
430
  private async _getNodeByPath(pathStr: string, em: EntityManager): Promise<FileNodeMetaEntity | null> {
471
431
  const normalized = normalize(pathStr);
472
432
 
473
433
  if (normalized === '/') {
474
- // Use QueryBuilder to avoid automatic relationship loading
475
434
  const qb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
476
435
  qb.where({ parent: null });
477
436
  const rootNode = await qb.getSingleResult();
@@ -480,13 +439,11 @@ class DBFS implements IFileSystem {
480
439
 
481
440
  const parts = normalized.split('/').filter((p) => p);
482
441
 
483
- // Get root node
484
442
  const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
485
443
  rootQb.where({ parent: null });
486
444
  let currentNode = await rootQb.getSingleResult();
487
445
  if (!currentNode) return null;
488
446
 
489
- // Traverse path parts
490
447
  for (const part of parts) {
491
448
  const childQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
492
449
  childQb.where({ parent: currentNode, filename: part });
@@ -498,11 +455,7 @@ class DBFS implements IFileSystem {
498
455
  return currentNode;
499
456
  }
500
457
 
501
- /**
502
- * 将数据库实体转换为 IFileStat 接口
503
- */
504
458
  private _toFileStat(node: FileNodeMetaEntity, path: string): IFileStat {
505
- // Handle mtime - it might be a Date object or a string from raw query
506
459
  let mtime: number;
507
460
  if (node.mtime instanceof Date) {
508
461
  mtime = node.mtime.getTime();
@@ -512,7 +465,6 @@ class DBFS implements IFileSystem {
512
465
  mtime = Date.now();
513
466
  }
514
467
 
515
- // Handle size - convert bigint to number if needed
516
468
  let size: number;
517
469
  if (typeof node.size === 'bigint') {
518
470
  size = Number(node.size);
@@ -527,7 +479,6 @@ class DBFS implements IFileSystem {
527
479
  size: size,
528
480
  mtime: mtime,
529
481
  meta: node.metadata || {},
530
- // `directory` 字段可以根据 path 动态计算
531
482
  directory: dirname(path),
532
483
  };
533
484
  }
@@ -538,44 +489,42 @@ class DBFS implements IFileSystem {
538
489
  newName: string,
539
490
  em: EntityManager,
540
491
  ): Promise<void> {
541
- // 1. 复制节点本身
542
492
  const now = new Date();
493
+ const copiedContent = srcNode.content ? Buffer.from(srcNode.content) : undefined;
543
494
  const newNode = em.create(FileNodeMetaEntity, {
544
495
  tid: srcNode.tid,
545
496
  filename: newName,
546
497
  parent: destParent,
547
498
  kind: srcNode.kind,
548
499
  size: srcNode.size,
549
- metadata: srcNode.metadata, // deep copy metadata
550
- content: srcNode.content, // copy small file content
500
+ metadata: srcNode.metadata ? { ...srcNode.metadata } : undefined,
501
+ content: copiedContent,
551
502
  atime: now,
552
503
  btime: now,
553
504
  ctime: now,
554
505
  mtime: now,
555
506
  });
556
507
 
557
- // 2. 复制大文件内容 (如果存在) - use QueryBuilder to avoid relationship loading
558
508
  if (!srcNode.content) {
559
509
  const srcFileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
560
510
  srcFileContentQb.where({ node: srcNode });
561
511
  const srcFileContent = await srcFileContentQb.getSingleResult();
562
512
  if (srcFileContent) {
563
- const newContent = em.create(FileNodeContentEntity, {
564
- node: newNode, // Use node relationship as primary key
513
+ em.create(FileNodeContentEntity, {
514
+ node: newNode,
565
515
  tid: srcNode.tid,
566
- content: srcFileContent.content,
516
+ content: srcFileContent.content ? Buffer.from(srcFileContent.content) : srcFileContent.content,
567
517
  size: srcFileContent.size,
568
518
  md5: srcFileContent.md5,
569
519
  sha256: srcFileContent.sha256,
570
520
  mimeType: srcFileContent.mimeType,
571
- metadata: srcFileContent.metadata,
572
- });
521
+ metadata: srcFileContent.metadata ? { ...srcFileContent.metadata } : undefined,
522
+ } as any);
573
523
  }
574
524
  }
575
525
 
576
- await em.persistAndFlush(newNode);
526
+ await em.persist(newNode).flush();
577
527
 
578
- // 3. 如果是目录,递归复制子节点
579
528
  if (srcNode.kind === FileKind.directory) {
580
529
  const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
581
530
  childrenQb.where({ parent: srcNode });
@@ -589,25 +538,36 @@ class DBFS implements IFileSystem {
589
538
 
590
539
  @Entity({ tableName: 'file_node_meta' })
591
540
  @Unique({ properties: ['tid', 'parent', 'filename'] })
592
- export class FileNodeMetaEntity extends TenantBaseEntity {
593
- @Property({ type: types.string, nullable: false, comment: '文件名' })
541
+ export class FileNodeMetaEntity extends BaseEntity {
542
+ @PrimaryKey({ type: 'text', onCreate: () => crypto.randomUUID() })
543
+ id!: string & Opt;
544
+
545
+ @Property({ type: 'text', nullable: true })
546
+ tid?: string;
547
+
548
+ @Property({ type: 'text', nullable: false })
594
549
  filename!: string;
595
- @Property({ type: types.bigint, nullable: false, default: 0, comment: '文件大小' })
550
+
551
+ @Property({ type: 'integer', nullable: false, default: 0 })
596
552
  size!: number & Opt;
597
- @Property({ type: types.string, nullable: false, comment: '文件类型' })
553
+
554
+ @Property({ type: 'text', nullable: false })
598
555
  kind!: FileKind;
599
556
 
600
- @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
557
+ @Property({ type: 'text', nullable: false })
601
558
  atime!: Date & Opt;
602
- @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
559
+
560
+ @Property({ type: 'text', nullable: false })
603
561
  btime!: Date & Opt;
604
- @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
562
+
563
+ @Property({ type: 'text', nullable: false })
605
564
  ctime!: Date & Opt;
606
- @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
565
+
566
+ @Property({ type: 'text', nullable: false })
607
567
  mtime!: Date & Opt;
608
568
 
609
- @Property({ type: types.json, nullable: false, defaultRaw: '{}' })
610
- metadata!: Record<string, any>;
569
+ @Property({ type: 'json', nullable: false, default: '{}' })
570
+ metadata!: Record<string, any> & Opt;
611
571
 
612
572
  @ManyToOne(() => FileNodeMetaEntity, { nullable: true, cascade: [] })
613
573
  parent?: Rel<FileNodeMetaEntity>;
@@ -623,46 +583,50 @@ export class FileNodeMetaEntity extends TenantBaseEntity {
623
583
  cascade: [Cascade.ALL],
624
584
  })
625
585
  fileContent?: Rel<FileNodeContentEntity>;
626
- @Property({ type: types.blob, nullable: true, comment: '文件内容' })
627
- content?: Buffer; // for small file, e.g. < 64k
628
586
 
629
- //region content
587
+ @Property({ type: 'blob', nullable: true })
588
+ content?: Buffer;
630
589
 
631
590
  get parentId() {
632
591
  return this.parent?.id as Opt<string | undefined>;
633
592
  }
634
-
635
- //endregion
636
593
  }
637
594
 
638
595
  @Entity({ tableName: 'file_node_content' })
639
- export class FileNodeContentEntity extends TenantBaseEntity {
596
+ export class FileNodeContentEntity extends BaseEntity {
597
+ @PrimaryKey({ type: 'text', onCreate: () => crypto.randomUUID() })
598
+ id!: string & Opt;
599
+
600
+ @Property({ type: 'text', nullable: true })
601
+ tid?: string;
602
+
640
603
  @OneToOne({ entity: () => FileNodeMetaEntity, owner: true, joinColumn: 'node_id' })
641
604
  node!: Rel<FileNodeMetaEntity>;
642
605
 
643
- @Property({ type: types.integer, nullable: false })
644
- size!: number; // 保留 size 方便查询分析
606
+ @Property({ type: 'integer', nullable: false })
607
+ size!: number;
645
608
 
646
- @Property({ type: types.blob, lazy: true, comment: '文件内容' })
609
+ @Property({ type: 'blob', lazy: true })
647
610
  content!: Buffer;
648
611
 
649
- @Property({ type: types.string, nullable: true })
612
+ @Property({ type: 'text', nullable: true })
650
613
  mimeType?: string;
651
614
 
652
- @Property({ type: types.string, nullable: false })
615
+ @Property({ type: 'text', nullable: true })
653
616
  md5?: string;
654
- @Property({ type: types.string, nullable: false })
617
+
618
+ @Property({ type: 'text', nullable: true })
655
619
  sha256?: string;
656
620
 
657
- @Property({ type: types.string, nullable: true })
621
+ @Property({ type: 'text', nullable: true })
658
622
  text?: string;
659
- @Property({ type: types.integer, nullable: true })
623
+
624
+ @Property({ type: 'integer', nullable: true })
660
625
  width?: number;
661
- @Property({ type: types.integer, nullable: true })
626
+
627
+ @Property({ type: 'integer', nullable: true })
662
628
  height?: number;
663
- // @Property({ type: types.integer, nullable: true })
664
- // length?: number;
665
629
 
666
- @Property({ type: types.json, nullable: false, defaultRaw: '{}' })
667
- metadata!: Record<string, any>;
630
+ @Property({ type: 'json', nullable: false, default: '{}' })
631
+ metadata!: Record<string, any> & Opt;
668
632
  }
@@ -12,17 +12,18 @@ import type {
12
12
  CreateReadStreamOptions,
13
13
  CreateWriteStreamOptions,
14
14
  IFileStat,
15
- IFileSystem,
15
+ IServerFileSystem,
16
16
  MkdirOptions,
17
17
  ReaddirOptions,
18
18
  ReadFileOptions,
19
19
  RenameOptions,
20
20
  RmOptions,
21
21
  StatOptions,
22
+ WritableData,
22
23
  WriteFileOptions,
23
24
  } from '../IFileSystem';
24
25
 
25
- export type INodeFileSystem = IFileSystem & {
26
+ export type INodeFileSystem = IServerFileSystem & {
26
27
  readonly root: string;
27
28
  resolvePath(filePath: string): string;
28
29
  };
@@ -38,7 +39,7 @@ export function createNodeFileSystem(options: { root?: string } = {}): INodeFile
38
39
 
39
40
  type IFS = typeof import('fs/promises');
40
41
 
41
- class NodeFs implements IFileSystem, INodeFileSystem {
42
+ class NodeFs implements IServerFileSystem, INodeFileSystem {
42
43
  readonly root: string;
43
44
  private readonly fs: IFS;
44
45
 
@@ -113,7 +114,7 @@ class NodeFs implements IFileSystem, INodeFileSystem {
113
114
  }
114
115
 
115
116
  if (!relativePath.startsWith('/') && !relativePath.startsWith('\\')) {
116
- relativePath = '/' + relativePath;
117
+ relativePath = `/${relativePath}`;
117
118
  }
118
119
 
119
120
  // Normalize to ensure consistent path separators
@@ -183,7 +184,7 @@ class NodeFs implements IFileSystem, INodeFileSystem {
183
184
  const maxDepth = recursive ? Infinity : depth - 1;
184
185
  if (maxDepth > 0) {
185
186
  // Need to convert the path back to a full path for the recursive call
186
- const subdirFullPath = this.resolvePath(subdir.path);
187
+ const _subdirFullPath = this.resolvePath(subdir.path);
187
188
 
188
189
  const subEntries = await this.readdir(subdir.path, {
189
190
  ...options,
@@ -295,11 +296,7 @@ class NodeFs implements IFileSystem, INodeFileSystem {
295
296
  }
296
297
  }
297
298
 
298
- async writeFile(
299
- path: string,
300
- data: string | Buffer | ArrayBuffer | Readable,
301
- options: WriteFileOptions = {},
302
- ): Promise<void> {
299
+ async writeFile(path: string, data: WritableData, options: WriteFileOptions = {}): Promise<void> {
303
300
  const { fs } = this;
304
301
 
305
302
  const { signal, overwrite = true, onUploadProgress } = options;
@@ -354,12 +351,34 @@ class NodeFs implements IFileSystem, INodeFileSystem {
354
351
  });
355
352
  }
356
353
  });
354
+ } else if (data instanceof ReadableStream) {
355
+ // Handle web ReadableStream
356
+ const reader = data.getReader();
357
+ const chunks: Uint8Array[] = [];
358
+ let loaded = 0;
359
+ while (true) {
360
+ const { done, value } = await reader.read();
361
+ if (done) break;
362
+ if (value) {
363
+ chunks.push(value);
364
+ loaded += value.length;
365
+ if (onUploadProgress) {
366
+ onUploadProgress({ loaded, total: -1 });
367
+ }
368
+ }
369
+ }
370
+ await fs.writeFile(resolvedPath, Buffer.concat(chunks));
357
371
  } else {
358
- // Convert ArrayBuffer to Buffer if necessary
372
+ // Convert ArrayBuffer/ArrayBufferView to Buffer if necessary
373
+ let writeData: string | Buffer;
359
374
  if (data instanceof ArrayBuffer) {
360
- data = Buffer.from(data);
375
+ writeData = Buffer.from(data);
376
+ } else if (ArrayBuffer.isView(data)) {
377
+ writeData = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
378
+ } else {
379
+ writeData = data;
361
380
  }
362
- await fs.writeFile(resolvedPath, data);
381
+ await fs.writeFile(resolvedPath, writeData);
363
382
  }
364
383
  }
365
384
 
@@ -1,5 +1,5 @@
1
- import type { EntityManager } from '@mikro-orm/knex';
2
- import { beforeEach, describe, test } from 'vitest';
1
+ import type { EntityManager } from '@mikro-orm/sql';
2
+ import { beforeEach, describe, expect, test } from 'vitest';
3
3
  import { runFileSystemTest } from '../tests/runFileSystemTest';
4
4
  import { createDatabaseFileSystem, FileNodeMetaEntity } from './createDatabaseFileSystem';
5
5
  import { loadTestDatabase } from './loadTestDatabase';
@@ -10,10 +10,10 @@ describe('DatabaseFileSystem', () => {
10
10
 
11
11
  beforeEach(async () => {
12
12
  const { em: entityManager } = await loadTestDatabase();
13
- em = entityManager as any; // Type cast to match expected EntityManager type
13
+ em = entityManager as any;
14
14
 
15
15
  fs = createDatabaseFileSystem({
16
- getEntityManager: () => em as any,
16
+ getEntityManager: () => (em as any).fork(),
17
17
  });
18
18
 
19
19
  // Setup initial state: ensure root directory exists and create /README.txt
@@ -31,9 +31,9 @@ describe('DatabaseFileSystem', () => {
31
31
  btime: new Date(),
32
32
  ctime: new Date(),
33
33
  mtime: new Date(),
34
- });
35
- await em.persistAndFlush(readmeFile);
36
- }, 30000); // Increase timeout to 30 seconds
34
+ } as any);
35
+ await em.persist(readmeFile).flush();
36
+ }, 30000);
37
37
 
38
38
  test('common tests', async () => {
39
39
  await runFileSystemTest(fs, {
@@ -43,5 +43,10 @@ describe('DatabaseFileSystem', () => {
43
43
  writeStream: false,
44
44
  abort: false,
45
45
  });
46
- }, 60000); // Increase timeout to 60 seconds
46
+ }, 60000);
47
+
48
+ test('root node exists', async () => {
49
+ const stat = await fs.stat('/');
50
+ expect(stat.kind).toBe('directory');
51
+ });
47
52
  });
@@ -1 +1,2 @@
1
+ export { createDatabaseFileSystem, FileNodeContentEntity, FileNodeMetaEntity } from './createDatabaseFileSystem';
1
2
  export { createNodeFileSystem } from './createNodeFileSystem';