@trokky/trokky 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 (639) hide show
  1. package/dist/adapters/filesystem/filesystem-adapter.d.ts +80 -0
  2. package/dist/adapters/filesystem/filesystem-adapter.d.ts.map +1 -0
  3. package/dist/adapters/filesystem/filesystem-adapter.js +1370 -0
  4. package/dist/adapters/filesystem/filesystem-adapter.js.map +1 -0
  5. package/dist/adapters/filesystem/index.d.ts +29 -0
  6. package/dist/adapters/filesystem/index.d.ts.map +1 -0
  7. package/dist/adapters/filesystem/index.js +31 -0
  8. package/dist/adapters/filesystem/index.js.map +1 -0
  9. package/dist/adapters/filesystem/types.d.ts +111 -0
  10. package/dist/adapters/filesystem/types.d.ts.map +1 -0
  11. package/dist/adapters/filesystem/types.js +2 -0
  12. package/dist/adapters/filesystem/types.js.map +1 -0
  13. package/dist/adapters/filesystem-data/filesystem-data-adapter.d.ts +111 -0
  14. package/dist/adapters/filesystem-data/filesystem-data-adapter.d.ts.map +1 -0
  15. package/dist/adapters/filesystem-data/filesystem-data-adapter.js +1363 -0
  16. package/dist/adapters/filesystem-data/filesystem-data-adapter.js.map +1 -0
  17. package/dist/adapters/filesystem-data/index.d.ts +3 -0
  18. package/dist/adapters/filesystem-data/index.d.ts.map +1 -0
  19. package/dist/adapters/filesystem-data/index.js +13 -0
  20. package/dist/adapters/filesystem-data/index.js.map +1 -0
  21. package/dist/adapters/filesystem-data/types.d.ts +147 -0
  22. package/dist/adapters/filesystem-data/types.d.ts.map +1 -0
  23. package/dist/adapters/filesystem-data/types.js +2 -0
  24. package/dist/adapters/filesystem-data/types.js.map +1 -0
  25. package/dist/adapters/filesystem-media/filesystem-media-adapter.d.ts +32 -0
  26. package/dist/adapters/filesystem-media/filesystem-media-adapter.d.ts.map +1 -0
  27. package/dist/adapters/filesystem-media/filesystem-media-adapter.js +639 -0
  28. package/dist/adapters/filesystem-media/filesystem-media-adapter.js.map +1 -0
  29. package/dist/adapters/filesystem-media/index.d.ts +3 -0
  30. package/dist/adapters/filesystem-media/index.d.ts.map +1 -0
  31. package/dist/adapters/filesystem-media/index.js +13 -0
  32. package/dist/adapters/filesystem-media/index.js.map +1 -0
  33. package/dist/adapters/filesystem-media/types.d.ts +76 -0
  34. package/dist/adapters/filesystem-media/types.d.ts.map +1 -0
  35. package/dist/adapters/filesystem-media/types.js +2 -0
  36. package/dist/adapters/filesystem-media/types.js.map +1 -0
  37. package/dist/adapters/postgres-data/index.d.ts +3 -0
  38. package/dist/adapters/postgres-data/index.d.ts.map +1 -0
  39. package/dist/adapters/postgres-data/index.js +13 -0
  40. package/dist/adapters/postgres-data/index.js.map +1 -0
  41. package/dist/adapters/postgres-data/postgres-data-adapter.d.ts +154 -0
  42. package/dist/adapters/postgres-data/postgres-data-adapter.d.ts.map +1 -0
  43. package/dist/adapters/postgres-data/postgres-data-adapter.js +1432 -0
  44. package/dist/adapters/postgres-data/postgres-data-adapter.js.map +1 -0
  45. package/dist/adapters/postgres-data/types.d.ts +139 -0
  46. package/dist/adapters/postgres-data/types.d.ts.map +1 -0
  47. package/dist/adapters/postgres-data/types.js +2 -0
  48. package/dist/adapters/postgres-data/types.js.map +1 -0
  49. package/dist/core/adapters/registry.d.ts +79 -0
  50. package/dist/core/adapters/registry.d.ts.map +1 -0
  51. package/dist/core/adapters/registry.js +149 -0
  52. package/dist/core/adapters/registry.js.map +1 -0
  53. package/dist/core/core/engine.d.ts +551 -0
  54. package/dist/core/core/engine.d.ts.map +1 -0
  55. package/dist/core/core/engine.js +1074 -0
  56. package/dist/core/core/engine.js.map +1 -0
  57. package/dist/core/crypto/adapter.d.ts +63 -0
  58. package/dist/core/crypto/adapter.d.ts.map +1 -0
  59. package/dist/core/crypto/adapter.js +63 -0
  60. package/dist/core/crypto/adapter.js.map +1 -0
  61. package/dist/core/crypto/fallback-adapter.d.ts +23 -0
  62. package/dist/core/crypto/fallback-adapter.d.ts.map +1 -0
  63. package/dist/core/crypto/fallback-adapter.js +195 -0
  64. package/dist/core/crypto/fallback-adapter.js.map +1 -0
  65. package/dist/core/crypto/node-adapter.d.ts +20 -0
  66. package/dist/core/crypto/node-adapter.d.ts.map +1 -0
  67. package/dist/core/crypto/node-adapter.js +85 -0
  68. package/dist/core/crypto/node-adapter.js.map +1 -0
  69. package/dist/core/crypto/password-hash.d.ts +73 -0
  70. package/dist/core/crypto/password-hash.d.ts.map +1 -0
  71. package/dist/core/crypto/password-hash.js +244 -0
  72. package/dist/core/crypto/password-hash.js.map +1 -0
  73. package/dist/core/crypto/webcrypto-adapter.d.ts +24 -0
  74. package/dist/core/crypto/webcrypto-adapter.d.ts.map +1 -0
  75. package/dist/core/crypto/webcrypto-adapter.js +158 -0
  76. package/dist/core/crypto/webcrypto-adapter.js.map +1 -0
  77. package/dist/core/errors/index.d.ts +30 -0
  78. package/dist/core/errors/index.d.ts.map +1 -0
  79. package/dist/core/errors/index.js +61 -0
  80. package/dist/core/errors/index.js.map +1 -0
  81. package/dist/core/events/event-bus.d.ts +121 -0
  82. package/dist/core/events/event-bus.d.ts.map +1 -0
  83. package/dist/core/events/event-bus.js +631 -0
  84. package/dist/core/events/event-bus.js.map +1 -0
  85. package/dist/core/events/index.d.ts +17 -0
  86. package/dist/core/events/index.d.ts.map +1 -0
  87. package/dist/core/events/index.js +22 -0
  88. package/dist/core/events/index.js.map +1 -0
  89. package/dist/core/events/memory-storage.d.ts +57 -0
  90. package/dist/core/events/memory-storage.d.ts.map +1 -0
  91. package/dist/core/events/memory-storage.js +175 -0
  92. package/dist/core/events/memory-storage.js.map +1 -0
  93. package/dist/core/events/types.d.ts +434 -0
  94. package/dist/core/events/types.d.ts.map +1 -0
  95. package/dist/core/events/types.js +9 -0
  96. package/dist/core/events/types.js.map +1 -0
  97. package/dist/core/events/utils.d.ts +134 -0
  98. package/dist/core/events/utils.d.ts.map +1 -0
  99. package/dist/core/events/utils.js +333 -0
  100. package/dist/core/events/utils.js.map +1 -0
  101. package/dist/core/fields/conditional.d.ts +115 -0
  102. package/dist/core/fields/conditional.d.ts.map +1 -0
  103. package/dist/core/fields/conditional.js +296 -0
  104. package/dist/core/fields/conditional.js.map +1 -0
  105. package/dist/core/fields/field-type.d.ts +218 -0
  106. package/dist/core/fields/field-type.d.ts.map +1 -0
  107. package/dist/core/fields/field-type.js +17 -0
  108. package/dist/core/fields/field-type.js.map +1 -0
  109. package/dist/core/fields/helpers.d.ts +86 -0
  110. package/dist/core/fields/helpers.d.ts.map +1 -0
  111. package/dist/core/fields/helpers.js +260 -0
  112. package/dist/core/fields/helpers.js.map +1 -0
  113. package/dist/core/fields/index.d.ts +5 -0
  114. package/dist/core/fields/index.d.ts.map +1 -0
  115. package/dist/core/fields/index.js +6 -0
  116. package/dist/core/fields/index.js.map +1 -0
  117. package/dist/core/fields/registry.d.ts +78 -0
  118. package/dist/core/fields/registry.d.ts.map +1 -0
  119. package/dist/core/fields/registry.js +185 -0
  120. package/dist/core/fields/registry.js.map +1 -0
  121. package/dist/core/index.d.ts +39 -0
  122. package/dist/core/index.d.ts.map +1 -0
  123. package/dist/core/index.js +57 -0
  124. package/dist/core/index.js.map +1 -0
  125. package/dist/core/media/image-processor.d.ts +24 -0
  126. package/dist/core/media/image-processor.d.ts.map +1 -0
  127. package/dist/core/media/image-processor.js +69 -0
  128. package/dist/core/media/image-processor.js.map +1 -0
  129. package/dist/core/media/processors/cloudflare-images.d.ts +83 -0
  130. package/dist/core/media/processors/cloudflare-images.d.ts.map +1 -0
  131. package/dist/core/media/processors/cloudflare-images.js +241 -0
  132. package/dist/core/media/processors/cloudflare-images.js.map +1 -0
  133. package/dist/core/media/processors/cloudflare-transform-store.d.ts +127 -0
  134. package/dist/core/media/processors/cloudflare-transform-store.d.ts.map +1 -0
  135. package/dist/core/media/processors/cloudflare-transform-store.js +237 -0
  136. package/dist/core/media/processors/cloudflare-transform-store.js.map +1 -0
  137. package/dist/core/media/processors/cloudflare-transformations.d.ts +81 -0
  138. package/dist/core/media/processors/cloudflare-transformations.d.ts.map +1 -0
  139. package/dist/core/media/processors/cloudflare-transformations.js +144 -0
  140. package/dist/core/media/processors/cloudflare-transformations.js.map +1 -0
  141. package/dist/core/media/processors/index.d.ts +9 -0
  142. package/dist/core/media/processors/index.d.ts.map +1 -0
  143. package/dist/core/media/processors/index.js +13 -0
  144. package/dist/core/media/processors/index.js.map +1 -0
  145. package/dist/core/media/processors/noop.d.ts +22 -0
  146. package/dist/core/media/processors/noop.d.ts.map +1 -0
  147. package/dist/core/media/processors/noop.js +38 -0
  148. package/dist/core/media/processors/noop.js.map +1 -0
  149. package/dist/core/media/processors/sharp.d.ts +30 -0
  150. package/dist/core/media/processors/sharp.d.ts.map +1 -0
  151. package/dist/core/media/processors/sharp.js +126 -0
  152. package/dist/core/media/processors/sharp.js.map +1 -0
  153. package/dist/core/media/processors/types.d.ts +67 -0
  154. package/dist/core/media/processors/types.d.ts.map +1 -0
  155. package/dist/core/media/processors/types.js +42 -0
  156. package/dist/core/media/processors/types.js.map +1 -0
  157. package/dist/core/media/processors/workers-images.d.ts +112 -0
  158. package/dist/core/media/processors/workers-images.d.ts.map +1 -0
  159. package/dist/core/media/processors/workers-images.js +257 -0
  160. package/dist/core/media/processors/workers-images.js.map +1 -0
  161. package/dist/core/schema/registry.d.ts +41 -0
  162. package/dist/core/schema/registry.d.ts.map +1 -0
  163. package/dist/core/schema/registry.js +248 -0
  164. package/dist/core/schema/registry.js.map +1 -0
  165. package/dist/core/schema/singleton.d.ts +107 -0
  166. package/dist/core/schema/singleton.d.ts.map +1 -0
  167. package/dist/core/schema/singleton.js +180 -0
  168. package/dist/core/schema/singleton.js.map +1 -0
  169. package/dist/core/security/auth.d.ts +120 -0
  170. package/dist/core/security/auth.d.ts.map +1 -0
  171. package/dist/core/security/auth.js +274 -0
  172. package/dist/core/security/auth.js.map +1 -0
  173. package/dist/core/security/captcha/index.d.ts +21 -0
  174. package/dist/core/security/captcha/index.d.ts.map +1 -0
  175. package/dist/core/security/captcha/index.js +34 -0
  176. package/dist/core/security/captcha/index.js.map +1 -0
  177. package/dist/core/security/captcha/recaptcha.d.ts +41 -0
  178. package/dist/core/security/captcha/recaptcha.d.ts.map +1 -0
  179. package/dist/core/security/captcha/recaptcha.js +89 -0
  180. package/dist/core/security/captcha/recaptcha.js.map +1 -0
  181. package/dist/core/security/captcha/turnstile.d.ts +41 -0
  182. package/dist/core/security/captcha/turnstile.d.ts.map +1 -0
  183. package/dist/core/security/captcha/turnstile.js +90 -0
  184. package/dist/core/security/captcha/turnstile.js.map +1 -0
  185. package/dist/core/security/captcha/types.d.ts +66 -0
  186. package/dist/core/security/captcha/types.d.ts.map +1 -0
  187. package/dist/core/security/captcha/types.js +7 -0
  188. package/dist/core/security/captcha/types.js.map +1 -0
  189. package/dist/core/security/mfa/email-otp.d.ts +91 -0
  190. package/dist/core/security/mfa/email-otp.d.ts.map +1 -0
  191. package/dist/core/security/mfa/email-otp.js +264 -0
  192. package/dist/core/security/mfa/email-otp.js.map +1 -0
  193. package/dist/core/security/mfa/index.d.ts +8 -0
  194. package/dist/core/security/mfa/index.d.ts.map +1 -0
  195. package/dist/core/security/mfa/index.js +8 -0
  196. package/dist/core/security/mfa/index.js.map +1 -0
  197. package/dist/core/security/mfa/totp.d.ts +87 -0
  198. package/dist/core/security/mfa/totp.d.ts.map +1 -0
  199. package/dist/core/security/mfa/totp.js +293 -0
  200. package/dist/core/security/mfa/totp.js.map +1 -0
  201. package/dist/core/security/middleware.d.ts +102 -0
  202. package/dist/core/security/middleware.d.ts.map +1 -0
  203. package/dist/core/security/middleware.js +261 -0
  204. package/dist/core/security/middleware.js.map +1 -0
  205. package/dist/core/security/oauth/google.d.ts +76 -0
  206. package/dist/core/security/oauth/google.d.ts.map +1 -0
  207. package/dist/core/security/oauth/google.js +154 -0
  208. package/dist/core/security/oauth/google.js.map +1 -0
  209. package/dist/core/security/oauth/index.d.ts +8 -0
  210. package/dist/core/security/oauth/index.d.ts.map +1 -0
  211. package/dist/core/security/oauth/index.js +7 -0
  212. package/dist/core/security/oauth/index.js.map +1 -0
  213. package/dist/core/security/oauth2/authorization-server.d.ts +195 -0
  214. package/dist/core/security/oauth2/authorization-server.d.ts.map +1 -0
  215. package/dist/core/security/oauth2/authorization-server.js +569 -0
  216. package/dist/core/security/oauth2/authorization-server.js.map +1 -0
  217. package/dist/core/security/oauth2/index.d.ts +9 -0
  218. package/dist/core/security/oauth2/index.d.ts.map +1 -0
  219. package/dist/core/security/oauth2/index.js +9 -0
  220. package/dist/core/security/oauth2/index.js.map +1 -0
  221. package/dist/core/security/rate-limiter.d.ts +15 -0
  222. package/dist/core/security/rate-limiter.d.ts.map +1 -0
  223. package/dist/core/security/rate-limiter.js +48 -0
  224. package/dist/core/security/rate-limiter.js.map +1 -0
  225. package/dist/core/security/validation.d.ts +20 -0
  226. package/dist/core/security/validation.d.ts.map +1 -0
  227. package/dist/core/security/validation.js +273 -0
  228. package/dist/core/security/validation.js.map +1 -0
  229. package/dist/core/services/auth-service.d.ts +128 -0
  230. package/dist/core/services/auth-service.d.ts.map +1 -0
  231. package/dist/core/services/auth-service.js +530 -0
  232. package/dist/core/services/auth-service.js.map +1 -0
  233. package/dist/core/services/captcha-service.d.ts +44 -0
  234. package/dist/core/services/captcha-service.d.ts.map +1 -0
  235. package/dist/core/services/captcha-service.js +82 -0
  236. package/dist/core/services/captcha-service.js.map +1 -0
  237. package/dist/core/services/document-service.d.ts +59 -0
  238. package/dist/core/services/document-service.d.ts.map +1 -0
  239. package/dist/core/services/document-service.js +269 -0
  240. package/dist/core/services/document-service.js.map +1 -0
  241. package/dist/core/services/media-service.d.ts +40 -0
  242. package/dist/core/services/media-service.d.ts.map +1 -0
  243. package/dist/core/services/media-service.js +396 -0
  244. package/dist/core/services/media-service.js.map +1 -0
  245. package/dist/core/services/mfa-service.d.ts +123 -0
  246. package/dist/core/services/mfa-service.d.ts.map +1 -0
  247. package/dist/core/services/mfa-service.js +606 -0
  248. package/dist/core/services/mfa-service.js.map +1 -0
  249. package/dist/core/services/oauth-service.d.ts +58 -0
  250. package/dist/core/services/oauth-service.d.ts.map +1 -0
  251. package/dist/core/services/oauth-service.js +253 -0
  252. package/dist/core/services/oauth-service.js.map +1 -0
  253. package/dist/core/services/oauth2-server-service.d.ts +61 -0
  254. package/dist/core/services/oauth2-server-service.d.ts.map +1 -0
  255. package/dist/core/services/oauth2-server-service.js +259 -0
  256. package/dist/core/services/oauth2-server-service.js.map +1 -0
  257. package/dist/core/services/passkey-service.d.ts +59 -0
  258. package/dist/core/services/passkey-service.d.ts.map +1 -0
  259. package/dist/core/services/passkey-service.js +238 -0
  260. package/dist/core/services/passkey-service.js.map +1 -0
  261. package/dist/core/services/token-service.d.ts +31 -0
  262. package/dist/core/services/token-service.d.ts.map +1 -0
  263. package/dist/core/services/token-service.js +132 -0
  264. package/dist/core/services/token-service.js.map +1 -0
  265. package/dist/core/services/trusted-device-service.d.ts +39 -0
  266. package/dist/core/services/trusted-device-service.d.ts.map +1 -0
  267. package/dist/core/services/trusted-device-service.js +123 -0
  268. package/dist/core/services/trusted-device-service.js.map +1 -0
  269. package/dist/core/services/user-service.d.ts +38 -0
  270. package/dist/core/services/user-service.d.ts.map +1 -0
  271. package/dist/core/services/user-service.js +265 -0
  272. package/dist/core/services/user-service.js.map +1 -0
  273. package/dist/core/studio/integration.d.ts +54 -0
  274. package/dist/core/studio/integration.d.ts.map +1 -0
  275. package/dist/core/studio/integration.js +326 -0
  276. package/dist/core/studio/integration.js.map +1 -0
  277. package/dist/core/types/index.d.ts +282 -0
  278. package/dist/core/types/index.d.ts.map +1 -0
  279. package/dist/core/types/index.js +83 -0
  280. package/dist/core/types/index.js.map +1 -0
  281. package/dist/core/types/mail.d.ts +90 -0
  282. package/dist/core/types/mail.d.ts.map +1 -0
  283. package/dist/core/types/mail.js +7 -0
  284. package/dist/core/types/mail.js.map +1 -0
  285. package/dist/core/types/oauth2.d.ts +9 -0
  286. package/dist/core/types/oauth2.d.ts.map +1 -0
  287. package/dist/core/types/oauth2.js +7 -0
  288. package/dist/core/types/oauth2.js.map +1 -0
  289. package/dist/core/types/storage-adapters.d.ts +611 -0
  290. package/dist/core/types/storage-adapters.d.ts.map +1 -0
  291. package/dist/core/types/storage-adapters.js +14 -0
  292. package/dist/core/types/storage-adapters.js.map +1 -0
  293. package/dist/core/types/user.d.ts +10 -0
  294. package/dist/core/types/user.d.ts.map +1 -0
  295. package/dist/core/types/user.js +45 -0
  296. package/dist/core/types/user.js.map +1 -0
  297. package/dist/core/utils/id-generator.d.ts +16 -0
  298. package/dist/core/utils/id-generator.d.ts.map +1 -0
  299. package/dist/core/utils/id-generator.js +61 -0
  300. package/dist/core/utils/id-generator.js.map +1 -0
  301. package/dist/core/utils/logger.d.ts +100 -0
  302. package/dist/core/utils/logger.d.ts.map +1 -0
  303. package/dist/core/utils/logger.js +212 -0
  304. package/dist/core/utils/logger.js.map +1 -0
  305. package/dist/core/utils/reference-expander.d.ts +57 -0
  306. package/dist/core/utils/reference-expander.d.ts.map +1 -0
  307. package/dist/core/utils/reference-expander.js +192 -0
  308. package/dist/core/utils/reference-expander.js.map +1 -0
  309. package/dist/core/utils/universal-crypto.d.ts +46 -0
  310. package/dist/core/utils/universal-crypto.d.ts.map +1 -0
  311. package/dist/core/utils/universal-crypto.js +171 -0
  312. package/dist/core/utils/universal-crypto.js.map +1 -0
  313. package/dist/core/validation/validator.d.ts +11 -0
  314. package/dist/core/validation/validator.d.ts.map +1 -0
  315. package/dist/core/validation/validator.js +164 -0
  316. package/dist/core/validation/validator.js.map +1 -0
  317. package/dist/i18n/config.d.ts +4077 -0
  318. package/dist/i18n/config.d.ts.map +1 -0
  319. package/dist/i18n/config.js +116 -0
  320. package/dist/i18n/config.js.map +1 -0
  321. package/dist/i18n/hooks.d.ts +86 -0
  322. package/dist/i18n/hooks.d.ts.map +1 -0
  323. package/dist/i18n/hooks.js +122 -0
  324. package/dist/i18n/hooks.js.map +1 -0
  325. package/dist/i18n/index.d.ts +39 -0
  326. package/dist/i18n/index.d.ts.map +1 -0
  327. package/dist/i18n/index.js +40 -0
  328. package/dist/i18n/index.js.map +1 -0
  329. package/dist/i18n/locales/en/auth.json +108 -0
  330. package/dist/i18n/locales/en/common.json +100 -0
  331. package/dist/i18n/locales/en/errors.json +55 -0
  332. package/dist/i18n/locales/en/fields.json +456 -0
  333. package/dist/i18n/locales/en/studio.json +1329 -0
  334. package/dist/i18n/locales/fr/auth.json +108 -0
  335. package/dist/i18n/locales/fr/common.json +100 -0
  336. package/dist/i18n/locales/fr/errors.json +55 -0
  337. package/dist/i18n/locales/fr/fields.json +456 -0
  338. package/dist/i18n/locales/fr/studio.json +1274 -0
  339. package/dist/i18n/provider.d.ts +45 -0
  340. package/dist/i18n/provider.d.ts.map +1 -0
  341. package/dist/i18n/provider.js +49 -0
  342. package/dist/i18n/provider.js.map +1 -0
  343. package/dist/i18n/types.d.ts +8 -0
  344. package/dist/i18n/types.d.ts.map +1 -0
  345. package/dist/i18n/types.js +7 -0
  346. package/dist/i18n/types.js.map +1 -0
  347. package/dist/index.d.ts +15 -0
  348. package/dist/index.d.ts.map +1 -0
  349. package/dist/index.js +16 -0
  350. package/dist/index.js.map +1 -0
  351. package/dist/integrations/express/adapter.d.ts +33 -0
  352. package/dist/integrations/express/adapter.d.ts.map +1 -0
  353. package/dist/integrations/express/adapter.js +203 -0
  354. package/dist/integrations/express/adapter.js.map +1 -0
  355. package/dist/integrations/express/config.d.ts +599 -0
  356. package/dist/integrations/express/config.d.ts.map +1 -0
  357. package/dist/integrations/express/config.js +166 -0
  358. package/dist/integrations/express/config.js.map +1 -0
  359. package/dist/integrations/express/index.d.ts +10 -0
  360. package/dist/integrations/express/index.d.ts.map +1 -0
  361. package/dist/integrations/express/index.js +9 -0
  362. package/dist/integrations/express/index.js.map +1 -0
  363. package/dist/integrations/express/integration.d.ts +111 -0
  364. package/dist/integrations/express/integration.d.ts.map +1 -0
  365. package/dist/integrations/express/integration.js +730 -0
  366. package/dist/integrations/express/integration.js.map +1 -0
  367. package/dist/integrations/express/media-rewrite-middleware.d.ts +78 -0
  368. package/dist/integrations/express/media-rewrite-middleware.d.ts.map +1 -0
  369. package/dist/integrations/express/media-rewrite-middleware.js +211 -0
  370. package/dist/integrations/express/media-rewrite-middleware.js.map +1 -0
  371. package/dist/integrations/express/media-url-generator.d.ts +52 -0
  372. package/dist/integrations/express/media-url-generator.d.ts.map +1 -0
  373. package/dist/integrations/express/media-url-generator.js +107 -0
  374. package/dist/integrations/express/media-url-generator.js.map +1 -0
  375. package/dist/integrations/express/middleware.d.ts +25 -0
  376. package/dist/integrations/express/middleware.d.ts.map +1 -0
  377. package/dist/integrations/express/middleware.js +130 -0
  378. package/dist/integrations/express/middleware.js.map +1 -0
  379. package/dist/integrations/express/server.d.ts +99 -0
  380. package/dist/integrations/express/server.d.ts.map +1 -0
  381. package/dist/integrations/express/server.js +597 -0
  382. package/dist/integrations/express/server.js.map +1 -0
  383. package/dist/integrations/express/types.d.ts +106 -0
  384. package/dist/integrations/express/types.d.ts.map +1 -0
  385. package/dist/integrations/express/types.js +2 -0
  386. package/dist/integrations/express/types.js.map +1 -0
  387. package/dist/mail/adapters/console.d.ts +30 -0
  388. package/dist/mail/adapters/console.d.ts.map +1 -0
  389. package/dist/mail/adapters/console.js +169 -0
  390. package/dist/mail/adapters/console.js.map +1 -0
  391. package/dist/mail/adapters/resend.d.ts +18 -0
  392. package/dist/mail/adapters/resend.d.ts.map +1 -0
  393. package/dist/mail/adapters/resend.js +148 -0
  394. package/dist/mail/adapters/resend.js.map +1 -0
  395. package/dist/mail/adapters/smtp.d.ts +22 -0
  396. package/dist/mail/adapters/smtp.d.ts.map +1 -0
  397. package/dist/mail/adapters/smtp.js +180 -0
  398. package/dist/mail/adapters/smtp.js.map +1 -0
  399. package/dist/mail/index.d.ts +15 -0
  400. package/dist/mail/index.d.ts.map +1 -0
  401. package/dist/mail/index.js +13 -0
  402. package/dist/mail/index.js.map +1 -0
  403. package/dist/mail/mail-service.d.ts +180 -0
  404. package/dist/mail/mail-service.d.ts.map +1 -0
  405. package/dist/mail/mail-service.js +370 -0
  406. package/dist/mail/mail-service.js.map +1 -0
  407. package/dist/mail/notification-service.d.ts +82 -0
  408. package/dist/mail/notification-service.d.ts.map +1 -0
  409. package/dist/mail/notification-service.js +262 -0
  410. package/dist/mail/notification-service.js.map +1 -0
  411. package/dist/mail/templates/built-in-templates.d.ts +47 -0
  412. package/dist/mail/templates/built-in-templates.d.ts.map +1 -0
  413. package/dist/mail/templates/built-in-templates.js +416 -0
  414. package/dist/mail/templates/built-in-templates.js.map +1 -0
  415. package/dist/mail/templates/custom-template-renderer.d.ts +115 -0
  416. package/dist/mail/templates/custom-template-renderer.d.ts.map +1 -0
  417. package/dist/mail/templates/custom-template-renderer.js +225 -0
  418. package/dist/mail/templates/custom-template-renderer.js.map +1 -0
  419. package/dist/mail/types.d.ts +7 -0
  420. package/dist/mail/types.d.ts.map +1 -0
  421. package/dist/mail/types.js +7 -0
  422. package/dist/mail/types.js.map +1 -0
  423. package/dist/routes/auth/captcha.d.ts +33 -0
  424. package/dist/routes/auth/captcha.d.ts.map +1 -0
  425. package/dist/routes/auth/captcha.js +102 -0
  426. package/dist/routes/auth/captcha.js.map +1 -0
  427. package/dist/routes/auth/change-password.d.ts +17 -0
  428. package/dist/routes/auth/change-password.d.ts.map +1 -0
  429. package/dist/routes/auth/change-password.js +121 -0
  430. package/dist/routes/auth/change-password.js.map +1 -0
  431. package/dist/routes/auth/index.d.ts +13 -0
  432. package/dist/routes/auth/index.d.ts.map +1 -0
  433. package/dist/routes/auth/index.js +35 -0
  434. package/dist/routes/auth/index.js.map +1 -0
  435. package/dist/routes/auth/mfa.d.ts +121 -0
  436. package/dist/routes/auth/mfa.d.ts.map +1 -0
  437. package/dist/routes/auth/mfa.js +1143 -0
  438. package/dist/routes/auth/mfa.js.map +1 -0
  439. package/dist/routes/auth/oauth.d.ts +37 -0
  440. package/dist/routes/auth/oauth.d.ts.map +1 -0
  441. package/dist/routes/auth/oauth.js +420 -0
  442. package/dist/routes/auth/oauth.js.map +1 -0
  443. package/dist/routes/auth/oauth2-server.d.ts +70 -0
  444. package/dist/routes/auth/oauth2-server.d.ts.map +1 -0
  445. package/dist/routes/auth/oauth2-server.js +775 -0
  446. package/dist/routes/auth/oauth2-server.js.map +1 -0
  447. package/dist/routes/auth/passkey.d.ts +65 -0
  448. package/dist/routes/auth/passkey.d.ts.map +1 -0
  449. package/dist/routes/auth/passkey.js +910 -0
  450. package/dist/routes/auth/passkey.js.map +1 -0
  451. package/dist/routes/auth/password-reset.d.ts +23 -0
  452. package/dist/routes/auth/password-reset.d.ts.map +1 -0
  453. package/dist/routes/auth/password-reset.js +336 -0
  454. package/dist/routes/auth/password-reset.js.map +1 -0
  455. package/dist/routes/handlers/audit.d.ts +21 -0
  456. package/dist/routes/handlers/audit.d.ts.map +1 -0
  457. package/dist/routes/handlers/audit.js +124 -0
  458. package/dist/routes/handlers/audit.js.map +1 -0
  459. package/dist/routes/handlers/auth.d.ts +150 -0
  460. package/dist/routes/handlers/auth.d.ts.map +1 -0
  461. package/dist/routes/handlers/auth.js +512 -0
  462. package/dist/routes/handlers/auth.js.map +1 -0
  463. package/dist/routes/handlers/base.d.ts +34 -0
  464. package/dist/routes/handlers/base.d.ts.map +1 -0
  465. package/dist/routes/handlers/base.js +366 -0
  466. package/dist/routes/handlers/base.js.map +1 -0
  467. package/dist/routes/handlers/config.d.ts +31 -0
  468. package/dist/routes/handlers/config.d.ts.map +1 -0
  469. package/dist/routes/handlers/config.js +483 -0
  470. package/dist/routes/handlers/config.js.map +1 -0
  471. package/dist/routes/handlers/documents.d.ts +50 -0
  472. package/dist/routes/handlers/documents.d.ts.map +1 -0
  473. package/dist/routes/handlers/documents.js +708 -0
  474. package/dist/routes/handlers/documents.js.map +1 -0
  475. package/dist/routes/handlers/index.d.ts +12 -0
  476. package/dist/routes/handlers/index.d.ts.map +1 -0
  477. package/dist/routes/handlers/index.js +11 -0
  478. package/dist/routes/handlers/index.js.map +1 -0
  479. package/dist/routes/handlers/media.d.ts +24 -0
  480. package/dist/routes/handlers/media.d.ts.map +1 -0
  481. package/dist/routes/handlers/media.js +432 -0
  482. package/dist/routes/handlers/media.js.map +1 -0
  483. package/dist/routes/handlers/search.d.ts +10 -0
  484. package/dist/routes/handlers/search.d.ts.map +1 -0
  485. package/dist/routes/handlers/search.js +153 -0
  486. package/dist/routes/handlers/search.js.map +1 -0
  487. package/dist/routes/handlers/tokens.d.ts +14 -0
  488. package/dist/routes/handlers/tokens.d.ts.map +1 -0
  489. package/dist/routes/handlers/tokens.js +109 -0
  490. package/dist/routes/handlers/tokens.js.map +1 -0
  491. package/dist/routes/handlers/types.d.ts +13 -0
  492. package/dist/routes/handlers/types.d.ts.map +1 -0
  493. package/dist/routes/handlers/types.js +5 -0
  494. package/dist/routes/handlers/types.js.map +1 -0
  495. package/dist/routes/handlers/users.d.ts +16 -0
  496. package/dist/routes/handlers/users.d.ts.map +1 -0
  497. package/dist/routes/handlers/users.js +198 -0
  498. package/dist/routes/handlers/users.js.map +1 -0
  499. package/dist/routes/handlers/webhooks.d.ts +37 -0
  500. package/dist/routes/handlers/webhooks.d.ts.map +1 -0
  501. package/dist/routes/handlers/webhooks.js +343 -0
  502. package/dist/routes/handlers/webhooks.js.map +1 -0
  503. package/dist/routes/index.d.ts +5 -0
  504. package/dist/routes/index.d.ts.map +1 -0
  505. package/dist/routes/index.js +5 -0
  506. package/dist/routes/index.js.map +1 -0
  507. package/dist/routes/routes.d.ts +29 -0
  508. package/dist/routes/routes.d.ts.map +1 -0
  509. package/dist/routes/routes.js +531 -0
  510. package/dist/routes/routes.js.map +1 -0
  511. package/dist/routes/slug-processor.d.ts +32 -0
  512. package/dist/routes/slug-processor.d.ts.map +1 -0
  513. package/dist/routes/slug-processor.js +328 -0
  514. package/dist/routes/slug-processor.js.map +1 -0
  515. package/dist/routes/types.d.ts +31 -0
  516. package/dist/routes/types.d.ts.map +1 -0
  517. package/dist/routes/types.js +7 -0
  518. package/dist/routes/types.js.map +1 -0
  519. package/dist/routes/users/index.d.ts +7 -0
  520. package/dist/routes/users/index.d.ts.map +1 -0
  521. package/dist/routes/users/index.js +7 -0
  522. package/dist/routes/users/index.js.map +1 -0
  523. package/dist/structure/builder/StructureBuilder.d.ts +209 -0
  524. package/dist/structure/builder/StructureBuilder.d.ts.map +1 -0
  525. package/dist/structure/builder/StructureBuilder.js +425 -0
  526. package/dist/structure/builder/StructureBuilder.js.map +1 -0
  527. package/dist/structure/builder/index.d.ts +8 -0
  528. package/dist/structure/builder/index.d.ts.map +1 -0
  529. package/dist/structure/builder/index.js +6 -0
  530. package/dist/structure/builder/index.js.map +1 -0
  531. package/dist/structure/builder/navigation-tree-builder.d.ts +33 -0
  532. package/dist/structure/builder/navigation-tree-builder.d.ts.map +1 -0
  533. package/dist/structure/builder/navigation-tree-builder.js +284 -0
  534. package/dist/structure/builder/navigation-tree-builder.js.map +1 -0
  535. package/dist/structure/errors/index.d.ts +72 -0
  536. package/dist/structure/errors/index.d.ts.map +1 -0
  537. package/dist/structure/errors/index.js +136 -0
  538. package/dist/structure/errors/index.js.map +1 -0
  539. package/dist/structure/index.d.ts +14 -0
  540. package/dist/structure/index.d.ts.map +1 -0
  541. package/dist/structure/index.js +16 -0
  542. package/dist/structure/index.js.map +1 -0
  543. package/dist/structure/types/filters.d.ts +167 -0
  544. package/dist/structure/types/filters.d.ts.map +1 -0
  545. package/dist/structure/types/filters.js +38 -0
  546. package/dist/structure/types/filters.js.map +1 -0
  547. package/dist/structure/types/index.d.ts +11 -0
  548. package/dist/structure/types/index.d.ts.map +1 -0
  549. package/dist/structure/types/index.js +7 -0
  550. package/dist/structure/types/index.js.map +1 -0
  551. package/dist/structure/types/permissions.d.ts +159 -0
  552. package/dist/structure/types/permissions.d.ts.map +1 -0
  553. package/dist/structure/types/permissions.js +49 -0
  554. package/dist/structure/types/permissions.js.map +1 -0
  555. package/dist/structure/types/structure.d.ts +342 -0
  556. package/dist/structure/types/structure.d.ts.map +1 -0
  557. package/dist/structure/types/structure.js +6 -0
  558. package/dist/structure/types/structure.js.map +1 -0
  559. package/dist/structure/types/views.d.ts +278 -0
  560. package/dist/structure/types/views.d.ts.map +1 -0
  561. package/dist/structure/types/views.js +6 -0
  562. package/dist/structure/types/views.js.map +1 -0
  563. package/dist/structure/utils/hash.d.ts +61 -0
  564. package/dist/structure/utils/hash.d.ts.map +1 -0
  565. package/dist/structure/utils/hash.js +118 -0
  566. package/dist/structure/utils/hash.js.map +1 -0
  567. package/dist/structure/utils/index.d.ts +10 -0
  568. package/dist/structure/utils/index.d.ts.map +1 -0
  569. package/dist/structure/utils/index.js +7 -0
  570. package/dist/structure/utils/index.js.map +1 -0
  571. package/dist/structure/utils/permission-checker.d.ts +57 -0
  572. package/dist/structure/utils/permission-checker.d.ts.map +1 -0
  573. package/dist/structure/utils/permission-checker.js +286 -0
  574. package/dist/structure/utils/permission-checker.js.map +1 -0
  575. package/dist/structure/utils/query-builder.d.ts +82 -0
  576. package/dist/structure/utils/query-builder.d.ts.map +1 -0
  577. package/dist/structure/utils/query-builder.js +489 -0
  578. package/dist/structure/utils/query-builder.js.map +1 -0
  579. package/dist/structure/utils/structure-merger.d.ts +91 -0
  580. package/dist/structure/utils/structure-merger.d.ts.map +1 -0
  581. package/dist/structure/utils/structure-merger.js +445 -0
  582. package/dist/structure/utils/structure-merger.js.map +1 -0
  583. package/dist/structure/validation/schemas.d.ts +3107 -0
  584. package/dist/structure/validation/schemas.d.ts.map +1 -0
  585. package/dist/structure/validation/schemas.js +436 -0
  586. package/dist/structure/validation/schemas.js.map +1 -0
  587. package/dist/types/api-requests.d.ts +334 -0
  588. package/dist/types/api-requests.d.ts.map +1 -0
  589. package/dist/types/api-requests.js +8 -0
  590. package/dist/types/api-requests.js.map +1 -0
  591. package/dist/types/auth.d.ts +394 -0
  592. package/dist/types/auth.d.ts.map +1 -0
  593. package/dist/types/auth.js +8 -0
  594. package/dist/types/auth.js.map +1 -0
  595. package/dist/types/document.d.ts +49 -0
  596. package/dist/types/document.d.ts.map +1 -0
  597. package/dist/types/document.js +6 -0
  598. package/dist/types/document.js.map +1 -0
  599. package/dist/types/field-system.d.ts +197 -0
  600. package/dist/types/field-system.d.ts.map +1 -0
  601. package/dist/types/field-system.js +8 -0
  602. package/dist/types/field-system.js.map +1 -0
  603. package/dist/types/fields.d.ts +17 -0
  604. package/dist/types/fields.d.ts.map +1 -0
  605. package/dist/types/fields.js +21 -0
  606. package/dist/types/fields.js.map +1 -0
  607. package/dist/types/http.d.ts +162 -0
  608. package/dist/types/http.d.ts.map +1 -0
  609. package/dist/types/http.js +8 -0
  610. package/dist/types/http.js.map +1 -0
  611. package/dist/types/i18n.d.ts +56 -0
  612. package/dist/types/i18n.d.ts.map +1 -0
  613. package/dist/types/i18n.js +25 -0
  614. package/dist/types/i18n.js.map +1 -0
  615. package/dist/types/index.d.ts +22 -0
  616. package/dist/types/index.d.ts.map +1 -0
  617. package/dist/types/index.js +10 -0
  618. package/dist/types/index.js.map +1 -0
  619. package/dist/types/mail.d.ts +224 -0
  620. package/dist/types/mail.d.ts.map +1 -0
  621. package/dist/types/mail.js +8 -0
  622. package/dist/types/mail.js.map +1 -0
  623. package/dist/types/media.d.ts +56 -0
  624. package/dist/types/media.d.ts.map +1 -0
  625. package/dist/types/media.js +6 -0
  626. package/dist/types/media.js.map +1 -0
  627. package/dist/types/oauth2.d.ts +287 -0
  628. package/dist/types/oauth2.d.ts.map +1 -0
  629. package/dist/types/oauth2.js +43 -0
  630. package/dist/types/oauth2.js.map +1 -0
  631. package/dist/types/passkey.d.ts +217 -0
  632. package/dist/types/passkey.d.ts.map +1 -0
  633. package/dist/types/passkey.js +8 -0
  634. package/dist/types/passkey.js.map +1 -0
  635. package/dist/types/slug.d.ts +99 -0
  636. package/dist/types/slug.d.ts.map +1 -0
  637. package/dist/types/slug.js +8 -0
  638. package/dist/types/slug.js.map +1 -0
  639. package/package.json +183 -0
