@wener/common 2.0.5 → 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 (206) hide show
  1. package/lib/ai/qwen3vl/index.js +1 -1
  2. package/lib/ai/qwen3vl/utils.js +15 -15
  3. package/lib/ai/vision/DocLayoutElementTypeSchema.js +22 -22
  4. package/lib/ai/vision/ImageAnnotationSchema.js +63 -47
  5. package/lib/ai/vision/index.js +2 -2
  6. package/lib/ai/vision/resolveImageAnnotation.js +81 -95
  7. package/lib/cn/ChineseResidentIdNo.js +55 -41
  8. package/lib/cn/ChineseResidentIdNo.mod.js +6 -1
  9. package/lib/cn/ChineseResidentIdNo.test.js +22 -21
  10. package/lib/cn/DivisionCode.js +220 -235
  11. package/lib/cn/DivisionCode.mod.js +6 -1
  12. package/lib/cn/DivisionCode.test.js +92 -121
  13. package/lib/cn/Mod11.js +18 -37
  14. package/lib/cn/Mod31.js +23 -41
  15. package/lib/cn/UnifiedSocialCreditCode.js +143 -137
  16. package/lib/cn/UnifiedSocialCreditCode.mod.js +6 -1
  17. package/lib/cn/UnifiedSocialCreditCode.test.js +21 -15
  18. package/lib/cn/formatChineseAmount.js +46 -71
  19. package/lib/cn/index.js +6 -6
  20. package/lib/cn/mod.js +5 -3
  21. package/lib/cn/parseChineseNumber.js +81 -85
  22. package/lib/cn/parseChineseNumber.test.js +183 -261
  23. package/lib/cn/pinyin/cartesianProduct.js +19 -19
  24. package/lib/cn/pinyin/cartesianProduct.test.js +78 -178
  25. package/lib/cn/pinyin/loader.js +13 -11
  26. package/lib/cn/pinyin/preload.js +2 -1
  27. package/lib/cn/pinyin/toPinyin.test.js +149 -161
  28. package/lib/cn/pinyin/toPinyinPure.js +28 -23
  29. package/lib/cn/pinyin/transform.js +11 -11
  30. package/lib/cn/types.d.js +2 -2
  31. package/lib/consola/createStandardConsolaReporter.js +14 -15
  32. package/lib/consola/formatLogObject.js +149 -133
  33. package/lib/consola/formatLogObject.test.js +167 -178
  34. package/lib/consola/index.js +2 -2
  35. package/lib/data/formatSort.js +14 -12
  36. package/lib/data/formatSort.test.js +33 -33
  37. package/lib/data/index.js +3 -3
  38. package/lib/data/maybeNumber.js +23 -23
  39. package/lib/data/parseSort.js +75 -68
  40. package/lib/data/parseSort.test.js +196 -187
  41. package/lib/data/resolvePagination.js +38 -39
  42. package/lib/data/resolvePagination.test.js +228 -218
  43. package/lib/data/types.d.js +2 -2
  44. package/lib/dayjs/dayjs.js +20 -20
  45. package/lib/dayjs/formatDuration.js +56 -56
  46. package/lib/dayjs/formatDuration.test.js +63 -77
  47. package/lib/dayjs/index.js +4 -4
  48. package/lib/dayjs/parseDuration.js +21 -26
  49. package/lib/dayjs/parseRelativeTime.js +65 -66
  50. package/lib/dayjs/parseRelativeTime.test.js +227 -243
  51. package/lib/dayjs/resolveRelativeTime.js +73 -72
  52. package/lib/dayjs/resolveRelativeTime.test.js +296 -307
  53. package/lib/decimal/index.js +1 -1
  54. package/lib/decimal/parseDecimal.js +12 -12
  55. package/lib/drain3/Drain.js +303 -338
  56. package/lib/drain3/LogCluster.js +25 -25
  57. package/lib/drain3/Node.js +24 -24
  58. package/lib/drain3/TemplateMiner.js +197 -196
  59. package/lib/drain3/index.js +5 -5
  60. package/lib/drain3/persistence/FilePersistence.js +19 -19
  61. package/lib/drain3/persistence/MemoryPersistence.js +8 -8
  62. package/lib/drain3/persistence/PersistenceHandler.js +2 -2
  63. package/lib/drain3/types.js +2 -2
  64. package/lib/emittery/emitter.js +7 -7
  65. package/lib/emittery/index.js +1 -1
  66. package/lib/foundation/schema/SexType.js +15 -12
  67. package/lib/foundation/schema/index.js +1 -1
  68. package/lib/foundation/schema/parseSexType.js +15 -16
  69. package/lib/foundation/schema/types.js +8 -6
  70. package/lib/fs/FileSystemError.js +18 -18
  71. package/lib/fs/IFileSystem.d.js +2 -2
  72. package/lib/fs/MemoryFileSystem.test.js +172 -181
  73. package/lib/fs/createBrowserFileSystem.js +222 -235
  74. package/lib/fs/createMemoryFileSystem.js +472 -510
  75. package/lib/fs/createSandboxFileSystem.js +102 -101
  76. package/lib/fs/createWebDavFileSystem.js +162 -149
  77. package/lib/fs/createWebFileSystem.js +197 -220
  78. package/lib/fs/findMimeType.js +14 -14
  79. package/lib/fs/index.js +7 -7
  80. package/lib/fs/minio/createMinioFileSystem.js +959 -956
  81. package/lib/fs/minio/index.js +1 -1
  82. package/lib/fs/orpc/FileSystemContract.js +57 -57
  83. package/lib/fs/orpc/createContractClientFileSystem.js +88 -88
  84. package/lib/fs/orpc/index.js +2 -2
  85. package/lib/fs/orpc/server/createFileSystemContractImpl.js +62 -60
  86. package/lib/fs/orpc/server/index.js +1 -1
  87. package/lib/fs/s3/createS3MiniFileSystem.js +756 -737
  88. package/lib/fs/s3/index.js +1 -1
  89. package/lib/fs/s3/s3mini.test.js +524 -553
  90. package/lib/fs/scandir.js +56 -56
  91. package/lib/fs/server/createDatabaseFileSystem.js +834 -741
  92. package/lib/fs/server/createNodeFileSystem.js +407 -405
  93. package/lib/fs/server/dbfs.test.js +201 -214
  94. package/lib/fs/server/index.js +1 -1
  95. package/lib/fs/server/loadTestDatabase.js +40 -43
  96. package/lib/fs/tests/runFileSystemTest.js +352 -316
  97. package/lib/fs/types.js +17 -20
  98. package/lib/fs/utils/getFileUrl.js +24 -30
  99. package/lib/fs/utils.js +17 -17
  100. package/lib/fs/webdav/index.js +1 -1
  101. package/lib/index.js +2 -2
  102. package/lib/jsonschema/JsonSchema.js +216 -155
  103. package/lib/jsonschema/JsonSchema.test.js +123 -124
  104. package/lib/jsonschema/forEachJsonSchema.js +41 -41
  105. package/lib/jsonschema/index.js +2 -2
  106. package/lib/jsonschema/types.d.js +2 -2
  107. package/lib/meta/defineFileType.js +32 -38
  108. package/lib/meta/defineInit.js +39 -35
  109. package/lib/meta/defineMetadata.js +37 -34
  110. package/lib/meta/defineMetadata.test.js +13 -12
  111. package/lib/meta/index.js +3 -3
  112. package/lib/orpc/createOpenApiContractClient.js +26 -24
  113. package/lib/orpc/createRpcContractClient.js +37 -31
  114. package/lib/orpc/index.js +2 -2
  115. package/lib/orpc/resolveLinkPlugins.js +25 -25
  116. package/lib/password/PHC.js +187 -189
  117. package/lib/password/PHC.test.js +517 -535
  118. package/lib/password/Password.js +85 -80
  119. package/lib/password/Password.test.js +330 -364
  120. package/lib/password/createArgon2PasswordAlgorithm.js +50 -51
  121. package/lib/password/createBase64PasswordAlgorithm.js +11 -11
  122. package/lib/password/createBcryptPasswordAlgorithm.js +20 -18
  123. package/lib/password/createPBKDF2PasswordAlgorithm.js +65 -52
  124. package/lib/password/createScryptPasswordAlgorithm.js +74 -63
  125. package/lib/password/index.js +5 -5
  126. package/lib/password/server/index.js +1 -1
  127. package/lib/resource/Identifiable.js +2 -2
  128. package/lib/resource/ListQuery.js +42 -42
  129. package/lib/resource/getTitleOfResource.js +5 -5
  130. package/lib/resource/index.js +2 -2
  131. package/lib/resource/schema/AnyResourceSchema.js +91 -89
  132. package/lib/resource/schema/BaseResourceSchema.js +26 -26
  133. package/lib/resource/schema/ResourceActionType.js +117 -115
  134. package/lib/resource/schema/ResourceStatus.js +94 -92
  135. package/lib/resource/schema/ResourceType.js +25 -23
  136. package/lib/resource/schema/index.js +5 -5
  137. package/lib/resource/schema/types.js +86 -55
  138. package/lib/resource/schema/types.test.js +16 -13
  139. package/lib/s3/formatS3Url.js +60 -60
  140. package/lib/s3/formatS3Url.test.js +238 -261
  141. package/lib/s3/index.js +2 -2
  142. package/lib/s3/parseS3Url.js +61 -60
  143. package/lib/s3/parseS3Url.test.js +270 -269
  144. package/lib/schema/SchemaRegistry.js +41 -42
  145. package/lib/schema/SchemaRegistry.mod.js +1 -1
  146. package/lib/schema/TypeSchema.d.js +2 -2
  147. package/lib/schema/createSchemaData.js +113 -67
  148. package/lib/schema/findJsonSchemaByPath.js +28 -23
  149. package/lib/schema/formatZodError.js +112 -131
  150. package/lib/schema/formatZodError.test.js +192 -195
  151. package/lib/schema/getSchemaCache.js +7 -7
  152. package/lib/schema/getSchemaOptions.js +17 -16
  153. package/lib/schema/index.js +6 -6
  154. package/lib/schema/toJsonSchema.js +195 -189
  155. package/lib/schema/toJsonSchema.test.js +34 -26
  156. package/lib/schema/validate.js +105 -96
  157. package/lib/tools/generateSchema.js +40 -40
  158. package/lib/tools/renderJsonSchemaToMarkdownDoc.js +74 -74
  159. package/lib/utils/buildBaseUrl.js +8 -8
  160. package/lib/utils/buildRedactorFormSchema.js +54 -53
  161. package/lib/utils/getEstimateProcessTime.js +24 -19
  162. package/lib/utils/index.js +3 -3
  163. package/lib/utils/resolveFeatureOptions.js +9 -9
  164. package/package.json +14 -14
  165. package/src/ai/vision/index.ts +2 -2
  166. package/src/cn/index.ts +1 -2
  167. package/src/consola/index.ts +1 -1
  168. package/src/data/index.ts +3 -4
  169. package/src/data/resolvePagination.ts +2 -2
  170. package/src/dayjs/formatDuration.ts +8 -9
  171. package/src/dayjs/index.ts +1 -1
  172. package/src/dayjs/parseRelativeTime.ts +1 -1
  173. package/src/dayjs/resolveRelativeTime.ts +1 -1
  174. package/src/drain3/Drain.test.ts +2 -2
  175. package/src/drain3/index.ts +2 -4
  176. package/src/fs/createWebDavFileSystem.ts +2 -7
  177. package/src/fs/createWebFileSystem.ts +1 -1
  178. package/src/fs/index.ts +4 -4
  179. package/src/fs/minio/createMinioFileSystem.ts +2 -2
  180. package/src/fs/minio/index.ts +1 -1
  181. package/src/fs/s3/createS3MiniFileSystem.ts +1 -1
  182. package/src/fs/server/createDatabaseFileSystem.ts +84 -120
  183. package/src/fs/server/dbfs.test.ts +14 -10
  184. package/src/fs/server/index.ts +1 -0
  185. package/src/fs/server/loadTestDatabase.ts +8 -119
  186. package/src/jsonschema/index.ts +1 -1
  187. package/src/meta/index.ts +2 -3
  188. package/src/orm/createSqliteDialect.ts +17 -0
  189. package/src/orm/index.ts +1 -0
  190. package/src/orpc/createOpenApiContractClient.ts +1 -1
  191. package/src/orpc/index.ts +1 -1
  192. package/src/password/createArgon2PasswordAlgorithm.ts +1 -1
  193. package/src/password/index.ts +2 -2
  194. package/src/resource/index.ts +3 -3
  195. package/src/resource/schema/index.ts +4 -4
  196. package/src/s3/index.ts +1 -1
  197. package/src/schema/SchemaRegistry.ts +1 -1
  198. package/src/schema/createSchemaData.ts +1 -1
  199. package/src/schema/findJsonSchemaByPath.ts +1 -1
  200. package/src/schema/index.ts +5 -5
  201. package/src/schema/validate.ts +1 -1
  202. package/src/utils/buildRedactorFormSchema.ts +1 -1
  203. package/src/utils/formatNumber.ts +18 -0
  204. package/src/utils/formatPercent.ts +17 -0
  205. package/src/utils/index.ts +3 -3
  206. package/src/utils/resolveFeatureOptions.ts +1 -1
