@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,668 @@
1
+ import * as crypto from 'node:crypto';
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';
19
+ import type { CopyOptions } from 'fs-extra';
20
+ import { FileSystemError, FileSystemErrorCode } from '../FileSystemError';
21
+ import type {
22
+ CreateReadStreamOptions,
23
+ CreateWriteStreamOptions,
24
+ IFileStat,
25
+ IFileSystem,
26
+ MkdirOptions,
27
+ ReaddirOptions,
28
+ ReadFileOptions,
29
+ RenameOptions,
30
+ RmOptions,
31
+ StatOptions,
32
+ WriteFileOptions,
33
+ } from '../IFileSystem';
34
+ import { FileKind } from '../types';
35
+
36
+ export function createDatabaseFileSystem(options: Partial<IDatabaseFileSystemOptions> = {}): IDatabaseFileSystem {
37
+ return new DBFS(options);
38
+ }
39
+
40
+ type IDatabaseFileSystem = IFileSystem & {
41
+ ensureRootNode(): Promise<FileNodeMetaEntity>;
42
+ };
43
+ type IDatabaseFileSystemOptions = {
44
+ getEntityManager: () => EntityManager;
45
+ smallFileThreshold?: number;
46
+ };
47
+
48
+ class DBFS implements IFileSystem {
49
+ options: IDatabaseFileSystemOptions;
50
+
51
+ constructor(options: Partial<IDatabaseFileSystemOptions> = {}) {
52
+ this.options = {
53
+ getEntityManager: () => getEntityManager<EntityManager>().fork(),
54
+ smallFileThreshold: 512,
55
+ ...options,
56
+ };
57
+ }
58
+
59
+ get em() {
60
+ return this.options.getEntityManager();
61
+ }
62
+
63
+ /**
64
+ * Ensure root node exists, create it if it doesn't exist
65
+ * @returns The root FileNodeMetaEntity
66
+ */
67
+ async ensureRootNode(): Promise<FileNodeMetaEntity> {
68
+ const em = this.em;
69
+ const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
70
+ rootQb.where({ parent: null });
71
+ const rootNode = await rootQb.getSingleResult();
72
+
73
+ if (rootNode) {
74
+ return rootNode;
75
+ }
76
+
77
+ // Create root directory (parent is null, filename is empty string)
78
+ const now = new Date();
79
+ const rootDir = em.create(FileNodeMetaEntity, {
80
+ filename: '',
81
+ parent: null,
82
+ kind: FileKind.directory,
83
+ size: 0,
84
+ atime: now,
85
+ btime: now,
86
+ ctime: now,
87
+ mtime: now,
88
+ });
89
+ try {
90
+ await em.persistAndFlush(rootDir);
91
+ return rootDir;
92
+ } catch (error: any) {
93
+ // If root already exists (race condition), fetch and return it
94
+ if (error.message?.includes('UNIQUE constraint') || error.message?.includes('duplicate')) {
95
+ const existingRootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
96
+ existingRootQb.where({ parent: null });
97
+ const existingRoot = await existingRootQb.getSingleResult();
98
+ if (existingRoot) {
99
+ return existingRoot;
100
+ }
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ async stat(path: string, options?: StatOptions): Promise<IFileStat> {
107
+ // Validate input
108
+ if (!path || typeof path !== 'string') {
109
+ throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
110
+ }
111
+
112
+ const em = this.em;
113
+ const node = await this._getNodeByPath(path, em);
114
+ if (!node) {
115
+ throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
116
+ }
117
+ return this._toFileStat(node, path);
118
+ }
119
+
120
+ async exists(path: string): Promise<boolean> {
121
+ return !!(await this._getNodeByPath(path, this.em));
122
+ }
123
+
124
+ async readdir(dir: string, options?: ReaddirOptions): Promise<IFileStat[]> {
125
+ const em = this.em;
126
+ const parentNode = await this._getNodeByPath(dir, em);
127
+
128
+ if (!parentNode) {
129
+ throw new FileSystemError(`Directory not found: ${dir}`, FileSystemErrorCode.ENOENT);
130
+ }
131
+ if (parentNode.kind !== FileKind.directory) {
132
+ throw new FileSystemError(`Path is not a directory: ${dir}`, FileSystemErrorCode.ENOTDIR);
133
+ }
134
+
135
+ // Use QueryBuilder to avoid automatic relationship loading
136
+ const qb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
137
+ qb.where({ parent: parentNode });
138
+ const children = await qb.getResult();
139
+
140
+ return children.map((child) => this._toFileStat(child, join(dir, child.filename)));
141
+ }
142
+
143
+ async mkdir(path: string, options: MkdirOptions = {}): Promise<void> {
144
+ await this._mkdirInTransaction(path, options, this.em);
145
+ }
146
+
147
+ private async _mkdirInTransaction(path: string, options: MkdirOptions, em: EntityManager): Promise<void> {
148
+ const normalized = normalize(path);
149
+
150
+ // Special handling for root directory
151
+ if (normalized === '/') {
152
+ const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
153
+ rootQb.where({ parent: null });
154
+ const rootNode = await rootQb.getSingleResult();
155
+ if (rootNode) {
156
+ // Root directory already exists
157
+ return;
158
+ }
159
+ // Create root directory (parent is null, filename is empty string)
160
+ const now = new Date();
161
+ const rootDir = em.create(FileNodeMetaEntity, {
162
+ filename: '',
163
+ parent: null,
164
+ kind: FileKind.directory,
165
+ size: 0,
166
+ atime: now,
167
+ btime: now,
168
+ ctime: now,
169
+ mtime: now,
170
+ });
171
+ try {
172
+ await em.persistAndFlush(rootDir);
173
+ } catch (error: any) {
174
+ // If root already exists (race condition), ignore the error
175
+ if (!error.message?.includes('UNIQUE constraint') && !error.message?.includes('duplicate')) {
176
+ throw error;
177
+ }
178
+ }
179
+ return;
180
+ }
181
+
182
+ const parentPath = dirname(normalized);
183
+ const newDirName = basename(normalized);
184
+
185
+ if (!newDirName) throw new FileSystemError('Cannot create directory with empty name', FileSystemErrorCode.EINVAL);
186
+
187
+ let parentNode = await this._getNodeByPath(parentPath, em);
188
+
189
+ if (!parentNode) {
190
+ if (options.recursive) {
191
+ // 递归创建父目录
192
+ await this._mkdirInTransaction(parentPath, options, em);
193
+ parentNode = await this._getNodeByPath(parentPath, em);
194
+ } else {
195
+ throw new FileSystemError(`Parent directory not found: ${parentPath}`, FileSystemErrorCode.ENOENT);
196
+ }
197
+ }
198
+
199
+ if (!parentNode)
200
+ throw new FileSystemError('Failed to create parent directory structure', FileSystemErrorCode.EINVAL);
201
+
202
+ // Check if directory already exists using QueryBuilder
203
+ const existingQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
204
+ existingQb.where({ parent: parentNode, filename: newDirName });
205
+ const existing = await existingQb.getSingleResult();
206
+
207
+ if (existing) {
208
+ if (existing.kind === FileKind.directory) {
209
+ // Directory already exists, return silently
210
+ return;
211
+ }
212
+ throw new FileSystemError(`A file with the same name already exists: ${path}`, FileSystemErrorCode.EEXIST);
213
+ }
214
+
215
+ // Create directory using EntityManager
216
+ const now = new Date();
217
+ const newDir = em.create(FileNodeMetaEntity, {
218
+ tid: parentNode.tid,
219
+ parent: parentNode,
220
+ filename: newDirName,
221
+ kind: FileKind.directory,
222
+ size: 0,
223
+ atime: now,
224
+ btime: now,
225
+ ctime: now,
226
+ mtime: now,
227
+ });
228
+ await em.persistAndFlush(newDir);
229
+ }
230
+
231
+ readFile(path: string, options?: ReadFileOptions & { encoding: 'text' }): Promise<string>;
232
+
233
+ readFile(path: string, options?: ReadFileOptions): Promise<Uint8Array>;
234
+
235
+ async readFile(
236
+ path: string,
237
+ options?: ReadFileOptions & {
238
+ encoding?: 'text' | 'binary' | string;
239
+ },
240
+ ): Promise<string | Uint8Array> {
241
+ const em = this.em;
242
+ const node = await this._getNodeByPath(path, em);
243
+
244
+ if (!node) throw new FileSystemError(`File not found: ${path}`, FileSystemErrorCode.ENOENT);
245
+ if (node.kind !== FileKind.file)
246
+ throw new FileSystemError(`Path is not a file: ${path}`, FileSystemErrorCode.EISDIR);
247
+
248
+ let buffer: Buffer;
249
+ if (node.content) {
250
+ // 小文件优化 - content is already loaded
251
+ buffer = node.content;
252
+ } else {
253
+ // Large file: load from file_node_content table using QueryBuilder
254
+ const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
255
+ fileContentQb.where({ node: node });
256
+ const fileContent = await fileContentQb.getSingleResult();
257
+ if (!fileContent) throw new FileSystemError('File content is missing', FileSystemErrorCode.ENOENT);
258
+ buffer = fileContent.content;
259
+ }
260
+
261
+ return options?.encoding === 'text' ? buffer.toString('utf-8') : buffer;
262
+ }
263
+
264
+ async writeFile(path: string, data: string | Buffer, options: WriteFileOptions = {}): Promise<void> {
265
+ // Validate input
266
+ if (!path || typeof path !== 'string') {
267
+ throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
268
+ }
269
+ if (data === null || data === undefined) {
270
+ throw new FileSystemError('Invalid data', FileSystemErrorCode.EINVAL);
271
+ }
272
+
273
+ await this.em.transactional(async (em) => {
274
+ const { overwrite = true } = options;
275
+ const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf-8');
276
+ const size = bufferData.length;
277
+
278
+ const parentPath = dirname(path);
279
+ const filename = basename(path);
280
+
281
+ // Validate filename
282
+ if (!filename) {
283
+ throw new FileSystemError('filename cannot be empty', FileSystemErrorCode.EINVAL);
284
+ }
285
+
286
+ // 确保父目录存在 - create it within the transaction
287
+ await this._mkdirInTransaction(parentPath, { recursive: true }, em);
288
+ const parentNode = await this._getNodeByPath(parentPath, em);
289
+ if (!parentNode) throw new FileSystemError('Failed to establish parent directory', FileSystemErrorCode.EINVAL);
290
+
291
+ // Find existing node using QueryBuilder
292
+ const nodeQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
293
+ nodeQb.where({ parent: parentNode, filename });
294
+ let node = await nodeQb.getSingleResult();
295
+
296
+ if (node) {
297
+ // 文件已存在
298
+ if (!overwrite) throw new FileSystemError(`File already exists: ${path}`, FileSystemErrorCode.EEXIST);
299
+ if (node.kind === FileKind.directory)
300
+ throw new FileSystemError(`Cannot overwrite a directory with a file: ${path}`, FileSystemErrorCode.EISDIR);
301
+
302
+ // 更新节点
303
+ node.size = size;
304
+ node.mtime = new Date();
305
+ // ... 其他时间戳
306
+ } else {
307
+ // 新建文件
308
+ const now = new Date();
309
+ node = em.create(FileNodeMetaEntity, {
310
+ tid: parentNode.tid,
311
+ filename,
312
+ parent: parentNode,
313
+ kind: FileKind.file,
314
+ size,
315
+ atime: now,
316
+ btime: now,
317
+ ctime: now,
318
+ mtime: now,
319
+ });
320
+ }
321
+
322
+ // 处理文件内容
323
+ if (size <= this.options.smallFileThreshold!) {
324
+ // Small file: store in file_node_meta.content
325
+ node.content = bufferData;
326
+ // If there was large file content, delete it
327
+ // Use QueryBuilder to avoid relationship issues
328
+ const existingContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
329
+ existingContentQb.where({ node: node });
330
+ const existingContent = await existingContentQb.getSingleResult();
331
+ if (existingContent) {
332
+ await em.removeAndFlush(existingContent);
333
+ }
334
+ } else {
335
+ // Large file: store in file_node_content table
336
+ node.content = undefined; // Clear small file content
337
+ const md5 = crypto.createHash('md5').update(bufferData).digest('hex');
338
+ const sha256 = crypto.createHash('sha256').update(bufferData).digest('hex');
339
+
340
+ // Check if fileContent already exists using QueryBuilder
341
+ const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
342
+ fileContentQb.where({ node: node });
343
+ let fileContent = await fileContentQb.getSingleResult();
344
+
345
+ if (fileContent) {
346
+ // Update existing content
347
+ fileContent.content = bufferData;
348
+ fileContent.size = size;
349
+ fileContent.md5 = md5;
350
+ fileContent.sha256 = sha256;
351
+ } else {
352
+ // Create new content entity
353
+ fileContent = em.create(FileNodeContentEntity, {
354
+ node: node, // Use node relationship as primary key
355
+ tid: node.tid,
356
+ content: bufferData,
357
+ size: size,
358
+ md5: md5,
359
+ sha256: sha256,
360
+ });
361
+ }
362
+ }
363
+
364
+ await em.persistAndFlush(node);
365
+ });
366
+ }
367
+
368
+ async rm(path: string, options: RmOptions = {}): Promise<void> {
369
+ await this.em.transactional(async (em) => {
370
+ const node = await this._getNodeByPath(path, em);
371
+ if (!node) {
372
+ if (options.force) return; // force=true, 不存在也算成功
373
+ throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
374
+ }
375
+
376
+ if (node.kind === FileKind.directory && !options.recursive) {
377
+ // Check if directory has children using QueryBuilder
378
+ const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
379
+ childrenQb.where({ parent: node });
380
+ childrenQb.select('id');
381
+ const children = await childrenQb.getResult();
382
+ if (children.length > 0) {
383
+ throw new FileSystemError(`Directory not empty: ${path}`, FileSystemErrorCode.ENOTEMPTY);
384
+ }
385
+ }
386
+
387
+ // 使用 orphanRemoval: true, ORM会自动处理子节点和内容的删除
388
+ await em.removeAndFlush(node);
389
+ });
390
+ }
391
+
392
+ async rename(oldPath: string, newPath: string, options: RenameOptions = {}): Promise<void> {
393
+ await this.em.transactional(async (em) => {
394
+ const node = await this._getNodeByPath(oldPath, em);
395
+ if (!node) throw new FileSystemError(`Source path not found: ${oldPath}`, FileSystemErrorCode.ENOENT);
396
+
397
+ const newParentPath = dirname(newPath);
398
+ const newFilename = basename(newPath);
399
+
400
+ const newParentNode = await this._getNodeByPath(newParentPath, em);
401
+ if (!newParentNode)
402
+ throw new FileSystemError(`Destination directory not found: ${newParentPath}`, FileSystemErrorCode.ENOENT);
403
+
404
+ const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
405
+ existingDestQb.where({ parent: newParentNode, filename: newFilename });
406
+ const existingDest = await existingDestQb.getSingleResult();
407
+ if (existingDest) {
408
+ if (!options.overwrite)
409
+ throw new FileSystemError(`Destination path already exists: ${newPath}`, FileSystemErrorCode.EEXIST);
410
+ if (node.id === existingDest.id) return; // 移动到原位置,什么都不做
411
+ await em.removeAndFlush(existingDest);
412
+ }
413
+
414
+ node.parent = newParentNode;
415
+ node.filename = newFilename;
416
+ node.mtime = new Date();
417
+
418
+ await em.flush();
419
+ });
420
+ }
421
+
422
+ async copy(srcPath: string, destPath: string, options: CopyOptions = {}): Promise<void> {
423
+ await this.em.transactional(async (em) => {
424
+ const srcNode = await this._getNodeByPath(srcPath, em);
425
+ if (!srcNode) throw new FileSystemError(`Source path not found: ${srcPath}`, FileSystemErrorCode.ENOENT);
426
+
427
+ const destParentPath = dirname(destPath);
428
+ const destFilename = basename(destPath);
429
+
430
+ const destParentNode = await this._getNodeByPath(destParentPath, em);
431
+ if (!destParentNode)
432
+ throw new FileSystemError(`Destination directory not found: ${destParentPath}`, FileSystemErrorCode.ENOENT);
433
+
434
+ const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
435
+ existingDestQb.where({ parent: destParentNode, filename: destFilename });
436
+ const existingDest = await existingDestQb.getSingleResult();
437
+ if (existingDest) {
438
+ if (!options.overwrite)
439
+ throw new FileSystemError(`Destination path already exists: ${destPath}`, FileSystemErrorCode.EEXIST);
440
+ // Delete existing destination within the same transaction
441
+ await em.removeAndFlush(existingDest);
442
+ }
443
+
444
+ await this._copyNode(srcNode, destParentNode, destFilename, em);
445
+ });
446
+ }
447
+
448
+ createReadStream(path: string, options?: CreateReadStreamOptions): never {
449
+ throw new Error('Streaming read is not supported by DBFS yet.');
450
+ }
451
+
452
+ createWriteStream(path: string, options?: CreateWriteStreamOptions): never {
453
+ throw new Error('Streaming write is not supported by DBFS yet.');
454
+ }
455
+
456
+ createReadableStream(path: string, options?: CreateReadStreamOptions): ReadableStream {
457
+ throw new Error('ReadableStream is not supported by DBFS yet.');
458
+ }
459
+
460
+ createWritableStream(path: string, options?: CreateWriteStreamOptions): WritableStream {
461
+ throw new Error('WritableStream is not supported by DBFS yet.');
462
+ }
463
+
464
+ /**
465
+ * 将路径字符串解析为数据库中的节点。这是大部分操作的基础。
466
+ * @param pathStr 绝对路径, e.g., /home/user/file.txt
467
+ * @param em EntityManager 实例
468
+ * @returns 找到的节点或 null
469
+ */
470
+ private async _getNodeByPath(pathStr: string, em: EntityManager): Promise<FileNodeMetaEntity | null> {
471
+ const normalized = normalize(pathStr);
472
+
473
+ if (normalized === '/') {
474
+ // Use QueryBuilder to avoid automatic relationship loading
475
+ const qb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
476
+ qb.where({ parent: null });
477
+ const rootNode = await qb.getSingleResult();
478
+ return rootNode || null;
479
+ }
480
+
481
+ const parts = normalized.split('/').filter((p) => p);
482
+
483
+ // Get root node
484
+ const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
485
+ rootQb.where({ parent: null });
486
+ let currentNode = await rootQb.getSingleResult();
487
+ if (!currentNode) return null;
488
+
489
+ // Traverse path parts
490
+ for (const part of parts) {
491
+ const childQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
492
+ childQb.where({ parent: currentNode, filename: part });
493
+ const child = await childQb.getSingleResult();
494
+ if (!child) return null;
495
+ currentNode = child;
496
+ }
497
+
498
+ return currentNode;
499
+ }
500
+
501
+ /**
502
+ * 将数据库实体转换为 IFileStat 接口
503
+ */
504
+ private _toFileStat(node: FileNodeMetaEntity, path: string): IFileStat {
505
+ // Handle mtime - it might be a Date object or a string from raw query
506
+ let mtime: number;
507
+ if (node.mtime instanceof Date) {
508
+ mtime = node.mtime.getTime();
509
+ } else if (typeof node.mtime === 'string') {
510
+ mtime = new Date(node.mtime).getTime();
511
+ } else {
512
+ mtime = Date.now();
513
+ }
514
+
515
+ // Handle size - convert bigint to number if needed
516
+ let size: number;
517
+ if (typeof node.size === 'bigint') {
518
+ size = Number(node.size);
519
+ } else {
520
+ size = node.size;
521
+ }
522
+
523
+ return {
524
+ path: path,
525
+ name: node.filename,
526
+ kind: node.kind,
527
+ size: size,
528
+ mtime: mtime,
529
+ meta: node.metadata || {},
530
+ // `directory` 字段可以根据 path 动态计算
531
+ directory: dirname(path),
532
+ };
533
+ }
534
+
535
+ private async _copyNode(
536
+ srcNode: FileNodeMetaEntity,
537
+ destParent: FileNodeMetaEntity,
538
+ newName: string,
539
+ em: EntityManager,
540
+ ): Promise<void> {
541
+ // 1. 复制节点本身
542
+ const now = new Date();
543
+ const newNode = em.create(FileNodeMetaEntity, {
544
+ tid: srcNode.tid,
545
+ filename: newName,
546
+ parent: destParent,
547
+ kind: srcNode.kind,
548
+ size: srcNode.size,
549
+ metadata: srcNode.metadata, // deep copy metadata
550
+ content: srcNode.content, // copy small file content
551
+ atime: now,
552
+ btime: now,
553
+ ctime: now,
554
+ mtime: now,
555
+ });
556
+
557
+ // 2. 复制大文件内容 (如果存在) - use QueryBuilder to avoid relationship loading
558
+ if (!srcNode.content) {
559
+ const srcFileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
560
+ srcFileContentQb.where({ node: srcNode });
561
+ const srcFileContent = await srcFileContentQb.getSingleResult();
562
+ if (srcFileContent) {
563
+ const newContent = em.create(FileNodeContentEntity, {
564
+ node: newNode, // Use node relationship as primary key
565
+ tid: srcNode.tid,
566
+ content: srcFileContent.content,
567
+ size: srcFileContent.size,
568
+ md5: srcFileContent.md5,
569
+ sha256: srcFileContent.sha256,
570
+ mimeType: srcFileContent.mimeType,
571
+ metadata: srcFileContent.metadata,
572
+ });
573
+ }
574
+ }
575
+
576
+ await em.persistAndFlush(newNode);
577
+
578
+ // 3. 如果是目录,递归复制子节点
579
+ if (srcNode.kind === FileKind.directory) {
580
+ const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
581
+ childrenQb.where({ parent: srcNode });
582
+ const children = await childrenQb.getResult();
583
+ for (const child of children) {
584
+ await this._copyNode(child, newNode, child.filename, em);
585
+ }
586
+ }
587
+ }
588
+ }
589
+
590
+ @Entity({ tableName: 'file_node_meta' })
591
+ @Unique({ properties: ['tid', 'parent', 'filename'] })
592
+ export class FileNodeMetaEntity extends TenantBaseEntity {
593
+ @Property({ type: types.string, nullable: false, comment: '文件名' })
594
+ filename!: string;
595
+ @Property({ type: types.bigint, nullable: false, default: 0, comment: '文件大小' })
596
+ size!: number & Opt;
597
+ @Property({ type: types.string, nullable: false, comment: '文件类型' })
598
+ kind!: FileKind;
599
+
600
+ @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
601
+ atime!: Date & Opt;
602
+ @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
603
+ btime!: Date & Opt;
604
+ @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
605
+ ctime!: Date & Opt;
606
+ @Property({ type: types.datetime, nullable: false, defaultRaw: 'CURRENT_TIMESTAMP' })
607
+ mtime!: Date & Opt;
608
+
609
+ @Property({ type: types.json, nullable: false, defaultRaw: '{}' })
610
+ metadata!: Record<string, any>;
611
+
612
+ @ManyToOne(() => FileNodeMetaEntity, { nullable: true, cascade: [] })
613
+ parent?: Rel<FileNodeMetaEntity>;
614
+
615
+ @OneToMany({ entity: () => FileNodeMetaEntity, mappedBy: 'parent', orphanRemoval: true })
616
+ children = new Collection<FileNodeMetaEntity>(this);
617
+
618
+ @OneToOne({
619
+ entity: () => FileNodeContentEntity,
620
+ mappedBy: 'node',
621
+ orphanRemoval: true,
622
+ nullable: true,
623
+ cascade: [Cascade.ALL],
624
+ })
625
+ fileContent?: Rel<FileNodeContentEntity>;
626
+ @Property({ type: types.blob, nullable: true, comment: '文件内容' })
627
+ content?: Buffer; // for small file, e.g. < 64k
628
+
629
+ //region content
630
+
631
+ get parentId() {
632
+ return this.parent?.id as Opt<string | undefined>;
633
+ }
634
+
635
+ //endregion
636
+ }
637
+
638
+ @Entity({ tableName: 'file_node_content' })
639
+ export class FileNodeContentEntity extends TenantBaseEntity {
640
+ @OneToOne({ entity: () => FileNodeMetaEntity, owner: true, joinColumn: 'node_id' })
641
+ node!: Rel<FileNodeMetaEntity>;
642
+
643
+ @Property({ type: types.integer, nullable: false })
644
+ size!: number; // 保留 size 方便查询分析
645
+
646
+ @Property({ type: types.blob, lazy: true, comment: '文件内容' })
647
+ content!: Buffer;
648
+
649
+ @Property({ type: types.string, nullable: true })
650
+ mimeType?: string;
651
+
652
+ @Property({ type: types.string, nullable: false })
653
+ md5?: string;
654
+ @Property({ type: types.string, nullable: false })
655
+ sha256?: string;
656
+
657
+ @Property({ type: types.string, nullable: true })
658
+ text?: string;
659
+ @Property({ type: types.integer, nullable: true })
660
+ width?: number;
661
+ @Property({ type: types.integer, nullable: true })
662
+ height?: number;
663
+ // @Property({ type: types.integer, nullable: true })
664
+ // length?: number;
665
+
666
+ @Property({ type: types.json, nullable: false, defaultRaw: '{}' })
667
+ metadata!: Record<string, any>;
668
+ }