@@ -0,0 +1,1370 @@
1
+ import { promises as fs, constants } from 'fs';
2
+ import * as fsExtra from 'fs-extra';
3
+ import * as path from 'path';
4
+ import { SecurityValidator, InvalidInputError } from '../../core/index.js';
5
+ /**
6
+ * @deprecated FilesystemAdapter is deprecated. Use the split storage architecture instead:
7
+ *
8
+ * ```ts
9
+ * // Old way (deprecated)
10
+ * const adapter = new FilesystemAdapter({ contentDir: './content', mediaDir: './media' })
11
+ *
12
+ * // New way (recommended)
13
+ * const trokky = await TrokkyExpress.create({
14
+ * storage: {
15
+ * data: { adapter: 'filesystem-data', options: { contentDir: './data/content' } },
16
+ * media: { adapter: 'filesystem-media', options: { mediaDir: './data/media' } }
17
+ * }
18
+ * })
19
+ * ```
20
+ *
21
+ * This provides better separation of concerns, improved scalability, and clearer configuration.
22
+ * See migration guide in docs for more details.
23
+ */
24
+ export class FilesystemAdapter {
25
+ config;
26
+ // Security limits
27
+ MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
28
+ MAX_DOCUMENT_SIZE = 10 * 1024 * 1024; // 10MB
29
+ constructor(config = {}) {
30
+ // Show deprecation warning
31
+ if (!config.silent) {
32
+ console.warn('⚠️ [DEPRECATED] FilesystemAdapter is deprecated. Please migrate to the split storage architecture:\n' +
33
+ ' Use TrokkyExpress.create() with storage.data and storage.media adapters.\n' +
34
+ ' See migration guide: https://docs.trokky.dev/migration/split-storage');
35
+ }
36
+ this.config = {
37
+ contentDir: config.contentDir || './content',
38
+ mediaDir: config.mediaDir || './media',
39
+ usersDir: config.usersDir || './users',
40
+ tokensDir: config.tokensDir || './tokens',
41
+ webhooksDir: config.webhooksDir || './webhooks',
42
+ createDirs: config.createDirs ?? true,
43
+ prettyJson: config.prettyJson ?? true,
44
+ jsonSpaces: config.jsonSpaces ?? 2,
45
+ syncWrites: config.syncWrites ?? false,
46
+ fileMode: config.fileMode ?? 0o644,
47
+ dirMode: config.dirMode ?? 0o755,
48
+ silent: config.silent ?? false,
49
+ mediaBaseUrl: config.mediaBaseUrl
50
+ };
51
+ // Initialize directories
52
+ if (this.config.createDirs) {
53
+ this.initializeDirectories();
54
+ }
55
+ }
56
+ async initializeDirectories() {
57
+ try {
58
+ await fsExtra.ensureDir(this.config.contentDir, { mode: this.config.dirMode });
59
+ await fsExtra.ensureDir(this.config.mediaDir, { mode: this.config.dirMode });
60
+ await fsExtra.ensureDir(this.config.usersDir, { mode: this.config.dirMode });
61
+ await fsExtra.ensureDir(this.config.webhooksDir, { mode: this.config.dirMode });
62
+ // Create metadata directory for media files
63
+ const mediaMetadataDir = path.join(this.config.mediaDir, '.metadata');
64
+ await fsExtra.ensureDir(mediaMetadataDir, { mode: this.config.dirMode });
65
+ }
66
+ catch (error) {
67
+ // Don't throw on directory creation failure during initialization
68
+ // Let individual operations handle their own directory creation
69
+ if (!this.config.silent) {
70
+ console.warn(`Warning: Failed to initialize directories: ${error}`);
71
+ }
72
+ }
73
+ }
74
+ // Document operations
75
+ async getDocument(collection, id) {
76
+ try {
77
+ // Input validation is handled in getDocumentPath
78
+ const filePath = this.getDocumentPath(collection, id);
79
+ // Check if file exists
80
+ try {
81
+ await fs.access(filePath, constants.F_OK);
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ const content = await fs.readFile(filePath, 'utf-8');
87
+ const documentFile = this.safeParseJSON(content, this.dateReviver);
88
+ // Convert stored data back to Document format
89
+ const document = {
90
+ id: documentFile.id,
91
+ _id: documentFile.id,
92
+ ...documentFile.data,
93
+ _collection: documentFile.collection,
94
+ _createdAt: documentFile.metadata.createdAt instanceof Date ? documentFile.metadata.createdAt : new Date(documentFile.metadata.createdAt),
95
+ _updatedAt: documentFile.metadata.updatedAt instanceof Date ? documentFile.metadata.updatedAt : new Date(documentFile.metadata.updatedAt),
96
+ _revision: documentFile.metadata.revision,
97
+ _status: documentFile.metadata.status,
98
+ _createdBy: documentFile.metadata.createdBy,
99
+ _updatedBy: documentFile.metadata.updatedBy,
100
+ _createdByType: documentFile.metadata.createdByType,
101
+ _updatedByType: documentFile.metadata.updatedByType
102
+ };
103
+ return document;
104
+ }
105
+ catch (error) {
106
+ throw new Error(`Failed to read document ${collection}/${id}: ${error}`);
107
+ }
108
+ }
109
+ async saveDocument(collection, id, data, auditContext) {
110
+ try {
111
+ // Validate document data
112
+ SecurityValidator.validateDocumentData(data);
113
+ // Check document size to prevent DoS attacks
114
+ const dataSize = JSON.stringify(data).length;
115
+ if (dataSize > this.MAX_DOCUMENT_SIZE) {
116
+ throw new InvalidInputError(`Document size ${dataSize} exceeds maximum allowed size of ${this.MAX_DOCUMENT_SIZE} bytes`, 'size');
117
+ }
118
+ // Input validation is handled in getDocumentPath
119
+ const filePath = this.getDocumentPath(collection, id);
120
+ const collectionDir = path.dirname(filePath);
121
+ // Ensure collection directory exists
122
+ await fsExtra.ensureDir(collectionDir, { mode: this.config.dirMode });
123
+ // Check if document already exists to determine if it's an update
124
+ let existingDoc = null;
125
+ try {
126
+ existingDoc = await this.getDocument(collection, id);
127
+ }
128
+ catch {
129
+ // Document doesn't exist, this is a new document
130
+ }
131
+ const now = new Date();
132
+ const isUpdate = existingDoc !== null;
133
+ const documentFile = {
134
+ id,
135
+ collection,
136
+ data: { ...data },
137
+ metadata: {
138
+ createdAt: isUpdate ? existingDoc._createdAt : now,
139
+ updatedAt: now,
140
+ revision: isUpdate ? (existingDoc._revision || 0) + 1 : 1,
141
+ status: existingDoc?._status,
142
+ // Add audit metadata
143
+ createdBy: isUpdate ? existingDoc._createdBy : auditContext?.userId,
144
+ updatedBy: auditContext?.userId,
145
+ createdByType: isUpdate ? existingDoc._createdByType : auditContext?.userType,
146
+ updatedByType: auditContext?.userType
147
+ }
148
+ };
149
+ // Write to file with Date serialization - use atomic write to prevent race conditions
150
+ const jsonContent = this.config.prettyJson
151
+ ? JSON.stringify(documentFile, this.dateReplacer, this.config.jsonSpaces)
152
+ : JSON.stringify(documentFile, this.dateReplacer);
153
+ await this.atomicWriteFile(filePath, jsonContent);
154
+ if (this.config.syncWrites) {
155
+ // Force sync to disk
156
+ const fileHandle = await fs.open(filePath, 'r+');
157
+ await fileHandle.sync();
158
+ await fileHandle.close();
159
+ }
160
+ // Return the document in the expected format
161
+ const document = {
162
+ id,
163
+ _id: id,
164
+ ...data,
165
+ _collection: collection,
166
+ _createdAt: documentFile.metadata.createdAt,
167
+ _updatedAt: documentFile.metadata.updatedAt,
168
+ _revision: documentFile.metadata.revision,
169
+ _status: documentFile.metadata.status,
170
+ _createdBy: documentFile.metadata.createdBy,
171
+ _updatedBy: documentFile.metadata.updatedBy,
172
+ _createdByType: documentFile.metadata.createdByType,
173
+ _updatedByType: documentFile.metadata.updatedByType
174
+ };
175
+ return document;
176
+ }
177
+ catch (error) {
178
+ throw new Error(`Failed to save document ${collection}/${id}: ${error}`);
179
+ }
180
+ }
181
+ async listDocuments(collection, options = {}) {
182
+ try {
183
+ // Validate inputs
184
+ SecurityValidator.validateCollectionName(collection);
185
+ SecurityValidator.sanitizeListOptions(options);
186
+ // Apply resource limits
187
+ const limit = Math.min(options.limit || 1000, 1000); // Cap at 1000 documents
188
+ const offset = Math.max(options.offset || 0, 0);
189
+ const collectionDir = path.join(this.config.contentDir, collection);
190
+ this.validateSecurePath(collectionDir, this.config.contentDir);
191
+ // Check if collection directory exists
192
+ try {
193
+ await fs.access(collectionDir, constants.F_OK);
194
+ }
195
+ catch {
196
+ return [];
197
+ }
198
+ const files = await fs.readdir(collectionDir);
199
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
200
+ let documents = [];
201
+ // Read all documents
202
+ for (const file of jsonFiles) {
203
+ const id = path.basename(file, '.json');
204
+ try {
205
+ const doc = await this.getDocument(collection, id);
206
+ if (doc) {
207
+ documents.push(doc);
208
+ }
209
+ }
210
+ catch (error) {
211
+ // Skip corrupted files but log the error
212
+ if (!this.config.silent) {
213
+ console.warn(`Skipping corrupted document ${collection}/${id}: ${error}`);
214
+ }
215
+ }
216
+ }
217
+ // Apply filtering
218
+ if (options.filter) {
219
+ documents = documents.filter(doc => this.matchesFilter(doc, options.filter));
220
+ }
221
+ // Apply sorting
222
+ if (options.sort) {
223
+ const sortFields = Array.isArray(options.sort) ? options.sort : [options.sort];
224
+ documents = this.sortDocuments(documents, sortFields);
225
+ }
226
+ // Apply pagination with validated limits
227
+ return documents.slice(offset, offset + limit);
228
+ }
229
+ catch (error) {
230
+ throw new Error(`Failed to list documents in collection ${collection}: ${error}`);
231
+ }
232
+ }
233
+ async deleteDocument(collection, id) {
234
+ try {
235
+ // Input validation is handled in getDocumentPath
236
+ const filePath = this.getDocumentPath(collection, id);
237
+ // Atomic delete - check and delete in one operation
238
+ await fs.unlink(filePath);
239
+ }
240
+ catch (error) {
241
+ if (error.code === 'ENOENT') {
242
+ throw new Error(`Document ${collection}/${id} not found`);
243
+ }
244
+ throw new Error(`Failed to delete document ${collection}/${id}: ${error}`);
245
+ }
246
+ }
247
+ // Media operations
248
+ async uploadFile(file, metadata) {
249
+ try {
250
+ // Validate media metadata
251
+ this.validateMediaMetadata(metadata);
252
+ // Use secure path methods
253
+ const filePath = this.getMediaPath(metadata.id, metadata.extension);
254
+ const metadataPath = this.getMediaMetadataPath(metadata.id);
255
+ // Ensure media directory exists
256
+ await fsExtra.ensureDir(path.dirname(filePath), { mode: this.config.dirMode });
257
+ await fsExtra.ensureDir(path.dirname(metadataPath), { mode: this.config.dirMode });
258
+ // Write file content
259
+ const buffer = await this.fileToBuffer(file);
260
+ await fs.writeFile(filePath, buffer, { mode: this.config.fileMode });
261
+ const now = new Date();
262
+ const fileMetadata = {
263
+ id: metadata.id,
264
+ filename: metadata.filename,
265
+ contentType: metadata.contentType,
266
+ size: metadata.size,
267
+ extension: metadata.extension,
268
+ originalPath: filePath,
269
+ createdAt: now,
270
+ updatedAt: now
271
+ };
272
+ // Write metadata atomically
273
+ const metadataContent = this.config.prettyJson
274
+ ? JSON.stringify(fileMetadata, this.dateReplacer, this.config.jsonSpaces)
275
+ : JSON.stringify(fileMetadata, this.dateReplacer);
276
+ await this.atomicWriteFile(metadataPath, metadataContent);
277
+ // Generate file URL (relative path for portability)
278
+ const relativeUrl = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
279
+ const mediaFile = {
280
+ id: metadata.id,
281
+ filename: metadata.filename,
282
+ contentType: metadata.contentType,
283
+ size: metadata.size,
284
+ metadata: {
285
+ path: relativeUrl,
286
+ extension: metadata.extension,
287
+ originalFilename: metadata.filename
288
+ },
289
+ _createdAt: now
290
+ };
291
+ return mediaFile;
292
+ }
293
+ catch (error) {
294
+ throw new Error(`Failed to upload file ${metadata.filename}: ${error}`);
295
+ }
296
+ }
297
+ async getFile(id) {
298
+ try {
299
+ // Input validation is handled in getMediaMetadataPath
300
+ const metadataPath = this.getMediaMetadataPath(id);
301
+ // Check if metadata file exists
302
+ try {
303
+ await fs.access(metadataPath, constants.F_OK);
304
+ }
305
+ catch {
306
+ return null;
307
+ }
308
+ const metadataContent = await fs.readFile(metadataPath, 'utf-8');
309
+ const fileMetadata = this.safeParseJSON(metadataContent, this.dateReviver);
310
+ const filePath = this.getMediaPath(id, fileMetadata.extension);
311
+ // Check if actual file exists
312
+ try {
313
+ await fs.access(filePath, constants.F_OK);
314
+ }
315
+ catch {
316
+ // Metadata exists but file is missing
317
+ throw new Error(`Media file ${id} metadata found but file is missing`);
318
+ }
319
+ const relativeUrl = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
320
+ const mediaFile = {
321
+ id: fileMetadata.id,
322
+ filename: fileMetadata.filename,
323
+ contentType: fileMetadata.contentType,
324
+ size: fileMetadata.size,
325
+ metadata: {
326
+ // Include basic file metadata
327
+ path: relativeUrl,
328
+ extension: fileMetadata.extension,
329
+ originalFilename: fileMetadata.filename,
330
+ // Include all user-editable metadata fields (include even if empty string)
331
+ ...(fileMetadata.title !== undefined && { title: fileMetadata.title }),
332
+ ...(fileMetadata.alt !== undefined && { alt: fileMetadata.alt }),
333
+ ...(fileMetadata.author !== undefined && { author: fileMetadata.author }),
334
+ ...(fileMetadata.credit !== undefined && { credit: fileMetadata.credit }),
335
+ // Include image processing metadata with updated URLs
336
+ ...(fileMetadata.imageVariants && {
337
+ imageVariants: this.updateVariantUrls(fileMetadata.imageVariants, fileMetadata.id)
338
+ }),
339
+ ...(fileMetadata.originalDimensions && { originalDimensions: fileMetadata.originalDimensions })
340
+ },
341
+ _createdAt: fileMetadata.createdAt instanceof Date ? fileMetadata.createdAt : new Date(fileMetadata.createdAt)
342
+ };
343
+ return mediaFile;
344
+ }
345
+ catch (error) {
346
+ throw new Error(`Failed to get file ${id}: ${error}`);
347
+ }
348
+ }
349
+ async updateFile(id, metadata) {
350
+ try {
351
+ // Input validation
352
+ SecurityValidator.validateDocumentId(id);
353
+ const metadataPath = this.getMediaMetadataPath(id);
354
+ // Check if metadata file exists
355
+ try {
356
+ await fs.access(metadataPath, constants.F_OK);
357
+ }
358
+ catch {
359
+ throw new Error(`Media file ${id} not found`);
360
+ }
361
+ // Read existing metadata
362
+ const metadataContent = await fs.readFile(metadataPath, 'utf-8');
363
+ const existingMetadata = this.safeParseJSON(metadataContent, this.dateReviver);
364
+ // Update metadata with new values
365
+ const updatedMetadata = {
366
+ ...existingMetadata,
367
+ ...metadata,
368
+ updatedAt: new Date(),
369
+ // Preserve core fields that shouldn't be changed
370
+ id: existingMetadata.id,
371
+ filename: existingMetadata.filename,
372
+ contentType: existingMetadata.contentType,
373
+ size: existingMetadata.size,
374
+ extension: existingMetadata.extension,
375
+ originalPath: existingMetadata.originalPath,
376
+ createdAt: existingMetadata.createdAt
377
+ };
378
+ // Write updated metadata atomically
379
+ const updatedMetadataContent = this.config.prettyJson
380
+ ? JSON.stringify(updatedMetadata, this.dateReplacer, this.config.jsonSpaces)
381
+ : JSON.stringify(updatedMetadata, this.dateReplacer);
382
+ await this.atomicWriteFile(metadataPath, updatedMetadataContent);
383
+ // Return updated MediaFile
384
+ const filePath = this.getMediaPath(id, updatedMetadata.extension);
385
+ const relativeUrl = path.relative(process.cwd(), filePath).replace(/\\/g, '/');
386
+ const mediaFile = {
387
+ id: updatedMetadata.id,
388
+ filename: updatedMetadata.filename,
389
+ contentType: updatedMetadata.contentType,
390
+ size: updatedMetadata.size,
391
+ metadata: {
392
+ path: relativeUrl,
393
+ extension: updatedMetadata.extension,
394
+ originalFilename: updatedMetadata.filename,
395
+ // Include custom metadata fields
396
+ ...metadata
397
+ },
398
+ _createdAt: updatedMetadata.createdAt instanceof Date ? updatedMetadata.createdAt : new Date(updatedMetadata.createdAt)
399
+ };
400
+ return mediaFile;
401
+ }
402
+ catch (error) {
403
+ console.error('[ERROR] FilesystemAdapter.updateFile failed:', error);
404
+ throw new Error(`Failed to update file metadata ${id}: ${error}`);
405
+ }
406
+ }
407
+ async getFileContent(id) {
408
+ try {
409
+ // Input validation is handled in getMediaMetadataPath
410
+ const metadataPath = this.getMediaMetadataPath(id);
411
+ // Check if metadata file exists
412
+ try {
413
+ await fs.access(metadataPath, constants.F_OK);
414
+ }
415
+ catch {
416
+ return null;
417
+ }
418
+ const metadataContent = await fs.readFile(metadataPath, 'utf-8');
419
+ const fileMetadata = this.safeParseJSON(metadataContent, this.dateReviver);
420
+ const filePath = this.getMediaPath(id, fileMetadata.extension);
421
+ // Check if actual file exists
422
+ try {
423
+ await fs.access(filePath, constants.F_OK);
424
+ }
425
+ catch {
426
+ return null;
427
+ }
428
+ // Read file content as buffer and convert to ArrayBuffer
429
+ const buffer = await fs.readFile(filePath);
430
+ const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
431
+ // Ensure we return ArrayBuffer, not SharedArrayBuffer
432
+ if (sliced instanceof ArrayBuffer) {
433
+ return sliced;
434
+ }
435
+ else {
436
+ // Copy SharedArrayBuffer to ArrayBuffer
437
+ const sharedBuffer = sliced;
438
+ const arrayBuffer = new ArrayBuffer(sharedBuffer.byteLength);
439
+ new Uint8Array(arrayBuffer).set(new Uint8Array(sharedBuffer));
440
+ return arrayBuffer;
441
+ }
442
+ }
443
+ catch (error) {
444
+ throw new Error(`Failed to get file content ${id}: ${error}`);
445
+ }
446
+ }
447
+ async deleteFile(id) {
448
+ try {
449
+ // Input validation is handled in getMediaMetadataPath
450
+ const metadataPath = this.getMediaMetadataPath(id);
451
+ // Read metadata to get file extension
452
+ let extension = '';
453
+ try {
454
+ const metadataContent = await fs.readFile(metadataPath, 'utf-8');
455
+ const fileMetadata = this.safeParseJSON(metadataContent, this.dateReviver);
456
+ extension = fileMetadata.extension;
457
+ }
458
+ catch {
459
+ throw new Error(`Media file ${id} not found`);
460
+ }
461
+ const filePath = this.getMediaPath(id, extension);
462
+ // Delete metadata FIRST, then main file
463
+ // This ensures if file deletion fails, the file won't show up in listMedia()
464
+ // since listMedia() uses metadata files as source of truth
465
+ const unlinkWithErrorCheck = async (path) => {
466
+ try {
467
+ await fs.unlink(path);
468
+ }
469
+ catch (error) {
470
+ // Only ignore "file not found" errors, throw everything else
471
+ if (error.code !== 'ENOENT') {
472
+ throw error;
473
+ }
474
+ }
475
+ };
476
+ // Delete metadata first, then file
477
+ await unlinkWithErrorCheck(metadataPath);
478
+ await unlinkWithErrorCheck(filePath);
479
+ }
480
+ catch (error) {
481
+ throw new Error(`Failed to delete file ${id}: ${error}`);
482
+ }
483
+ }
484
+ async listMedia(options = {}) {
485
+ try {
486
+ // Apply resource limits
487
+ const limit = Math.min(options.limit || 1000, 1000);
488
+ const offset = Math.max(options.offset || 0, 0);
489
+ const metadataDir = path.join(this.config.mediaDir, '.metadata');
490
+ // Check if metadata directory exists
491
+ try {
492
+ await fs.access(metadataDir, constants.F_OK);
493
+ }
494
+ catch (error) {
495
+ return { items: [], total: 0 };
496
+ }
497
+ const files = await fs.readdir(metadataDir);
498
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
499
+ const mediaFiles = [];
500
+ // Read all media metadata files
501
+ for (const file of jsonFiles) {
502
+ const id = path.basename(file, '.json');
503
+ try {
504
+ const mediaFile = await this.getFile(id);
505
+ if (mediaFile) {
506
+ mediaFiles.push(mediaFile);
507
+ }
508
+ }
509
+ catch (error) {
510
+ // Skip corrupted files but log the error
511
+ console.error(`[ERROR] Failed to load media file ${id}:`, error);
512
+ }
513
+ }
514
+ // Sort by creation date (newest first)
515
+ mediaFiles.sort((a, b) => new Date(b._createdAt).getTime() - new Date(a._createdAt).getTime());
516
+ // Store total before pagination
517
+ const total = mediaFiles.length;
518
+ // Apply pagination
519
+ const items = mediaFiles.slice(offset, offset + limit);
520
+ return { items, total };
521
+ }
522
+ catch (error) {
523
+ console.error('[ERROR] FilesystemAdapter.listMedia failed:', error);
524
+ throw new Error(`Failed to list media files: ${error}`);
525
+ }
526
+ }
527
+ // User operations (system entities, stored separately from content)
528
+ async getUser(id) {
529
+ try {
530
+ const filePath = this.getUserPath(id);
531
+ // Check if file exists
532
+ try {
533
+ await fs.access(filePath, constants.F_OK);
534
+ }
535
+ catch {
536
+ return null;
537
+ }
538
+ const content = await fs.readFile(filePath, 'utf-8');
539
+ const user = this.safeParseJSON(content, this.dateReviver);
540
+ return user;
541
+ }
542
+ catch (error) {
543
+ throw new Error(`Failed to read user ${id}: ${error}`);
544
+ }
545
+ }
546
+ async saveUser(id, userData) {
547
+ try {
548
+ const filePath = this.getUserPath(id);
549
+ const userDir = path.dirname(filePath);
550
+ // Ensure users directory exists
551
+ await fsExtra.ensureDir(userDir, { mode: this.config.dirMode });
552
+ // Check if user already exists
553
+ let existingUser = null;
554
+ try {
555
+ existingUser = await this.getUser(id);
556
+ }
557
+ catch {
558
+ // User doesn't exist, this is a new user
559
+ }
560
+ const now = new Date().toISOString();
561
+ const isUpdate = existingUser !== null;
562
+ const user = {
563
+ id,
564
+ username: userData.username || existingUser?.username || '',
565
+ email: userData.email || existingUser?.email || '',
566
+ passwordHash: userData.passwordHash || existingUser?.passwordHash || '',
567
+ firstName: userData.firstName || existingUser?.firstName || '',
568
+ lastName: userData.lastName || existingUser?.lastName || '',
569
+ role: userData.role || existingUser?.role || 'viewer',
570
+ permissions: userData.permissions || existingUser?.permissions || ['content:read'],
571
+ isActive: userData.isActive ?? existingUser?.isActive ?? true,
572
+ profileImage: userData.profileImage || existingUser?.profileImage,
573
+ preferences: userData.preferences || existingUser?.preferences || {},
574
+ lastLoginAt: userData.lastLoginAt || existingUser?.lastLoginAt,
575
+ createdAt: existingUser?.createdAt || now,
576
+ updatedAt: now
577
+ };
578
+ // Write to file atomically
579
+ const jsonContent = this.config.prettyJson
580
+ ? JSON.stringify(user, this.dateReplacer, this.config.jsonSpaces)
581
+ : JSON.stringify(user, this.dateReplacer);
582
+ await this.atomicWriteFile(filePath, jsonContent);
583
+ if (this.config.syncWrites) {
584
+ // Force sync to disk
585
+ const fileHandle = await fs.open(filePath, 'r+');
586
+ await fileHandle.sync();
587
+ await fileHandle.close();
588
+ }
589
+ return user;
590
+ }
591
+ catch (error) {
592
+ throw new Error(`Failed to save user ${id}: ${error}`);
593
+ }
594
+ }
595
+ async listUsers(options = {}) {
596
+ try {
597
+ // Apply resource limits
598
+ const limit = Math.min(options.limit || 1000, 1000);
599
+ const offset = Math.max(options.offset || 0, 0);
600
+ // Check if users directory exists
601
+ try {
602
+ await fs.access(this.config.usersDir, constants.F_OK);
603
+ }
604
+ catch {
605
+ return [];
606
+ }
607
+ const files = await fs.readdir(this.config.usersDir);
608
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
609
+ let users = [];
610
+ // Read all users
611
+ for (const file of jsonFiles) {
612
+ const id = path.basename(file, '.json');
613
+ try {
614
+ const user = await this.getUser(id);
615
+ if (user) {
616
+ users.push(user);
617
+ }
618
+ }
619
+ catch (error) {
620
+ // Skip corrupted files but log the error
621
+ if (!this.config.silent) {
622
+ console.warn(`Skipping corrupted user ${id}: ${error}`);
623
+ }
624
+ }
625
+ }
626
+ // Apply filtering
627
+ if (options.role) {
628
+ users = users.filter(user => user.role === options.role);
629
+ }
630
+ if (options.isActive !== undefined) {
631
+ users = users.filter(user => user.isActive === options.isActive);
632
+ }
633
+ // Sort by creation date (newest first)
634
+ users.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
635
+ // Apply pagination
636
+ return users.slice(offset, offset + limit);
637
+ }
638
+ catch (error) {
639
+ throw new Error(`Failed to list users: ${error}`);
640
+ }
641
+ }
642
+ async deleteUser(id) {
643
+ try {
644
+ const filePath = this.getUserPath(id);
645
+ // Atomic delete
646
+ await fs.unlink(filePath);
647
+ }
648
+ catch (error) {
649
+ if (error.code === 'ENOENT') {
650
+ throw new Error(`User ${id} not found`);
651
+ }
652
+ throw new Error(`Failed to delete user ${id}: ${error}`);
653
+ }
654
+ }
655
+ async getUserByUsername(username) {
656
+ try {
657
+ const users = await this.listUsers();
658
+ return users.find(user => user.username === username) || null;
659
+ }
660
+ catch (error) {
661
+ throw new Error(`Failed to get user by username ${username}: ${error}`);
662
+ }
663
+ }
664
+ async getUserByEmail(email) {
665
+ try {
666
+ const users = await this.listUsers();
667
+ return users.find(user => user.email === email) || null;
668
+ }
669
+ catch (error) {
670
+ throw new Error(`Failed to get user by email ${email}: ${error}`);
671
+ }
672
+ }
673
+ async getUserByOAuthProvider(provider, providerId) {
674
+ if (!provider || !providerId) {
675
+ return null;
676
+ }
677
+ try {
678
+ const users = await this.listUsers();
679
+ return users.find(user => user.oauthProviders?.some(p => p.provider === provider && p.providerId === providerId)) || null;
680
+ }
681
+ catch (error) {
682
+ throw new Error(`Failed to get user by OAuth provider ${provider}:${providerId}: ${error}`);
683
+ }
684
+ }
685
+ // App Token operations (system entities, stored separately from content)
686
+ async getAppToken(id) {
687
+ try {
688
+ const filePath = this.getTokenPath(id);
689
+ // Check if file exists
690
+ try {
691
+ await fs.access(filePath, constants.F_OK);
692
+ }
693
+ catch {
694
+ return null;
695
+ }
696
+ const content = await fs.readFile(filePath, 'utf-8');
697
+ const token = this.safeParseJSON(content, this.dateReviver);
698
+ return token;
699
+ }
700
+ catch (error) {
701
+ throw new Error(`Failed to read app token ${id}: ${error}`);
702
+ }
703
+ }
704
+ async saveAppToken(id, tokenData) {
705
+ try {
706
+ const filePath = this.getTokenPath(id);
707
+ const tokenDir = path.dirname(filePath);
708
+ // Ensure tokens directory exists
709
+ await fsExtra.ensureDir(tokenDir, { mode: this.config.dirMode });
710
+ // Check if token already exists
711
+ let existingToken = null;
712
+ try {
713
+ existingToken = await this.getAppToken(id);
714
+ }
715
+ catch {
716
+ // Token doesn't exist, this is a new token
717
+ }
718
+ const now = new Date().toISOString();
719
+ const isUpdate = existingToken !== null;
720
+ const token = {
721
+ id,
722
+ name: tokenData.name || existingToken?.name || '',
723
+ description: tokenData.description || existingToken?.description,
724
+ tokenHash: tokenData.tokenHash || existingToken?.tokenHash || '',
725
+ permissions: tokenData.permissions || existingToken?.permissions || [],
726
+ createdBy: tokenData.createdBy || existingToken?.createdBy || '',
727
+ isActive: tokenData.isActive ?? existingToken?.isActive ?? true,
728
+ lastUsedAt: tokenData.lastUsedAt || existingToken?.lastUsedAt,
729
+ usageCount: tokenData.usageCount ?? existingToken?.usageCount ?? 0,
730
+ expiresAt: tokenData.expiresAt || existingToken?.expiresAt,
731
+ createdAt: existingToken?.createdAt || now,
732
+ updatedAt: now
733
+ };
734
+ // Validate required fields
735
+ if (!token.name) {
736
+ throw new Error('Token name is required');
737
+ }
738
+ if (!token.tokenHash) {
739
+ throw new Error('Token hash is required');
740
+ }
741
+ if (!token.createdBy) {
742
+ throw new Error('Token createdBy is required');
743
+ }
744
+ // Write token to file
745
+ const jsonContent = this.config.prettyJson
746
+ ? JSON.stringify(token, this.dateReplacer, this.config.jsonSpaces)
747
+ : JSON.stringify(token, this.dateReplacer);
748
+ if (this.config.syncWrites) {
749
+ await fs.writeFile(filePath, jsonContent, { mode: this.config.fileMode, flag: 'w' });
750
+ }
751
+ else {
752
+ await fs.writeFile(filePath, jsonContent, { mode: this.config.fileMode });
753
+ }
754
+ return token;
755
+ }
756
+ catch (error) {
757
+ throw new Error(`Failed to save app token ${id}: ${error}`);
758
+ }
759
+ }
760
+ async listAppTokens(options = {}) {
761
+ try {
762
+ const limit = Math.min(options.limit || 1000, 1000);
763
+ const offset = Math.max(options.offset || 0, 0);
764
+ // Ensure tokens directory exists
765
+ await fsExtra.ensureDir(this.config.tokensDir, { mode: this.config.dirMode });
766
+ let files;
767
+ try {
768
+ files = await fs.readdir(this.config.tokensDir);
769
+ }
770
+ catch (error) {
771
+ if (error.code === 'ENOENT') {
772
+ return [];
773
+ }
774
+ throw error;
775
+ }
776
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
777
+ const tokens = [];
778
+ for (const file of jsonFiles) {
779
+ const id = path.basename(file, '.json');
780
+ try {
781
+ const token = await this.getAppToken(id);
782
+ if (token) {
783
+ tokens.push(token);
784
+ }
785
+ }
786
+ catch (error) {
787
+ // Skip corrupted files but log the error
788
+ if (!this.config.silent) {
789
+ console.warn(`Skipping corrupted app token ${id}: ${error}`);
790
+ }
791
+ }
792
+ }
793
+ // Apply filtering
794
+ let filteredTokens = tokens;
795
+ if (options.createdBy) {
796
+ filteredTokens = filteredTokens.filter(token => token.createdBy === options.createdBy);
797
+ }
798
+ if (options.isActive !== undefined) {
799
+ filteredTokens = filteredTokens.filter(token => token.isActive === options.isActive);
800
+ }
801
+ // Sort by creation date (newest first)
802
+ filteredTokens.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
803
+ // Apply pagination
804
+ return filteredTokens.slice(offset, offset + limit);
805
+ }
806
+ catch (error) {
807
+ throw new Error(`Failed to list app tokens: ${error}`);
808
+ }
809
+ }
810
+ async deleteAppToken(id) {
811
+ try {
812
+ const filePath = this.getTokenPath(id);
813
+ // Atomic delete
814
+ await fs.unlink(filePath);
815
+ }
816
+ catch (error) {
817
+ if (error.code === 'ENOENT') {
818
+ throw new Error(`App token ${id} not found`);
819
+ }
820
+ throw new Error(`Failed to delete app token ${id}: ${error}`);
821
+ }
822
+ }
823
+ async getAppTokenByHash(hash) {
824
+ try {
825
+ const tokens = await this.listAppTokens();
826
+ return tokens.find(token => token.tokenHash === hash) || null;
827
+ }
828
+ catch (error) {
829
+ throw new Error(`Failed to get app token by hash: ${error}`);
830
+ }
831
+ }
832
+ // Webhook operations (system entities, stored separately from content)
833
+ async getWebhook(id) {
834
+ try {
835
+ const filePath = this.getWebhookPath(id);
836
+ // Check if file exists
837
+ try {
838
+ await fs.access(filePath, constants.F_OK);
839
+ }
840
+ catch {
841
+ return null;
842
+ }
843
+ const content = await fs.readFile(filePath, 'utf-8');
844
+ const webhook = this.safeParseJSON(content, this.dateReviver);
845
+ return webhook;
846
+ }
847
+ catch (error) {
848
+ throw new Error(`Failed to read webhook ${id}: ${error}`);
849
+ }
850
+ }
851
+ async saveWebhook(id, webhookData) {
852
+ try {
853
+ SecurityValidator.validateDocumentId(id);
854
+ const filePath = this.getWebhookPath(id);
855
+ const webhookDir = path.dirname(filePath);
856
+ // Ensure webhook directory exists
857
+ await fsExtra.ensureDir(webhookDir, { mode: this.config.dirMode });
858
+ // Check if webhook already exists
859
+ let existingWebhook = null;
860
+ try {
861
+ existingWebhook = await this.getWebhook(id);
862
+ }
863
+ catch {
864
+ // Webhook doesn't exist, this is a new webhook
865
+ }
866
+ const now = new Date();
867
+ const isUpdate = existingWebhook !== null;
868
+ const webhook = {
869
+ id,
870
+ name: webhookData.name || existingWebhook?.name || 'Untitled Webhook',
871
+ url: webhookData.url || existingWebhook?.url || '',
872
+ events: webhookData.events || existingWebhook?.events || [],
873
+ active: webhookData.active !== undefined ? webhookData.active : (existingWebhook?.active ?? true),
874
+ secret: webhookData.secret || existingWebhook?.secret || crypto.randomUUID(),
875
+ headers: webhookData.headers || existingWebhook?.headers || {},
876
+ retryPolicy: webhookData.retryPolicy || existingWebhook?.retryPolicy || {
877
+ maxRetries: 3,
878
+ backoffType: 'exponential',
879
+ baseDelay: 1000,
880
+ maxDelay: 30000,
881
+ retryOnStatus: [500, 502, 503, 504, 408, 429]
882
+ },
883
+ createdBy: webhookData.createdBy || existingWebhook?.createdBy || 'system',
884
+ createdAt: isUpdate ? existingWebhook.createdAt : now,
885
+ updatedAt: now
886
+ };
887
+ // Write to file with Date serialization
888
+ const jsonContent = this.config.prettyJson
889
+ ? JSON.stringify(webhook, this.dateReplacer, this.config.jsonSpaces)
890
+ : JSON.stringify(webhook, this.dateReplacer);
891
+ await fs.writeFile(filePath, jsonContent, { mode: this.config.fileMode });
892
+ if (this.config.syncWrites) {
893
+ const fd = await fs.open(filePath, 'r+');
894
+ await fd.sync();
895
+ await fd.close();
896
+ }
897
+ return webhook;
898
+ }
899
+ catch (error) {
900
+ throw new Error(`Failed to save webhook ${id}: ${error}`);
901
+ }
902
+ }
903
+ async listWebhooks(options = {}) {
904
+ try {
905
+ // Ensure webhooks directory exists
906
+ await fsExtra.ensureDir(this.config.webhooksDir, { mode: this.config.dirMode });
907
+ const files = await fs.readdir(this.config.webhooksDir);
908
+ const webhookFiles = files.filter(file => file.endsWith('.json'));
909
+ const webhooks = [];
910
+ for (const file of webhookFiles) {
911
+ try {
912
+ const id = path.basename(file, '.json');
913
+ const webhook = await this.getWebhook(id);
914
+ if (webhook) {
915
+ webhooks.push(webhook);
916
+ }
917
+ }
918
+ catch (error) {
919
+ // Skip invalid webhook files but log the issue
920
+ if (!this.config.silent) {
921
+ console.warn(`Warning: Could not read webhook file ${file}: ${error}`);
922
+ }
923
+ }
924
+ }
925
+ // Apply filters
926
+ let filteredWebhooks = webhooks;
927
+ if (options.active !== undefined) {
928
+ filteredWebhooks = filteredWebhooks.filter(webhook => webhook.active === options.active);
929
+ }
930
+ if (options.events && options.events.length > 0) {
931
+ filteredWebhooks = filteredWebhooks.filter(webhook => options.events.some((eventPattern) => webhook.events.some(webhookEvent => this.matchesEventPattern(webhookEvent, eventPattern))));
932
+ }
933
+ if (options.createdBy) {
934
+ filteredWebhooks = filteredWebhooks.filter(webhook => webhook.createdBy === options.createdBy);
935
+ }
936
+ // Sort by creation date (newest first)
937
+ filteredWebhooks.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
938
+ // Apply pagination
939
+ if (options.offset) {
940
+ filteredWebhooks = filteredWebhooks.slice(options.offset);
941
+ }
942
+ if (options.limit) {
943
+ filteredWebhooks = filteredWebhooks.slice(0, options.limit);
944
+ }
945
+ return filteredWebhooks;
946
+ }
947
+ catch (error) {
948
+ throw new Error(`Failed to list webhooks: ${error}`);
949
+ }
950
+ }
951
+ async deleteWebhook(id) {
952
+ try {
953
+ const filePath = this.getWebhookPath(id);
954
+ // Check if webhook exists
955
+ try {
956
+ await fs.access(filePath, constants.F_OK);
957
+ }
958
+ catch {
959
+ // Webhook doesn't exist, nothing to delete
960
+ return;
961
+ }
962
+ await fs.unlink(filePath);
963
+ }
964
+ catch (error) {
965
+ throw new Error(`Failed to delete webhook ${id}: ${error}`);
966
+ }
967
+ }
968
+ // Path helpers for app tokens
969
+ getTokenPath(id) {
970
+ SecurityValidator.validateDocumentId(id);
971
+ return path.join(this.config.tokensDir, `${id}.json`);
972
+ }
973
+ // Path helpers for webhooks
974
+ getWebhookPath(id) {
975
+ SecurityValidator.validateDocumentId(id);
976
+ const webhookPath = path.join(this.config.webhooksDir, `${id}.json`);
977
+ this.validateSecurePath(webhookPath, this.config.webhooksDir);
978
+ return webhookPath;
979
+ }
980
+ // Helper method for event pattern matching
981
+ matchesEventPattern(webhookEvent, pattern) {
982
+ if (pattern === '*')
983
+ return true;
984
+ if (pattern.endsWith('*')) {
985
+ return webhookEvent.startsWith(pattern.slice(0, -1));
986
+ }
987
+ return webhookEvent === pattern;
988
+ }
989
+ // Utility operations
990
+ async healthCheck() {
991
+ try {
992
+ // Ensure directories exist first
993
+ await fsExtra.ensureDir(this.config.contentDir, { mode: this.config.dirMode });
994
+ await fsExtra.ensureDir(this.config.mediaDir, { mode: this.config.dirMode });
995
+ await fsExtra.ensureDir(this.config.usersDir, { mode: this.config.dirMode });
996
+ await fsExtra.ensureDir(this.config.tokensDir, { mode: this.config.dirMode });
997
+ // Check if directories are accessible
998
+ await fs.access(this.config.contentDir, constants.R_OK | constants.W_OK);
999
+ await fs.access(this.config.mediaDir, constants.R_OK | constants.W_OK);
1000
+ await fs.access(this.config.usersDir, constants.R_OK | constants.W_OK);
1001
+ await fs.access(this.config.tokensDir, constants.R_OK | constants.W_OK);
1002
+ // Try to write a test file
1003
+ const testFile = path.join(this.config.contentDir, '.health-check');
1004
+ await fs.writeFile(testFile, 'test');
1005
+ await fs.unlink(testFile);
1006
+ return true;
1007
+ }
1008
+ catch {
1009
+ return false;
1010
+ }
1011
+ }
1012
+ async migrate(migrations) {
1013
+ // For filesystem adapter, migrations might involve reorganizing file structure
1014
+ // This is a placeholder - actual migration logic would depend on specific needs
1015
+ const migrationLogPath = path.join(this.config.contentDir, '.migrations.json');
1016
+ let appliedMigrations = [];
1017
+ try {
1018
+ const logContent = await fs.readFile(migrationLogPath, 'utf-8');
1019
+ appliedMigrations = JSON.parse(logContent);
1020
+ }
1021
+ catch {
1022
+ // No migration log exists yet
1023
+ }
1024
+ for (const migration of migrations) {
1025
+ if (!appliedMigrations.includes(migration.version)) {
1026
+ try {
1027
+ await migration.up();
1028
+ appliedMigrations.push(migration.version);
1029
+ // Update migration log
1030
+ await fs.writeFile(migrationLogPath, JSON.stringify(appliedMigrations, null, 2));
1031
+ }
1032
+ catch (error) {
1033
+ // Rollback on failure
1034
+ try {
1035
+ await migration.down();
1036
+ }
1037
+ catch (rollbackError) {
1038
+ throw new Error(`Migration ${migration.version} failed and rollback failed: ${error}, rollback: ${rollbackError}`);
1039
+ }
1040
+ throw new Error(`Migration ${migration.version} failed: ${error}`);
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+ // Private helper methods
1046
+ getDocumentPath(collection, id) {
1047
+ // Validate inputs for security
1048
+ SecurityValidator.validateCollectionName(collection);
1049
+ SecurityValidator.validateDocumentId(id);
1050
+ const documentPath = path.join(this.config.contentDir, collection, `${id}.json`);
1051
+ this.validateSecurePath(documentPath, this.config.contentDir);
1052
+ return documentPath;
1053
+ }
1054
+ getMediaPath(id, extension) {
1055
+ // Validate inputs
1056
+ SecurityValidator.validateDocumentId(id);
1057
+ this.validateFileExtension(extension);
1058
+ const mediaPath = path.join(this.config.mediaDir, `${id}.${extension}`);
1059
+ this.validateSecurePath(mediaPath, this.config.mediaDir);
1060
+ return mediaPath;
1061
+ }
1062
+ getMediaMetadataPath(id) {
1063
+ // Validate inputs
1064
+ SecurityValidator.validateDocumentId(id);
1065
+ const metadataPath = path.join(this.config.mediaDir, '.metadata', `${id}.json`);
1066
+ this.validateSecurePath(metadataPath, this.config.mediaDir);
1067
+ return metadataPath;
1068
+ }
1069
+ getUserPath(id) {
1070
+ // Validate inputs
1071
+ SecurityValidator.validateDocumentId(id);
1072
+ const userPath = path.join(this.config.usersDir, `${id}.json`);
1073
+ this.validateSecurePath(userPath, this.config.usersDir);
1074
+ return userPath;
1075
+ }
1076
+ validateSecurePath(targetPath, basePath) {
1077
+ const resolvedTarget = path.resolve(targetPath);
1078
+ const resolvedBase = path.resolve(basePath);
1079
+ if (!resolvedTarget.startsWith(resolvedBase + path.sep) && resolvedTarget !== resolvedBase) {
1080
+ throw new InvalidInputError('Path traversal attempt detected', 'path');
1081
+ }
1082
+ }
1083
+ validateFileExtension(extension) {
1084
+ // Basic extension validation - prevent executable extensions and dangerous files
1085
+ const dangerousExtensions = [
1086
+ 'exe', 'bat', 'cmd', 'com', 'pif', 'scr', 'vbs', 'vbe', 'js', 'jse',
1087
+ 'wsf', 'wsh', 'msi', 'msp', 'mst', 'reg', 'scf', 'lnk', 'inf',
1088
+ 'php', 'php3', 'php4', 'php5', 'phtml', 'pl', 'py', 'rb', 'sh'
1089
+ ];
1090
+ const cleanExt = extension.toLowerCase().replace(/^\./, '');
1091
+ if (dangerousExtensions.includes(cleanExt)) {
1092
+ throw new InvalidInputError(`File extension '${extension}' is not allowed for security reasons`, 'extension');
1093
+ }
1094
+ // Ensure extension is alphanumeric with some common safe characters
1095
+ if (!/^[a-z0-9_-]+$/i.test(cleanExt)) {
1096
+ throw new InvalidInputError(`Invalid file extension format: ${extension}`, 'extension');
1097
+ }
1098
+ }
1099
+ validateMediaMetadata(metadata) {
1100
+ // Validate required fields
1101
+ if (!metadata.id || !metadata.filename || !metadata.contentType || !metadata.extension) {
1102
+ throw new InvalidInputError('Missing required metadata fields', 'metadata');
1103
+ }
1104
+ // Validate file size
1105
+ if (metadata.size > this.MAX_FILE_SIZE) {
1106
+ throw new InvalidInputError(`File size ${metadata.size} exceeds maximum allowed size of ${this.MAX_FILE_SIZE} bytes`, 'size');
1107
+ }
1108
+ // Validate filename format
1109
+ if (!/^[a-zA-Z0-9._-]+$/.test(metadata.filename)) {
1110
+ throw new InvalidInputError('Invalid filename format. Only alphanumeric characters, dots, underscores, and hyphens are allowed', 'filename');
1111
+ }
1112
+ // Validate content type format
1113
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_.]*$/.test(metadata.contentType)) {
1114
+ throw new InvalidInputError('Invalid content type format', 'contentType');
1115
+ }
1116
+ }
1117
+ safeParseJSON(content, reviver) {
1118
+ // Check content size to prevent DoS attacks
1119
+ if (content.length > this.MAX_DOCUMENT_SIZE) {
1120
+ throw new InvalidInputError(`JSON content size ${content.length} exceeds maximum allowed size of ${this.MAX_DOCUMENT_SIZE} bytes`, 'size');
1121
+ }
1122
+ try {
1123
+ return JSON.parse(content, reviver);
1124
+ }
1125
+ catch (error) {
1126
+ throw new InvalidInputError(`Invalid JSON format: ${error}`, 'json');
1127
+ }
1128
+ }
1129
+ async atomicWriteFile(filePath, content) {
1130
+ // Write to temporary file first, then rename to prevent race conditions
1131
+ const tempPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substr(2, 9)}`;
1132
+ try {
1133
+ await fs.writeFile(tempPath, content, {
1134
+ mode: this.config.fileMode,
1135
+ flag: 'wx' // Fail if file exists
1136
+ });
1137
+ // Atomic rename
1138
+ await fs.rename(tempPath, filePath);
1139
+ }
1140
+ catch (error) {
1141
+ // Clean up temp file if it exists
1142
+ try {
1143
+ await fs.unlink(tempPath);
1144
+ }
1145
+ catch {
1146
+ // Ignore cleanup errors
1147
+ }
1148
+ throw error;
1149
+ }
1150
+ }
1151
+ dateReplacer = (key, value) => {
1152
+ // Convert Date objects to ISO strings
1153
+ if (value instanceof Date) {
1154
+ return value.toISOString();
1155
+ }
1156
+ return value;
1157
+ };
1158
+ dateReviver = (key, value) => {
1159
+ // Convert ISO date strings back to Date objects
1160
+ if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
1161
+ return new Date(value);
1162
+ }
1163
+ return value;
1164
+ };
1165
+ reviveDatesInObject(obj) {
1166
+ if (obj === null || typeof obj !== 'object') {
1167
+ return obj;
1168
+ }
1169
+ if (Array.isArray(obj)) {
1170
+ return obj.map(item => this.reviveDatesInObject(item));
1171
+ }
1172
+ const result = {};
1173
+ for (const [key, value] of Object.entries(obj)) {
1174
+ if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
1175
+ // console.log(`Converting date string ${value} to Date object`)
1176
+ result[key] = new Date(value);
1177
+ }
1178
+ else if (typeof value === 'object' && value !== null) {
1179
+ result[key] = this.reviveDatesInObject(value);
1180
+ }
1181
+ else {
1182
+ result[key] = value;
1183
+ }
1184
+ }
1185
+ return result;
1186
+ }
1187
+ async fileToBuffer(file) {
1188
+ // Convert File to Buffer for Node.js filesystem
1189
+ const arrayBuffer = await file.arrayBuffer();
1190
+ return Buffer.from(arrayBuffer);
1191
+ }
1192
+ matchesFilter(document, filter) {
1193
+ for (const [field, value] of Object.entries(filter)) {
1194
+ const docValue = document[field];
1195
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1196
+ // Handle comparison operators
1197
+ for (const [op, compareValue] of Object.entries(value)) {
1198
+ switch (op) {
1199
+ case '$eq':
1200
+ if (docValue !== compareValue)
1201
+ return false;
1202
+ break;
1203
+ case '$ne':
1204
+ if (docValue === compareValue)
1205
+ return false;
1206
+ break;
1207
+ case '$gt':
1208
+ if (typeof docValue !== 'number' || typeof compareValue !== 'number' || docValue <= compareValue)
1209
+ return false;
1210
+ break;
1211
+ case '$gte':
1212
+ if (typeof docValue !== 'number' || typeof compareValue !== 'number' || docValue < compareValue)
1213
+ return false;
1214
+ break;
1215
+ case '$lt':
1216
+ if (typeof docValue !== 'number' || typeof compareValue !== 'number' || docValue >= compareValue)
1217
+ return false;
1218
+ break;
1219
+ case '$lte':
1220
+ if (typeof docValue !== 'number' || typeof compareValue !== 'number' || docValue > compareValue)
1221
+ return false;
1222
+ break;
1223
+ case '$in':
1224
+ if (!Array.isArray(compareValue) || !compareValue.includes(docValue))
1225
+ return false;
1226
+ break;
1227
+ case '$nin':
1228
+ if (!Array.isArray(compareValue) || compareValue.includes(docValue))
1229
+ return false;
1230
+ break;
1231
+ default:
1232
+ // Unknown operator, skip
1233
+ break;
1234
+ }
1235
+ }
1236
+ }
1237
+ else {
1238
+ // Direct value comparison
1239
+ if (docValue !== value)
1240
+ return false;
1241
+ }
1242
+ }
1243
+ return true;
1244
+ }
1245
+ sortDocuments(documents, sortFields) {
1246
+ return documents.sort((a, b) => {
1247
+ for (const sortField of sortFields) {
1248
+ const [field, direction = 'asc'] = sortField.split('.');
1249
+ const aValue = a[field];
1250
+ const bValue = b[field];
1251
+ let comparison = 0;
1252
+ if (aValue < bValue)
1253
+ comparison = -1;
1254
+ else if (aValue > bValue)
1255
+ comparison = 1;
1256
+ if (direction === 'desc')
1257
+ comparison *= -1;
1258
+ if (comparison !== 0)
1259
+ return comparison;
1260
+ }
1261
+ return 0;
1262
+ });
1263
+ }
1264
+ // Variant file operations
1265
+ async saveVariantFile(parentId, variantName, buffer, format) {
1266
+ try {
1267
+ // Create variants directory if it doesn't exist
1268
+ const variantsDir = path.join(this.config.mediaDir, 'variants');
1269
+ await fsExtra.ensureDir(variantsDir, { mode: this.config.dirMode });
1270
+ // Create parent directory for this image's variants
1271
+ const parentVariantsDir = path.join(variantsDir, parentId);
1272
+ await fsExtra.ensureDir(parentVariantsDir, { mode: this.config.dirMode });
1273
+ // Save variant file
1274
+ const variantFilename = `${variantName}.${format}`;
1275
+ const variantPath = path.join(parentVariantsDir, variantFilename);
1276
+ await fs.writeFile(variantPath, buffer);
1277
+ // Return relative path for URL generation
1278
+ return `variants/${parentId}/${variantFilename}`;
1279
+ }
1280
+ catch (error) {
1281
+ console.error('[ERROR] FilesystemAdapter.saveVariantFile failed:', error);
1282
+ throw new Error(`Failed to save variant file ${parentId}/${variantName}: ${error}`);
1283
+ }
1284
+ }
1285
+ getVariantUrl(parentId, variantName) {
1286
+ // No URL generation - let frontend handle URL construction
1287
+ return '';
1288
+ }
1289
+ updateVariantUrls(imageVariants, parentId) {
1290
+ // Update variant URLs to use the new API format
1291
+ const updatedVariants = {};
1292
+ for (const [variantName, variantData] of Object.entries(imageVariants)) {
1293
+ updatedVariants[variantName] = {
1294
+ ...variantData,
1295
+ url: this.getVariantUrl(parentId, variantName)
1296
+ };
1297
+ }
1298
+ return updatedVariants;
1299
+ }
1300
+ async getVariantContent(parentId, variantName) {
1301
+ try {
1302
+ // First check if the parent media file exists
1303
+ const mediaFile = await this.getFile(parentId);
1304
+ if (!mediaFile) {
1305
+ return null;
1306
+ }
1307
+ // Get variant info from metadata to determine format
1308
+ const variants = mediaFile.metadata?.imageVariants;
1309
+ if (!variants || !variants[variantName]) {
1310
+ return null;
1311
+ }
1312
+ const variantInfo = variants[variantName];
1313
+ const variantFilename = `${variantName}.${variantInfo.format || 'webp'}`;
1314
+ const variantPath = path.join(this.config.mediaDir, 'variants', parentId, variantFilename);
1315
+ // Security check: ensure the resolved path is still within the media directory
1316
+ const resolvedPath = path.resolve(variantPath);
1317
+ const mediaBaseDir = path.resolve(this.config.mediaDir);
1318
+ if (!resolvedPath.startsWith(mediaBaseDir)) {
1319
+ throw new Error('Access denied: path traversal detected');
1320
+ }
1321
+ // Check if variant file exists
1322
+ try {
1323
+ await fs.access(resolvedPath, constants.F_OK);
1324
+ const stats = await fs.stat(resolvedPath);
1325
+ if (!stats.isFile()) {
1326
+ return null;
1327
+ }
1328
+ }
1329
+ catch {
1330
+ return null;
1331
+ }
1332
+ // Read and return variant file content
1333
+ const buffer = await fs.readFile(resolvedPath);
1334
+ const sliced = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
1335
+ // Ensure we return ArrayBuffer, not SharedArrayBuffer
1336
+ if (sliced instanceof ArrayBuffer) {
1337
+ return sliced;
1338
+ }
1339
+ else {
1340
+ // Copy SharedArrayBuffer to ArrayBuffer
1341
+ const sharedBuffer = sliced;
1342
+ const arrayBuffer = new ArrayBuffer(sharedBuffer.byteLength);
1343
+ new Uint8Array(arrayBuffer).set(new Uint8Array(sharedBuffer));
1344
+ return arrayBuffer;
1345
+ }
1346
+ }
1347
+ catch (error) {
1348
+ console.error(`[ERROR] FilesystemAdapter.getVariantContent failed for ${parentId}/${variantName}:`, error);
1349
+ return null;
1350
+ }
1351
+ }
1352
+ async deleteVariantFiles(parentId) {
1353
+ try {
1354
+ const parentVariantsDir = path.join(this.config.mediaDir, 'variants', parentId);
1355
+ // Check if directory exists before trying to delete
1356
+ try {
1357
+ await fs.access(parentVariantsDir, constants.F_OK);
1358
+ await fs.rm(parentVariantsDir, { recursive: true, force: true });
1359
+ }
1360
+ catch {
1361
+ // Directory doesn't exist, nothing to delete
1362
+ }
1363
+ }
1364
+ catch (error) {
1365
+ console.error('[ERROR] FilesystemAdapter.deleteVariantFiles failed:', error);
1366
+ throw new Error(`Failed to delete variant files for ${parentId}: ${error}`);
1367
+ }
1368
+ }
1369
+ }
1370
+ //# sourceMappingURL=filesystem-adapter.js.map