@@ -1,750 +1,843 @@
1
- import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
2
- import { _ as _ts_metadata } from "@swc/helpers/_/_ts_metadata";
3
- import * as crypto from "node:crypto";
4
- import { basename, dirname, join, normalize } from "node:path";
5
- import { Cascade, Collection, Entity, ManyToOne, OneToMany, OneToOne, Property, types, Unique } from "@mikro-orm/core";
6
- import { TenantBaseEntity } from "@wener/server/entity";
7
- import { getEntityManager } from "@wener/server/mikro-orm";
8
- import { FileSystemError, FileSystemErrorCode } from "../FileSystemError.js";
9
- import { FileKind } from "../types.js";
1
+ import * as crypto from 'node:crypto';
2
+ import { basename, dirname, join, normalize } from 'node:path';
3
+ import { Cascade, Collection, Entity, ManyToOne, OneToMany, OneToOne, Property, types, Unique } from '@mikro-orm/core';
4
+ import { _ as _ts_decorate } from '@swc/helpers/_/_ts_decorate';
5
+ import { _ as _ts_metadata } from '@swc/helpers/_/_ts_metadata';
6
+ import { TenantBaseEntity } from '@wener/server/entity';
7
+ import { getEntityManager } from '@wener/server/mikro-orm';
8
+ import { FileSystemError, FileSystemErrorCode } from '../FileSystemError.js';
9
+ import { FileKind } from '../types.js';
10
10
  export function createDatabaseFileSystem(options = {}) {
11
- return new DBFS(options);
11
+ return new DBFS(options);
12
12
  }
13
13
  let DBFS = class DBFS {
14
- options;
15
- constructor(options = {}) {
16
- this.options = {
17
- getEntityManager: () => getEntityManager().fork(),
18
- smallFileThreshold: 512,
19
- ...options
20
- };
21
- }
22
- get em() {
23
- return this.options.getEntityManager();
24
- }
25
- /**
26
- * Ensure root node exists, create it if it doesn't exist
27
- * @returns The root FileNodeMetaEntity
28
- */ async ensureRootNode() {
29
- const em = this.em;
30
- const rootQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
31
- rootQb.where({
32
- parent: null
33
- });
34
- const rootNode = await rootQb.getSingleResult();
35
- if (rootNode) {
36
- return rootNode;
37
- }
38
- // Create root directory (parent is null, filename is empty string)
39
- const now = new Date();
40
- const rootDir = em.create(FileNodeMetaEntity, {
41
- filename: "",
42
- parent: null,
43
- kind: FileKind.directory,
44
- size: 0,
45
- atime: now,
46
- btime: now,
47
- ctime: now,
48
- mtime: now
49
- });
50
- try {
51
- await em.persistAndFlush(rootDir);
52
- return rootDir;
53
- }
54
- catch (error) {
55
- // If root already exists (race condition), fetch and return it
56
- if (error.message?.includes("UNIQUE constraint") || error.message?.includes("duplicate")) {
57
- const existingRootQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
58
- existingRootQb.where({
59
- parent: null
60
- });
61
- const existingRoot = await existingRootQb.getSingleResult();
62
- if (existingRoot) {
63
- return existingRoot;
64
- }
65
- }
66
- throw error;
67
- }
68
- }
69
- async stat(path, _options) {
70
- // Validate input
71
- if (!path || typeof path !== "string") {
72
- throw new FileSystemError("Invalid path", FileSystemErrorCode.EINVAL);
73
- }
74
- const em = this.em;
75
- const node = await this._getNodeByPath(path, em);
76
- if (!node) {
77
- throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
78
- }
79
- return this._toFileStat(node, path);
80
- }
81
- async exists(path) {
82
- return !!await this._getNodeByPath(path, this.em);
83
- }
84
- async readdir(dir, _options) {
85
- const em = this.em;
86
- const parentNode = await this._getNodeByPath(dir, em);
87
- if (!parentNode) {
88
- throw new FileSystemError(`Directory not found: ${dir}`, FileSystemErrorCode.ENOENT);
89
- }
90
- if (parentNode.kind !== FileKind.directory) {
91
- throw new FileSystemError(`Path is not a directory: ${dir}`, FileSystemErrorCode.ENOTDIR);
92
- }
93
- // Use QueryBuilder to avoid automatic relationship loading
94
- const qb = em.createQueryBuilder(FileNodeMetaEntity, "f");
95
- qb.where({
96
- parent: parentNode
97
- });
98
- const children = await qb.getResult();
99
- return children.map((child) => this._toFileStat(child, join(dir, child.filename)));
100
- }
101
- async mkdir(path, options = {}) {
102
- await this._mkdirInTransaction(path, options, this.em);
103
- }
104
- async _mkdirInTransaction(path, options, em) {
105
- const normalized = normalize(path);
106
- // Special handling for root directory
107
- if (normalized === "/") {
108
- const rootQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
109
- rootQb.where({
110
- parent: null
111
- });
112
- const rootNode = await rootQb.getSingleResult();
113
- if (rootNode) {
114
- // Root directory already exists
115
- return;
116
- }
117
- // Create root directory (parent is null, filename is empty string)
118
- const now = new Date();
119
- const rootDir = em.create(FileNodeMetaEntity, {
120
- filename: "",
121
- parent: null,
122
- kind: FileKind.directory,
123
- size: 0,
124
- atime: now,
125
- btime: now,
126
- ctime: now,
127
- mtime: now
128
- });
129
- try {
130
- await em.persistAndFlush(rootDir);
131
- }
132
- catch (error) {
133
- // If root already exists (race condition), ignore the error
134
- if (!error.message?.includes("UNIQUE constraint") && !error.message?.includes("duplicate")) {
135
- throw error;
136
- }
137
- }
138
- return;
139
- }
140
- const parentPath = dirname(normalized);
141
- const newDirName = basename(normalized);
142
- if (!newDirName)
143
- throw new FileSystemError("Cannot create directory with empty name", FileSystemErrorCode.EINVAL);
144
- let parentNode = await this._getNodeByPath(parentPath, em);
145
- if (!parentNode) {
146
- if (options.recursive) {
147
- // 递归创建父目录
148
- await this._mkdirInTransaction(parentPath, options, em);
149
- parentNode = await this._getNodeByPath(parentPath, em);
150
- }
151
- else {
152
- throw new FileSystemError(`Parent directory not found: ${parentPath}`, FileSystemErrorCode.ENOENT);
153
- }
154
- }
155
- if (!parentNode)
156
- throw new FileSystemError("Failed to create parent directory structure", FileSystemErrorCode.EINVAL);
157
- // Check if directory already exists using QueryBuilder
158
- const existingQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
159
- existingQb.where({
160
- parent: parentNode,
161
- filename: newDirName
162
- });
163
- const existing = await existingQb.getSingleResult();
164
- if (existing) {
165
- if (existing.kind === FileKind.directory) {
166
- // Directory already exists, return silently
167
- return;
168
- }
169
- throw new FileSystemError(`A file with the same name already exists: ${path}`, FileSystemErrorCode.EEXIST);
170
- }
171
- // Create directory using EntityManager
172
- const now = new Date();
173
- const newDir = em.create(FileNodeMetaEntity, {
174
- tid: parentNode.tid,
175
- parent: parentNode,
176
- filename: newDirName,
177
- kind: FileKind.directory,
178
- size: 0,
179
- atime: now,
180
- btime: now,
181
- ctime: now,
182
- mtime: now
183
- });
184
- await em.persistAndFlush(newDir);
185
- }
186
- async readFile(path, options) {
187
- const em = this.em;
188
- const node = await this._getNodeByPath(path, em);
189
- if (!node)
190
- throw new FileSystemError(`File not found: ${path}`, FileSystemErrorCode.ENOENT);
191
- if (node.kind !== FileKind.file)
192
- throw new FileSystemError(`Path is not a file: ${path}`, FileSystemErrorCode.EISDIR);
193
- let buffer;
194
- if (node.content) {
195
- // 小文件优化 - content is already loaded
196
- buffer = node.content;
197
- }
198
- else {
199
- // Large file: load from file_node_content table using QueryBuilder
200
- const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, "fc");
201
- fileContentQb.where({
202
- node: node
203
- });
204
- const fileContent = await fileContentQb.getSingleResult();
205
- if (!fileContent)
206
- throw new FileSystemError("File content is missing", FileSystemErrorCode.ENOENT);
207
- buffer = fileContent.content;
208
- }
209
- return options?.encoding === "text" ? buffer.toString("utf-8") : buffer;
210
- }
211
- async writeFile(path, data, options = {}) {
212
- // Validate input
213
- if (!path || typeof path !== "string") {
214
- throw new FileSystemError("Invalid path", FileSystemErrorCode.EINVAL);
215
- }
216
- if (data === null || data === undefined) {
217
- throw new FileSystemError("Invalid data", FileSystemErrorCode.EINVAL);
218
- }
219
- await this.em.transactional(async (em) => {
220
- const { overwrite = true } = options;
221
- const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf-8");
222
- const size = bufferData.length;
223
- const parentPath = dirname(path);
224
- const filename = basename(path);
225
- // Validate filename
226
- if (!filename) {
227
- throw new FileSystemError("filename cannot be empty", FileSystemErrorCode.EINVAL);
228
- }
229
- // 确保父目录存在 - create it within the transaction
230
- await this._mkdirInTransaction(parentPath, {
231
- recursive: true
232
- }, em);
233
- const parentNode = await this._getNodeByPath(parentPath, em);
234
- if (!parentNode)
235
- throw new FileSystemError("Failed to establish parent directory", FileSystemErrorCode.EINVAL);
236
- // Find existing node using QueryBuilder
237
- const nodeQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
238
- nodeQb.where({
239
- parent: parentNode,
240
- filename
241
- });
242
- let node = await nodeQb.getSingleResult();
243
- if (node) {
244
- // 文件已存在
245
- if (!overwrite)
246
- throw new FileSystemError(`File already exists: ${path}`, FileSystemErrorCode.EEXIST);
247
- if (node.kind === FileKind.directory)
248
- throw new FileSystemError(`Cannot overwrite a directory with a file: ${path}`, FileSystemErrorCode.EISDIR);
249
- // 更新节点
250
- node.size = size;
251
- node.mtime = new Date();
252
- // ... 其他时间戳
253
- }
254
- else {
255
- // 新建文件
256
- const now = new Date();
257
- node = em.create(FileNodeMetaEntity, {
258
- tid: parentNode.tid,
259
- filename,
260
- parent: parentNode,
261
- kind: FileKind.file,
262
- size,
263
- atime: now,
264
- btime: now,
265
- ctime: now,
266
- mtime: now
267
- });
268
- }
269
- // 处理文件内容
270
- if (size <= this.options.smallFileThreshold) {
271
- // Small file: store in file_node_meta.content
272
- node.content = bufferData;
273
- // If there was large file content, delete it
274
- // Use QueryBuilder to avoid relationship issues
275
- const existingContentQb = em.createQueryBuilder(FileNodeContentEntity, "fc");
276
- existingContentQb.where({
277
- node: node
278
- });
279
- const existingContent = await existingContentQb.getSingleResult();
280
- if (existingContent) {
281
- await em.removeAndFlush(existingContent);
282
- }
283
- }
284
- else {
285
- // Large file: store in file_node_content table
286
- node.content = undefined; // Clear small file content
287
- const md5 = crypto.createHash("md5").update(bufferData).digest("hex");
288
- const sha256 = crypto.createHash("sha256").update(bufferData).digest("hex");
289
- // Check if fileContent already exists using QueryBuilder
290
- const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, "fc");
291
- fileContentQb.where({
292
- node: node
293
- });
294
- let fileContent = await fileContentQb.getSingleResult();
295
- if (fileContent) {
296
- // Update existing content
297
- fileContent.content = bufferData;
298
- fileContent.size = size;
299
- fileContent.md5 = md5;
300
- fileContent.sha256 = sha256;
301
- }
302
- else {
303
- // Create new content entity
304
- fileContent = em.create(FileNodeContentEntity, {
305
- node: node,
306
- tid: node.tid,
307
- content: bufferData,
308
- size: size,
309
- md5: md5,
310
- sha256: sha256
311
- });
312
- }
313
- }
314
- await em.persistAndFlush(node);
315
- });
316
- }
317
- async rm(path, options = {}) {
318
- await this.em.transactional(async (em) => {
319
- const node = await this._getNodeByPath(path, em);
320
- if (!node) {
321
- if (options.force)
322
- return; // force=true, 不存在也算成功
323
- throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
324
- }
325
- if (node.kind === FileKind.directory && !options.recursive) {
326
- // Check if directory has children using QueryBuilder
327
- const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
328
- childrenQb.where({
329
- parent: node
330
- });
331
- childrenQb.select("id");
332
- const children = await childrenQb.getResult();
333
- if (children.length > 0) {
334
- throw new FileSystemError(`Directory not empty: ${path}`, FileSystemErrorCode.ENOTEMPTY);
335
- }
336
- }
337
- // 使用 orphanRemoval: true, ORM会自动处理子节点和内容的删除
338
- await em.removeAndFlush(node);
339
- });
340
- }
341
- async rename(oldPath, newPath, options = {}) {
342
- await this.em.transactional(async (em) => {
343
- const node = await this._getNodeByPath(oldPath, em);
344
- if (!node)
345
- throw new FileSystemError(`Source path not found: ${oldPath}`, FileSystemErrorCode.ENOENT);
346
- const newParentPath = dirname(newPath);
347
- const newFilename = basename(newPath);
348
- const newParentNode = await this._getNodeByPath(newParentPath, em);
349
- if (!newParentNode)
350
- throw new FileSystemError(`Destination directory not found: ${newParentPath}`, FileSystemErrorCode.ENOENT);
351
- const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
352
- existingDestQb.where({
353
- parent: newParentNode,
354
- filename: newFilename
355
- });
356
- const existingDest = await existingDestQb.getSingleResult();
357
- if (existingDest) {
358
- if (!options.overwrite)
359
- throw new FileSystemError(`Destination path already exists: ${newPath}`, FileSystemErrorCode.EEXIST);
360
- if (node.id === existingDest.id)
361
- return; // 移动到原位置,什么都不做
362
- await em.removeAndFlush(existingDest);
363
- }
364
- node.parent = newParentNode;
365
- node.filename = newFilename;
366
- node.mtime = new Date();
367
- await em.flush();
368
- });
369
- }
370
- async copy(srcPath, destPath, options = {}) {
371
- await this.em.transactional(async (em) => {
372
- const srcNode = await this._getNodeByPath(srcPath, em);
373
- if (!srcNode)
374
- throw new FileSystemError(`Source path not found: ${srcPath}`, FileSystemErrorCode.ENOENT);
375
- const destParentPath = dirname(destPath);
376
- const destFilename = basename(destPath);
377
- const destParentNode = await this._getNodeByPath(destParentPath, em);
378
- if (!destParentNode)
379
- throw new FileSystemError(`Destination directory not found: ${destParentPath}`, FileSystemErrorCode.ENOENT);
380
- const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
381
- existingDestQb.where({
382
- parent: destParentNode,
383
- filename: destFilename
384
- });
385
- const existingDest = await existingDestQb.getSingleResult();
386
- if (existingDest) {
387
- if (!options.overwrite)
388
- throw new FileSystemError(`Destination path already exists: ${destPath}`, FileSystemErrorCode.EEXIST);
389
- // Delete existing destination within the same transaction
390
- await em.removeAndFlush(existingDest);
391
- }
392
- await this._copyNode(srcNode, destParentNode, destFilename, em);
393
- });
394
- }
395
- createReadStream(_path, _options) {
396
- throw new Error("Streaming read is not supported by DBFS yet.");
397
- }
398
- createWriteStream(_path, _options) {
399
- throw new Error("Streaming write is not supported by DBFS yet.");
400
- }
401
- createReadableStream(_path, _options) {
402
- throw new Error("ReadableStream is not supported by DBFS yet.");
403
- }
404
- createWritableStream(_path, _options) {
405
- throw new Error("WritableStream is not supported by DBFS yet.");
406
- }
407
- /**
408
- * 将路径字符串解析为数据库中的节点。这是大部分操作的基础。
409
- * @param pathStr 绝对路径, e.g., /home/user/file.txt
410
- * @param em EntityManager 实例
411
- * @returns 找到的节点或 null
412
- */ async _getNodeByPath(pathStr, em) {
413
- const normalized = normalize(pathStr);
414
- if (normalized === "/") {
415
- // Use QueryBuilder to avoid automatic relationship loading
416
- const qb = em.createQueryBuilder(FileNodeMetaEntity, "f");
417
- qb.where({
418
- parent: null
419
- });
420
- const rootNode = await qb.getSingleResult();
421
- return rootNode || null;
422
- }
423
- const parts = normalized.split("/").filter((p) => p);
424
- // Get root node
425
- const rootQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
426
- rootQb.where({
427
- parent: null
428
- });
429
- let currentNode = await rootQb.getSingleResult();
430
- if (!currentNode)
431
- return null;
432
- // Traverse path parts
433
- for (const part of parts) {
434
- const childQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
435
- childQb.where({
436
- parent: currentNode,
437
- filename: part
438
- });
439
- const child = await childQb.getSingleResult();
440
- if (!child)
441
- return null;
442
- currentNode = child;
443
- }
444
- return currentNode;
445
- }
446
- /**
447
- * 将数据库实体转换为 IFileStat 接口
448
- */ _toFileStat(node, path) {
449
- // Handle mtime - it might be a Date object or a string from raw query
450
- let mtime;
451
- if (node.mtime instanceof Date) {
452
- mtime = node.mtime.getTime();
453
- }
454
- else if (typeof node.mtime === "string") {
455
- mtime = new Date(node.mtime).getTime();
456
- }
457
- else {
458
- mtime = Date.now();
459
- }
460
- // Handle size - convert bigint to number if needed
461
- let size;
462
- if (typeof node.size === "bigint") {
463
- size = Number(node.size);
464
- }
465
- else {
466
- size = node.size;
467
- }
468
- return {
469
- path: path,
470
- name: node.filename,
471
- kind: node.kind,
472
- size: size,
473
- mtime: mtime,
474
- meta: node.metadata || {},
475
- // `directory` 字段可以根据 path 动态计算
476
- directory: dirname(path)
477
- };
478
- }
479
- async _copyNode(srcNode, destParent, newName, em) {
480
- // 1. 复制节点本身
481
- const now = new Date();
482
- const newNode = em.create(FileNodeMetaEntity, {
483
- tid: srcNode.tid,
484
- filename: newName,
485
- parent: destParent,
486
- kind: srcNode.kind,
487
- size: srcNode.size,
488
- metadata: srcNode.metadata,
489
- content: srcNode.content,
490
- atime: now,
491
- btime: now,
492
- ctime: now,
493
- mtime: now
494
- });
495
- // 2. 复制大文件内容 (如果存在) - use QueryBuilder to avoid relationship loading
496
- if (!srcNode.content) {
497
- const srcFileContentQb = em.createQueryBuilder(FileNodeContentEntity, "fc");
498
- srcFileContentQb.where({
499
- node: srcNode
500
- });
501
- const srcFileContent = await srcFileContentQb.getSingleResult();
502
- if (srcFileContent) {
503
- const _newContent = em.create(FileNodeContentEntity, {
504
- node: newNode,
505
- tid: srcNode.tid,
506
- content: srcFileContent.content,
507
- size: srcFileContent.size,
508
- md5: srcFileContent.md5,
509
- sha256: srcFileContent.sha256,
510
- mimeType: srcFileContent.mimeType,
511
- metadata: srcFileContent.metadata
512
- });
513
- }
514
- }
515
- await em.persistAndFlush(newNode);
516
- // 3. 如果是目录,递归复制子节点
517
- if (srcNode.kind === FileKind.directory) {
518
- const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, "f");
519
- childrenQb.where({
520
- parent: srcNode
521
- });
522
- const children = await childrenQb.getResult();
523
- for (const child of children) {
524
- await this._copyNode(child, newNode, child.filename, em);
525
- }
526
- }
527
- }
14
+ options;
15
+ constructor(options = {}) {
16
+ this.options = {
17
+ getEntityManager: () => getEntityManager().fork(),
18
+ smallFileThreshold: 512,
19
+ ...options,
20
+ };
21
+ }
22
+ get em() {
23
+ return this.options.getEntityManager();
24
+ }
25
+ /**
26
+ * Ensure root node exists, create it if it doesn't exist
27
+ * @returns The root FileNodeMetaEntity
28
+ */ async ensureRootNode() {
29
+ const em = this.em;
30
+ const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
31
+ rootQb.where({
32
+ parent: null,
33
+ });
34
+ const rootNode = await rootQb.getSingleResult();
35
+ if (rootNode) {
36
+ return rootNode;
37
+ }
38
+ // Create root directory (parent is null, filename is empty string)
39
+ const now = new Date();
40
+ const rootDir = em.create(FileNodeMetaEntity, {
41
+ filename: '',
42
+ parent: null,
43
+ kind: FileKind.directory,
44
+ size: 0,
45
+ atime: now,
46
+ btime: now,
47
+ ctime: now,
48
+ mtime: now,
49
+ });
50
+ try {
51
+ await em.persistAndFlush(rootDir);
52
+ return rootDir;
53
+ } catch (error) {
54
+ // If root already exists (race condition), fetch and return it
55
+ if (error.message?.includes('UNIQUE constraint') || error.message?.includes('duplicate')) {
56
+ const existingRootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
57
+ existingRootQb.where({
58
+ parent: null,
59
+ });
60
+ const existingRoot = await existingRootQb.getSingleResult();
61
+ if (existingRoot) {
62
+ return existingRoot;
63
+ }
64
+ }
65
+ throw error;
66
+ }
67
+ }
68
+ async stat(path, _options) {
69
+ // Validate input
70
+ if (!path || typeof path !== 'string') {
71
+ throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
72
+ }
73
+ const em = this.em;
74
+ const node = await this._getNodeByPath(path, em);
75
+ if (!node) {
76
+ throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
77
+ }
78
+ return this._toFileStat(node, path);
79
+ }
80
+ async exists(path) {
81
+ return !!(await this._getNodeByPath(path, this.em));
82
+ }
83
+ async readdir(dir, _options) {
84
+ const em = this.em;
85
+ const parentNode = await this._getNodeByPath(dir, em);
86
+ if (!parentNode) {
87
+ throw new FileSystemError(`Directory not found: ${dir}`, FileSystemErrorCode.ENOENT);
88
+ }
89
+ if (parentNode.kind !== FileKind.directory) {
90
+ throw new FileSystemError(`Path is not a directory: ${dir}`, FileSystemErrorCode.ENOTDIR);
91
+ }
92
+ // Use QueryBuilder to avoid automatic relationship loading
93
+ const qb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
94
+ qb.where({
95
+ parent: parentNode,
96
+ });
97
+ const children = await qb.getResult();
98
+ return children.map((child) => this._toFileStat(child, join(dir, child.filename)));
99
+ }
100
+ async mkdir(path, options = {}) {
101
+ await this._mkdirInTransaction(path, options, this.em);
102
+ }
103
+ async _mkdirInTransaction(path, options, em) {
104
+ const normalized = normalize(path);
105
+ // Special handling for root directory
106
+ if (normalized === '/') {
107
+ const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
108
+ rootQb.where({
109
+ parent: null,
110
+ });
111
+ const rootNode = await rootQb.getSingleResult();
112
+ if (rootNode) {
113
+ // Root directory already exists
114
+ return;
115
+ }
116
+ // Create root directory (parent is null, filename is empty string)
117
+ const now = new Date();
118
+ const rootDir = em.create(FileNodeMetaEntity, {
119
+ filename: '',
120
+ parent: null,
121
+ kind: FileKind.directory,
122
+ size: 0,
123
+ atime: now,
124
+ btime: now,
125
+ ctime: now,
126
+ mtime: now,
127
+ });
128
+ try {
129
+ await em.persistAndFlush(rootDir);
130
+ } catch (error) {
131
+ // If root already exists (race condition), ignore the error
132
+ if (!error.message?.includes('UNIQUE constraint') && !error.message?.includes('duplicate')) {
133
+ throw error;
134
+ }
135
+ }
136
+ return;
137
+ }
138
+ const parentPath = dirname(normalized);
139
+ const newDirName = basename(normalized);
140
+ if (!newDirName) throw new FileSystemError('Cannot create directory with empty name', FileSystemErrorCode.EINVAL);
141
+ let parentNode = await this._getNodeByPath(parentPath, em);
142
+ if (!parentNode) {
143
+ if (options.recursive) {
144
+ // 递归创建父目录
145
+ await this._mkdirInTransaction(parentPath, options, em);
146
+ parentNode = await this._getNodeByPath(parentPath, em);
147
+ } else {
148
+ throw new FileSystemError(`Parent directory not found: ${parentPath}`, FileSystemErrorCode.ENOENT);
149
+ }
150
+ }
151
+ if (!parentNode)
152
+ throw new FileSystemError('Failed to create parent directory structure', FileSystemErrorCode.EINVAL);
153
+ // Check if directory already exists using QueryBuilder
154
+ const existingQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
155
+ existingQb.where({
156
+ parent: parentNode,
157
+ filename: newDirName,
158
+ });
159
+ const existing = await existingQb.getSingleResult();
160
+ if (existing) {
161
+ if (existing.kind === FileKind.directory) {
162
+ // Directory already exists, return silently
163
+ return;
164
+ }
165
+ throw new FileSystemError(`A file with the same name already exists: ${path}`, FileSystemErrorCode.EEXIST);
166
+ }
167
+ // Create directory using EntityManager
168
+ const now = new Date();
169
+ const newDir = em.create(FileNodeMetaEntity, {
170
+ tid: parentNode.tid,
171
+ parent: parentNode,
172
+ filename: newDirName,
173
+ kind: FileKind.directory,
174
+ size: 0,
175
+ atime: now,
176
+ btime: now,
177
+ ctime: now,
178
+ mtime: now,
179
+ });
180
+ await em.persistAndFlush(newDir);
181
+ }
182
+ async readFile(path, options) {
183
+ const em = this.em;
184
+ const node = await this._getNodeByPath(path, em);
185
+ if (!node) throw new FileSystemError(`File not found: ${path}`, FileSystemErrorCode.ENOENT);
186
+ if (node.kind !== FileKind.file)
187
+ throw new FileSystemError(`Path is not a file: ${path}`, FileSystemErrorCode.EISDIR);
188
+ let buffer;
189
+ if (node.content) {
190
+ // 小文件优化 - content is already loaded
191
+ buffer = node.content;
192
+ } else {
193
+ // Large file: load from file_node_content table using QueryBuilder
194
+ const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
195
+ fileContentQb.where({
196
+ node: node,
197
+ });
198
+ const fileContent = await fileContentQb.getSingleResult();
199
+ if (!fileContent) throw new FileSystemError('File content is missing', FileSystemErrorCode.ENOENT);
200
+ buffer = fileContent.content;
201
+ }
202
+ return options?.encoding === 'text' ? buffer.toString('utf-8') : buffer;
203
+ }
204
+ async writeFile(path, data, options = {}) {
205
+ // Validate input
206
+ if (!path || typeof path !== 'string') {
207
+ throw new FileSystemError('Invalid path', FileSystemErrorCode.EINVAL);
208
+ }
209
+ if (data === null || data === undefined) {
210
+ throw new FileSystemError('Invalid data', FileSystemErrorCode.EINVAL);
211
+ }
212
+ await this.em.transactional(async (em) => {
213
+ const { overwrite = true } = options;
214
+ const bufferData = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf-8');
215
+ const size = bufferData.length;
216
+ const parentPath = dirname(path);
217
+ const filename = basename(path);
218
+ // Validate filename
219
+ if (!filename) {
220
+ throw new FileSystemError('filename cannot be empty', FileSystemErrorCode.EINVAL);
221
+ }
222
+ // 确保父目录存在 - create it within the transaction
223
+ await this._mkdirInTransaction(
224
+ parentPath,
225
+ {
226
+ recursive: true,
227
+ },
228
+ em,
229
+ );
230
+ const parentNode = await this._getNodeByPath(parentPath, em);
231
+ if (!parentNode) throw new FileSystemError('Failed to establish parent directory', FileSystemErrorCode.EINVAL);
232
+ // Find existing node using QueryBuilder
233
+ const nodeQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
234
+ nodeQb.where({
235
+ parent: parentNode,
236
+ filename,
237
+ });
238
+ let node = await nodeQb.getSingleResult();
239
+ if (node) {
240
+ // 文件已存在
241
+ if (!overwrite) throw new FileSystemError(`File already exists: ${path}`, FileSystemErrorCode.EEXIST);
242
+ if (node.kind === FileKind.directory)
243
+ throw new FileSystemError(`Cannot overwrite a directory with a file: ${path}`, FileSystemErrorCode.EISDIR);
244
+ // 更新节点
245
+ node.size = size;
246
+ node.mtime = new Date();
247
+ // ... 其他时间戳
248
+ } else {
249
+ // 新建文件
250
+ const now = new Date();
251
+ node = em.create(FileNodeMetaEntity, {
252
+ tid: parentNode.tid,
253
+ filename,
254
+ parent: parentNode,
255
+ kind: FileKind.file,
256
+ size,
257
+ atime: now,
258
+ btime: now,
259
+ ctime: now,
260
+ mtime: now,
261
+ });
262
+ }
263
+ // 处理文件内容
264
+ if (size <= this.options.smallFileThreshold) {
265
+ // Small file: store in file_node_meta.content
266
+ node.content = bufferData;
267
+ // If there was large file content, delete it
268
+ // Use QueryBuilder to avoid relationship issues
269
+ const existingContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
270
+ existingContentQb.where({
271
+ node: node,
272
+ });
273
+ const existingContent = await existingContentQb.getSingleResult();
274
+ if (existingContent) {
275
+ await em.removeAndFlush(existingContent);
276
+ }
277
+ } else {
278
+ // Large file: store in file_node_content table
279
+ node.content = undefined; // Clear small file content
280
+ const md5 = crypto.createHash('md5').update(bufferData).digest('hex');
281
+ const sha256 = crypto.createHash('sha256').update(bufferData).digest('hex');
282
+ // Check if fileContent already exists using QueryBuilder
283
+ const fileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
284
+ fileContentQb.where({
285
+ node: node,
286
+ });
287
+ let fileContent = await fileContentQb.getSingleResult();
288
+ if (fileContent) {
289
+ // Update existing content
290
+ fileContent.content = bufferData;
291
+ fileContent.size = size;
292
+ fileContent.md5 = md5;
293
+ fileContent.sha256 = sha256;
294
+ } else {
295
+ // Create new content entity
296
+ fileContent = em.create(FileNodeContentEntity, {
297
+ node: node,
298
+ tid: node.tid,
299
+ content: bufferData,
300
+ size: size,
301
+ md5: md5,
302
+ sha256: sha256,
303
+ });
304
+ }
305
+ }
306
+ await em.persistAndFlush(node);
307
+ });
308
+ }
309
+ async rm(path, options = {}) {
310
+ await this.em.transactional(async (em) => {
311
+ const node = await this._getNodeByPath(path, em);
312
+ if (!node) {
313
+ if (options.force) return; // force=true, 不存在也算成功
314
+ throw new FileSystemError(`Path not found: ${path}`, FileSystemErrorCode.ENOENT);
315
+ }
316
+ if (node.kind === FileKind.directory && !options.recursive) {
317
+ // Check if directory has children using QueryBuilder
318
+ const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
319
+ childrenQb.where({
320
+ parent: node,
321
+ });
322
+ childrenQb.select('id');
323
+ const children = await childrenQb.getResult();
324
+ if (children.length > 0) {
325
+ throw new FileSystemError(`Directory not empty: ${path}`, FileSystemErrorCode.ENOTEMPTY);
326
+ }
327
+ }
328
+ // 使用 orphanRemoval: true, ORM会自动处理子节点和内容的删除
329
+ await em.removeAndFlush(node);
330
+ });
331
+ }
332
+ async rename(oldPath, newPath, options = {}) {
333
+ await this.em.transactional(async (em) => {
334
+ const node = await this._getNodeByPath(oldPath, em);
335
+ if (!node) throw new FileSystemError(`Source path not found: ${oldPath}`, FileSystemErrorCode.ENOENT);
336
+ const newParentPath = dirname(newPath);
337
+ const newFilename = basename(newPath);
338
+ const newParentNode = await this._getNodeByPath(newParentPath, em);
339
+ if (!newParentNode)
340
+ throw new FileSystemError(`Destination directory not found: ${newParentPath}`, FileSystemErrorCode.ENOENT);
341
+ const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
342
+ existingDestQb.where({
343
+ parent: newParentNode,
344
+ filename: newFilename,
345
+ });
346
+ const existingDest = await existingDestQb.getSingleResult();
347
+ if (existingDest) {
348
+ if (!options.overwrite)
349
+ throw new FileSystemError(`Destination path already exists: ${newPath}`, FileSystemErrorCode.EEXIST);
350
+ if (node.id === existingDest.id) return; // 移动到原位置,什么都不做
351
+ await em.removeAndFlush(existingDest);
352
+ }
353
+ node.parent = newParentNode;
354
+ node.filename = newFilename;
355
+ node.mtime = new Date();
356
+ await em.flush();
357
+ });
358
+ }
359
+ async copy(srcPath, destPath, options = {}) {
360
+ await this.em.transactional(async (em) => {
361
+ const srcNode = await this._getNodeByPath(srcPath, em);
362
+ if (!srcNode) throw new FileSystemError(`Source path not found: ${srcPath}`, FileSystemErrorCode.ENOENT);
363
+ const destParentPath = dirname(destPath);
364
+ const destFilename = basename(destPath);
365
+ const destParentNode = await this._getNodeByPath(destParentPath, em);
366
+ if (!destParentNode)
367
+ throw new FileSystemError(`Destination directory not found: ${destParentPath}`, FileSystemErrorCode.ENOENT);
368
+ const existingDestQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
369
+ existingDestQb.where({
370
+ parent: destParentNode,
371
+ filename: destFilename,
372
+ });
373
+ const existingDest = await existingDestQb.getSingleResult();
374
+ if (existingDest) {
375
+ if (!options.overwrite)
376
+ throw new FileSystemError(`Destination path already exists: ${destPath}`, FileSystemErrorCode.EEXIST);
377
+ // Delete existing destination within the same transaction
378
+ await em.removeAndFlush(existingDest);
379
+ }
380
+ await this._copyNode(srcNode, destParentNode, destFilename, em);
381
+ });
382
+ }
383
+ createReadStream(_path, _options) {
384
+ throw new Error('Streaming read is not supported by DBFS yet.');
385
+ }
386
+ createWriteStream(_path, _options) {
387
+ throw new Error('Streaming write is not supported by DBFS yet.');
388
+ }
389
+ createReadableStream(_path, _options) {
390
+ throw new Error('ReadableStream is not supported by DBFS yet.');
391
+ }
392
+ createWritableStream(_path, _options) {
393
+ throw new Error('WritableStream is not supported by DBFS yet.');
394
+ }
395
+ /**
396
+ * 将路径字符串解析为数据库中的节点。这是大部分操作的基础。
397
+ * @param pathStr 绝对路径, e.g., /home/user/file.txt
398
+ * @param em EntityManager 实例
399
+ * @returns 找到的节点或 null
400
+ */ async _getNodeByPath(pathStr, em) {
401
+ const normalized = normalize(pathStr);
402
+ if (normalized === '/') {
403
+ // Use QueryBuilder to avoid automatic relationship loading
404
+ const qb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
405
+ qb.where({
406
+ parent: null,
407
+ });
408
+ const rootNode = await qb.getSingleResult();
409
+ return rootNode || null;
410
+ }
411
+ const parts = normalized.split('/').filter((p) => p);
412
+ // Get root node
413
+ const rootQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
414
+ rootQb.where({
415
+ parent: null,
416
+ });
417
+ let currentNode = await rootQb.getSingleResult();
418
+ if (!currentNode) return null;
419
+ // Traverse path parts
420
+ for (const part of parts) {
421
+ const childQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
422
+ childQb.where({
423
+ parent: currentNode,
424
+ filename: part,
425
+ });
426
+ const child = await childQb.getSingleResult();
427
+ if (!child) return null;
428
+ currentNode = child;
429
+ }
430
+ return currentNode;
431
+ }
432
+ /**
433
+ * 将数据库实体转换为 IFileStat 接口
434
+ */ _toFileStat(node, path) {
435
+ // Handle mtime - it might be a Date object or a string from raw query
436
+ let mtime;
437
+ if (node.mtime instanceof Date) {
438
+ mtime = node.mtime.getTime();
439
+ } else if (typeof node.mtime === 'string') {
440
+ mtime = new Date(node.mtime).getTime();
441
+ } else {
442
+ mtime = Date.now();
443
+ }
444
+ // Handle size - convert bigint to number if needed
445
+ let size;
446
+ if (typeof node.size === 'bigint') {
447
+ size = Number(node.size);
448
+ } else {
449
+ size = node.size;
450
+ }
451
+ return {
452
+ path: path,
453
+ name: node.filename,
454
+ kind: node.kind,
455
+ size: size,
456
+ mtime: mtime,
457
+ meta: node.metadata || {},
458
+ // `directory` 字段可以根据 path 动态计算
459
+ directory: dirname(path),
460
+ };
461
+ }
462
+ async _copyNode(srcNode, destParent, newName, em) {
463
+ // 1. 复制节点本身
464
+ const now = new Date();
465
+ const newNode = em.create(FileNodeMetaEntity, {
466
+ tid: srcNode.tid,
467
+ filename: newName,
468
+ parent: destParent,
469
+ kind: srcNode.kind,
470
+ size: srcNode.size,
471
+ metadata: srcNode.metadata,
472
+ content: srcNode.content,
473
+ atime: now,
474
+ btime: now,
475
+ ctime: now,
476
+ mtime: now,
477
+ });
478
+ // 2. 复制大文件内容 (如果存在) - use QueryBuilder to avoid relationship loading
479
+ if (!srcNode.content) {
480
+ const srcFileContentQb = em.createQueryBuilder(FileNodeContentEntity, 'fc');
481
+ srcFileContentQb.where({
482
+ node: srcNode,
483
+ });
484
+ const srcFileContent = await srcFileContentQb.getSingleResult();
485
+ if (srcFileContent) {
486
+ const _newContent = em.create(FileNodeContentEntity, {
487
+ node: newNode,
488
+ tid: srcNode.tid,
489
+ content: srcFileContent.content,
490
+ size: srcFileContent.size,
491
+ md5: srcFileContent.md5,
492
+ sha256: srcFileContent.sha256,
493
+ mimeType: srcFileContent.mimeType,
494
+ metadata: srcFileContent.metadata,
495
+ });
496
+ }
497
+ }
498
+ await em.persistAndFlush(newNode);
499
+ // 3. 如果是目录,递归复制子节点
500
+ if (srcNode.kind === FileKind.directory) {
501
+ const childrenQb = em.createQueryBuilder(FileNodeMetaEntity, 'f');
502
+ childrenQb.where({
503
+ parent: srcNode,
504
+ });
505
+ const children = await childrenQb.getResult();
506
+ for (const child of children) {
507
+ await this._copyNode(child, newNode, child.filename, em);
508
+ }
509
+ }
510
+ }
528
511
  };
529
512
  export class FileNodeMetaEntity extends TenantBaseEntity {
530
- filename;
531
- size;
532
- kind;
533
- atime;
534
- btime;
535
- ctime;
536
- mtime;
537
- metadata;
538
- parent;
539
- children = new Collection(this);
540
- fileContent;
541
- content;
542
- //region content
543
- get parentId() {
544
- return this.parent?.id;
545
- }
513
+ filename;
514
+ size;
515
+ kind;
516
+ atime;
517
+ btime;
518
+ ctime;
519
+ mtime;
520
+ metadata;
521
+ parent;
522
+ children = new Collection(this);
523
+ fileContent;
524
+ content;
525
+ //region content
526
+ get parentId() {
527
+ return this.parent?.id;
528
+ }
546
529
  }
547
- _ts_decorate([
548
- Property({
549
- type: types.string,
550
- nullable: false,
551
- comment: "\u6587\u4EF6\u540D"
552
- }),
553
- _ts_metadata("design:type", String)
554
- ], FileNodeMetaEntity.prototype, "filename", void 0);
555
- _ts_decorate([
556
- Property({
557
- type: types.bigint,
558
- nullable: false,
559
- default: 0,
560
- comment: "\u6587\u4EF6\u5927\u5C0F"
561
- }),
562
- _ts_metadata("design:type", Object)
563
- ], FileNodeMetaEntity.prototype, "size", void 0);
564
- _ts_decorate([
565
- Property({
566
- type: types.string,
567
- nullable: false,
568
- comment: "\u6587\u4EF6\u7C7B\u578B"
569
- }),
570
- _ts_metadata("design:type", typeof FileKind === "undefined" ? Object : FileKind)
571
- ], FileNodeMetaEntity.prototype, "kind", void 0);
572
- _ts_decorate([
573
- Property({
574
- type: types.datetime,
575
- nullable: false,
576
- defaultRaw: "CURRENT_TIMESTAMP"
577
- }),
578
- _ts_metadata("design:type", Object)
579
- ], FileNodeMetaEntity.prototype, "atime", void 0);
580
- _ts_decorate([
581
- Property({
582
- type: types.datetime,
583
- nullable: false,
584
- defaultRaw: "CURRENT_TIMESTAMP"
585
- }),
586
- _ts_metadata("design:type", Object)
587
- ], FileNodeMetaEntity.prototype, "btime", void 0);
588
- _ts_decorate([
589
- Property({
590
- type: types.datetime,
591
- nullable: false,
592
- defaultRaw: "CURRENT_TIMESTAMP"
593
- }),
594
- _ts_metadata("design:type", Object)
595
- ], FileNodeMetaEntity.prototype, "ctime", void 0);
596
- _ts_decorate([
597
- Property({
598
- type: types.datetime,
599
- nullable: false,
600
- defaultRaw: "CURRENT_TIMESTAMP"
601
- }),
602
- _ts_metadata("design:type", Object)
603
- ], FileNodeMetaEntity.prototype, "mtime", void 0);
604
- _ts_decorate([
605
- Property({
606
- type: types.json,
607
- nullable: false,
608
- defaultRaw: "{}"
609
- }),
610
- _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
611
- ], FileNodeMetaEntity.prototype, "metadata", void 0);
612
- _ts_decorate([
613
- ManyToOne(() => FileNodeMetaEntity, {
614
- nullable: true,
615
- cascade: []
616
- }),
617
- _ts_metadata("design:type", typeof Rel === "undefined" ? Object : Rel)
618
- ], FileNodeMetaEntity.prototype, "parent", void 0);
619
- _ts_decorate([
620
- OneToMany({
621
- entity: () => FileNodeMetaEntity,
622
- mappedBy: "parent",
623
- orphanRemoval: true
624
- })
625
- ], FileNodeMetaEntity.prototype, "children", void 0);
626
- _ts_decorate([
627
- OneToOne({
628
- entity: () => FileNodeContentEntity,
629
- mappedBy: "node",
630
- orphanRemoval: true,
631
- nullable: true,
632
- cascade: [
633
- Cascade.ALL
634
- ]
635
- }),
636
- _ts_metadata("design:type", typeof Rel === "undefined" ? Object : Rel)
637
- ], FileNodeMetaEntity.prototype, "fileContent", void 0);
638
- _ts_decorate([
639
- Property({
640
- type: types.blob,
641
- nullable: true,
642
- comment: "\u6587\u4EF6\u5185\u5BB9"
643
- }),
644
- _ts_metadata("design:type", typeof Buffer === "undefined" ? Object : Buffer)
645
- ], FileNodeMetaEntity.prototype, "content", void 0);
646
- FileNodeMetaEntity = _ts_decorate([
647
- Entity({
648
- tableName: "file_node_meta"
649
- }),
650
- Unique({
651
- properties: [
652
- "tid",
653
- "parent",
654
- "filename"
655
- ]
656
- })
657
- ], FileNodeMetaEntity);
530
+ _ts_decorate(
531
+ [
532
+ Property({
533
+ type: types.string,
534
+ nullable: false,
535
+ comment: '\u6587\u4EF6\u540D',
536
+ }),
537
+ _ts_metadata('design:type', String),
538
+ ],
539
+ FileNodeMetaEntity.prototype,
540
+ 'filename',
541
+ void 0,
542
+ );
543
+ _ts_decorate(
544
+ [
545
+ Property({
546
+ type: types.bigint,
547
+ nullable: false,
548
+ default: 0,
549
+ comment: '\u6587\u4EF6\u5927\u5C0F',
550
+ }),
551
+ _ts_metadata('design:type', Object),
552
+ ],
553
+ FileNodeMetaEntity.prototype,
554
+ 'size',
555
+ void 0,
556
+ );
557
+ _ts_decorate(
558
+ [
559
+ Property({
560
+ type: types.string,
561
+ nullable: false,
562
+ comment: '\u6587\u4EF6\u7C7B\u578B',
563
+ }),
564
+ _ts_metadata('design:type', typeof FileKind === 'undefined' ? Object : FileKind),
565
+ ],
566
+ FileNodeMetaEntity.prototype,
567
+ 'kind',
568
+ void 0,
569
+ );
570
+ _ts_decorate(
571
+ [
572
+ Property({
573
+ type: types.datetime,
574
+ nullable: false,
575
+ defaultRaw: 'CURRENT_TIMESTAMP',
576
+ }),
577
+ _ts_metadata('design:type', Object),
578
+ ],
579
+ FileNodeMetaEntity.prototype,
580
+ 'atime',
581
+ void 0,
582
+ );
583
+ _ts_decorate(
584
+ [
585
+ Property({
586
+ type: types.datetime,
587
+ nullable: false,
588
+ defaultRaw: 'CURRENT_TIMESTAMP',
589
+ }),
590
+ _ts_metadata('design:type', Object),
591
+ ],
592
+ FileNodeMetaEntity.prototype,
593
+ 'btime',
594
+ void 0,
595
+ );
596
+ _ts_decorate(
597
+ [
598
+ Property({
599
+ type: types.datetime,
600
+ nullable: false,
601
+ defaultRaw: 'CURRENT_TIMESTAMP',
602
+ }),
603
+ _ts_metadata('design:type', Object),
604
+ ],
605
+ FileNodeMetaEntity.prototype,
606
+ 'ctime',
607
+ void 0,
608
+ );
609
+ _ts_decorate(
610
+ [
611
+ Property({
612
+ type: types.datetime,
613
+ nullable: false,
614
+ defaultRaw: 'CURRENT_TIMESTAMP',
615
+ }),
616
+ _ts_metadata('design:type', Object),
617
+ ],
618
+ FileNodeMetaEntity.prototype,
619
+ 'mtime',
620
+ void 0,
621
+ );
622
+ _ts_decorate(
623
+ [
624
+ Property({
625
+ type: types.json,
626
+ nullable: false,
627
+ defaultRaw: '{}',
628
+ }),
629
+ _ts_metadata('design:type', typeof Record === 'undefined' ? Object : Record),
630
+ ],
631
+ FileNodeMetaEntity.prototype,
632
+ 'metadata',
633
+ void 0,
634
+ );
635
+ _ts_decorate(
636
+ [
637
+ ManyToOne(() => FileNodeMetaEntity, {
638
+ nullable: true,
639
+ cascade: [],
640
+ }),
641
+ _ts_metadata('design:type', typeof Rel === 'undefined' ? Object : Rel),
642
+ ],
643
+ FileNodeMetaEntity.prototype,
644
+ 'parent',
645
+ void 0,
646
+ );
647
+ _ts_decorate(
648
+ [
649
+ OneToMany({
650
+ entity: () => FileNodeMetaEntity,
651
+ mappedBy: 'parent',
652
+ orphanRemoval: true,
653
+ }),
654
+ ],
655
+ FileNodeMetaEntity.prototype,
656
+ 'children',
657
+ void 0,
658
+ );
659
+ _ts_decorate(
660
+ [
661
+ OneToOne({
662
+ entity: () => FileNodeContentEntity,
663
+ mappedBy: 'node',
664
+ orphanRemoval: true,
665
+ nullable: true,
666
+ cascade: [Cascade.ALL],
667
+ }),
668
+ _ts_metadata('design:type', typeof Rel === 'undefined' ? Object : Rel),
669
+ ],
670
+ FileNodeMetaEntity.prototype,
671
+ 'fileContent',
672
+ void 0,
673
+ );
674
+ _ts_decorate(
675
+ [
676
+ Property({
677
+ type: types.blob,
678
+ nullable: true,
679
+ comment: '\u6587\u4EF6\u5185\u5BB9',
680
+ }),
681
+ _ts_metadata('design:type', typeof Buffer === 'undefined' ? Object : Buffer),
682
+ ],
683
+ FileNodeMetaEntity.prototype,
684
+ 'content',
685
+ void 0,
686
+ );
687
+ FileNodeMetaEntity = _ts_decorate(
688
+ [
689
+ Entity({
690
+ tableName: 'file_node_meta',
691
+ }),
692
+ Unique({
693
+ properties: ['tid', 'parent', 'filename'],
694
+ }),
695
+ ],
696
+ FileNodeMetaEntity,
697
+ );
658
698
  export class FileNodeContentEntity extends TenantBaseEntity {
659
- node;
660
- size;
661
- content;
662
- mimeType;
663
- md5;
664
- sha256;
665
- text;
666
- width;
667
- height;
668
- // @Property({ type: types.integer, nullable: true })
669
- // length?: number;
670
- metadata;
699
+ node;
700
+ size;
701
+ content;
702
+ mimeType;
703
+ md5;
704
+ sha256;
705
+ text;
706
+ width;
707
+ height;
708
+ // @Property({ type: types.integer, nullable: true })
709
+ // length?: number;
710
+ metadata;
671
711
  }
672
- _ts_decorate([
673
- OneToOne({
674
- entity: () => FileNodeMetaEntity,
675
- owner: true,
676
- joinColumn: "node_id"
677
- }),
678
- _ts_metadata("design:type", typeof Rel === "undefined" ? Object : Rel)
679
- ], FileNodeContentEntity.prototype, "node", void 0);
680
- _ts_decorate([
681
- Property({
682
- type: types.integer,
683
- nullable: false
684
- }),
685
- _ts_metadata("design:type", Number)
686
- ], FileNodeContentEntity.prototype, "size", void 0);
687
- _ts_decorate([
688
- Property({
689
- type: types.blob,
690
- lazy: true,
691
- comment: "\u6587\u4EF6\u5185\u5BB9"
692
- }),
693
- _ts_metadata("design:type", typeof Buffer === "undefined" ? Object : Buffer)
694
- ], FileNodeContentEntity.prototype, "content", void 0);
695
- _ts_decorate([
696
- Property({
697
- type: types.string,
698
- nullable: true
699
- }),
700
- _ts_metadata("design:type", String)
701
- ], FileNodeContentEntity.prototype, "mimeType", void 0);
702
- _ts_decorate([
703
- Property({
704
- type: types.string,
705
- nullable: false
706
- }),
707
- _ts_metadata("design:type", String)
708
- ], FileNodeContentEntity.prototype, "md5", void 0);
709
- _ts_decorate([
710
- Property({
711
- type: types.string,
712
- nullable: false
713
- }),
714
- _ts_metadata("design:type", String)
715
- ], FileNodeContentEntity.prototype, "sha256", void 0);
716
- _ts_decorate([
717
- Property({
718
- type: types.string,
719
- nullable: true
720
- }),
721
- _ts_metadata("design:type", String)
722
- ], FileNodeContentEntity.prototype, "text", void 0);
723
- _ts_decorate([
724
- Property({
725
- type: types.integer,
726
- nullable: true
727
- }),
728
- _ts_metadata("design:type", Number)
729
- ], FileNodeContentEntity.prototype, "width", void 0);
730
- _ts_decorate([
731
- Property({
732
- type: types.integer,
733
- nullable: true
734
- }),
735
- _ts_metadata("design:type", Number)
736
- ], FileNodeContentEntity.prototype, "height", void 0);
737
- _ts_decorate([
738
- Property({
739
- type: types.json,
740
- nullable: false,
741
- defaultRaw: "{}"
742
- }),
743
- _ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
744
- ], FileNodeContentEntity.prototype, "metadata", void 0);
745
- FileNodeContentEntity = _ts_decorate([
746
- Entity({
747
- tableName: "file_node_content"
748
- })
749
- ], FileNodeContentEntity);
712
+ _ts_decorate(
713
+ [
714
+ OneToOne({
715
+ entity: () => FileNodeMetaEntity,
716
+ owner: true,
717
+ joinColumn: 'node_id',
718
+ }),
719
+ _ts_metadata('design:type', typeof Rel === 'undefined' ? Object : Rel),
720
+ ],
721
+ FileNodeContentEntity.prototype,
722
+ 'node',
723
+ void 0,
724
+ );
725
+ _ts_decorate(
726
+ [
727
+ Property({
728
+ type: types.integer,
729
+ nullable: false,
730
+ }),
731
+ _ts_metadata('design:type', Number),
732
+ ],
733
+ FileNodeContentEntity.prototype,
734
+ 'size',
735
+ void 0,
736
+ );
737
+ _ts_decorate(
738
+ [
739
+ Property({
740
+ type: types.blob,
741
+ lazy: true,
742
+ comment: '\u6587\u4EF6\u5185\u5BB9',
743
+ }),
744
+ _ts_metadata('design:type', typeof Buffer === 'undefined' ? Object : Buffer),
745
+ ],
746
+ FileNodeContentEntity.prototype,
747
+ 'content',
748
+ void 0,
749
+ );
750
+ _ts_decorate(
751
+ [
752
+ Property({
753
+ type: types.string,
754
+ nullable: true,
755
+ }),
756
+ _ts_metadata('design:type', String),
757
+ ],
758
+ FileNodeContentEntity.prototype,
759
+ 'mimeType',
760
+ void 0,
761
+ );
762
+ _ts_decorate(
763
+ [
764
+ Property({
765
+ type: types.string,
766
+ nullable: false,
767
+ }),
768
+ _ts_metadata('design:type', String),
769
+ ],
770
+ FileNodeContentEntity.prototype,
771
+ 'md5',
772
+ void 0,
773
+ );
774
+ _ts_decorate(
775
+ [
776
+ Property({
777
+ type: types.string,
778
+ nullable: false,
779
+ }),
780
+ _ts_metadata('design:type', String),
781
+ ],
782
+ FileNodeContentEntity.prototype,
783
+ 'sha256',
784
+ void 0,
785
+ );
786
+ _ts_decorate(
787
+ [
788
+ Property({
789
+ type: types.string,
790
+ nullable: true,
791
+ }),
792
+ _ts_metadata('design:type', String),
793
+ ],
794
+ FileNodeContentEntity.prototype,
795
+ 'text',
796
+ void 0,
797
+ );
798
+ _ts_decorate(
799
+ [
800
+ Property({
801
+ type: types.integer,
802
+ nullable: true,
803
+ }),
804
+ _ts_metadata('design:type', Number),
805
+ ],
806
+ FileNodeContentEntity.prototype,
807
+ 'width',
808
+ void 0,
809
+ );
810
+ _ts_decorate(
811
+ [
812
+ Property({
813
+ type: types.integer,
814
+ nullable: true,
815
+ }),
816
+ _ts_metadata('design:type', Number),
817
+ ],
818
+ FileNodeContentEntity.prototype,
819
+ 'height',
820
+ void 0,
821
+ );
822
+ _ts_decorate(
823
+ [
824
+ Property({
825
+ type: types.json,
826
+ nullable: false,
827
+ defaultRaw: '{}',
828
+ }),
829
+ _ts_metadata('design:type', typeof Record === 'undefined' ? Object : Record),
830
+ ],
831
+ FileNodeContentEntity.prototype,
832
+ 'metadata',
833
+ void 0,
834
+ );
835
+ FileNodeContentEntity = _ts_decorate(
836
+ [
837
+ Entity({
838
+ tableName: 'file_node_content',
839
+ }),
840
+ ],
841
+ FileNodeContentEntity,
842
+ );
750
843
  //# sourceMappingURL=createDatabaseFileSystem.js.map