@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,1363 @@
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, createLogger } from '../../core/index.js';
5
+ export class FilesystemDataAdapter {
6
+ /** Resolves once the configured directories exist. Rejects if they could not be created. */
7
+ ready;
8
+ config;
9
+ logger = createLogger('adapter', 'FilesystemDataAdapter');
10
+ // Security limits
11
+ MAX_DOCUMENT_SIZE = 10 * 1024 * 1024; // 10MB
12
+ // Ceiling on any list operation. Matches the postgres adapter's MAX_LIST_LIMIT
13
+ // so both backends agree on how much a single call can return.
14
+ MAX_LIST_LIMIT = 1000;
15
+ userWriteLocks = new Map();
16
+ constructor(config = {}) {
17
+ this.config = {
18
+ contentDir: config.contentDir || './content',
19
+ usersDir: config.usersDir || './users',
20
+ tokensDir: config.tokensDir || './tokens',
21
+ webhooksDir: config.webhooksDir || './webhooks',
22
+ settingsDir: config.settingsDir || './settings',
23
+ auditLogsDir: config.auditLogsDir || './audit-logs',
24
+ createDirs: config.createDirs ?? true,
25
+ prettyJson: config.prettyJson ?? true,
26
+ jsonSpaces: config.jsonSpaces ?? 2,
27
+ syncWrites: config.syncWrites ?? false,
28
+ fileMode: config.fileMode ?? 0o644,
29
+ dirMode: config.dirMode ?? 0o755,
30
+ silent: config.silent ?? false
31
+ };
32
+ // Directory creation is async and a constructor cannot await it. Keeping the promise
33
+ // gives callers something to wait on: without it the adapter can still be creating
34
+ // its directories after a caller believes it is done, which is how a teardown ends up
35
+ // racing ensureDir and failing with ENOTEMPTY. The catch only stops an early failure
36
+ // becoming an unhandled rejection; `ready` still rejects for whoever awaits it.
37
+ this.ready = this.config.createDirs ? this.initializeDirectories() : Promise.resolve();
38
+ this.ready.catch(() => undefined);
39
+ if (!this.config.silent) {
40
+ this.logger.info('FilesystemDataAdapter initialized', {
41
+ contentDir: this.config.contentDir,
42
+ usersDir: this.config.usersDir,
43
+ tokensDir: this.config.tokensDir
44
+ });
45
+ }
46
+ }
47
+ async initializeDirectories() {
48
+ try {
49
+ await fsExtra.ensureDir(this.config.contentDir, { mode: this.config.dirMode });
50
+ await fsExtra.ensureDir(this.config.usersDir, { mode: this.config.dirMode });
51
+ await fsExtra.ensureDir(this.config.tokensDir, { mode: this.config.dirMode });
52
+ await fsExtra.ensureDir(this.config.webhooksDir, { mode: this.config.dirMode });
53
+ await fsExtra.ensureDir(this.config.settingsDir, { mode: this.config.dirMode });
54
+ await fsExtra.ensureDir(this.config.auditLogsDir, { mode: this.config.dirMode });
55
+ }
56
+ catch (error) {
57
+ if (!this.config.silent) {
58
+ this.logger.error('Failed to initialize directories', error);
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ // ==========================================================================
64
+ // DOCUMENT OPERATIONS
65
+ // ==========================================================================
66
+ async getDocument(collection, id) {
67
+ try {
68
+ SecurityValidator.validateCollectionName(collection);
69
+ SecurityValidator.validateDocumentId(id);
70
+ const filePath = this.getDocumentPath(collection, id);
71
+ try {
72
+ await fs.access(filePath, constants.F_OK);
73
+ }
74
+ catch {
75
+ return null; // File doesn't exist
76
+ }
77
+ const fileContent = await fs.readFile(filePath, 'utf8');
78
+ const documentFile = JSON.parse(fileContent);
79
+ return this.documentFileToDocument(documentFile, collection);
80
+ }
81
+ catch (error) {
82
+ this.logger.error(`Failed to get document ${id} from collection ${collection}`, error);
83
+ throw new Error(`Failed to get document: ${error instanceof Error ? error.message : 'Unknown error'}`);
84
+ }
85
+ }
86
+ async documentExists(collection, id) {
87
+ SecurityValidator.validateCollectionName(collection);
88
+ SecurityValidator.validateDocumentId(id);
89
+ try {
90
+ await fs.access(this.getDocumentPath(collection, id), constants.F_OK);
91
+ return true;
92
+ }
93
+ catch {
94
+ // Missing file, or a collection directory that was never created.
95
+ return false;
96
+ }
97
+ }
98
+ async saveDocument(collection, id, data, auditContext) {
99
+ try {
100
+ SecurityValidator.validateCollectionName(collection);
101
+ SecurityValidator.validateDocumentId(id);
102
+ const collectionDir = path.join(this.config.contentDir, collection);
103
+ await fsExtra.ensureDir(collectionDir, { mode: this.config.dirMode });
104
+ const filePath = this.getDocumentPath(collection, id);
105
+ let documentFile;
106
+ // Check if document exists
107
+ let isUpdate = false;
108
+ try {
109
+ await fs.access(filePath, constants.F_OK);
110
+ const existingContent = await fs.readFile(filePath, 'utf8');
111
+ const existingDoc = JSON.parse(existingContent);
112
+ // Document exists, this is an update
113
+ isUpdate = true;
114
+ // Use _status from data if explicitly provided, otherwise preserve existing status
115
+ const status = data._status ?? existingDoc.metadata.status;
116
+ documentFile = {
117
+ id,
118
+ collection,
119
+ data,
120
+ metadata: {
121
+ createdAt: existingDoc.metadata.createdAt,
122
+ updatedAt: new Date(),
123
+ revision: existingDoc.metadata.revision + 1,
124
+ status,
125
+ // Preserve original creator, update current user
126
+ createdBy: existingDoc.metadata.createdBy,
127
+ createdByType: existingDoc.metadata.createdByType,
128
+ updatedBy: auditContext?.userId,
129
+ updatedByType: auditContext?.userType
130
+ }
131
+ };
132
+ }
133
+ catch {
134
+ // Document doesn't exist, this is a new document
135
+ // Extract _status from data if provided (for restore), otherwise default to 'draft'
136
+ const status = data._status || 'draft';
137
+ documentFile = {
138
+ id,
139
+ collection,
140
+ data,
141
+ metadata: {
142
+ createdAt: new Date(),
143
+ updatedAt: new Date(),
144
+ revision: 1,
145
+ status,
146
+ createdBy: auditContext?.userId,
147
+ createdByType: auditContext?.userType,
148
+ updatedBy: auditContext?.userId,
149
+ updatedByType: auditContext?.userType
150
+ }
151
+ };
152
+ }
153
+ // Validate content size
154
+ const jsonContent = this.formatJson(documentFile);
155
+ if (jsonContent.length > this.MAX_DOCUMENT_SIZE) {
156
+ throw new InvalidInputError(`Document content too large (${jsonContent.length} bytes). Maximum size is ${this.MAX_DOCUMENT_SIZE} bytes.`, 'size');
157
+ }
158
+ // Write document file
159
+ await this.atomicWriteFile(filePath, jsonContent);
160
+ if (!this.config.silent) {
161
+ this.logger.info(`Document ${isUpdate ? 'updated' : 'created'}`, { collection, id, revision: documentFile.metadata.revision });
162
+ }
163
+ return this.documentFileToDocument(documentFile, collection);
164
+ }
165
+ catch (error) {
166
+ this.logger.error(`Failed to save document ${id} in collection ${collection}`, error);
167
+ throw error;
168
+ }
169
+ }
170
+ async listDocuments(collection, options = {}) {
171
+ try {
172
+ SecurityValidator.validateCollectionName(collection);
173
+ const collectionDir = path.join(this.config.contentDir, collection);
174
+ // Check if collection directory exists
175
+ try {
176
+ await fs.access(collectionDir, constants.F_OK);
177
+ }
178
+ catch {
179
+ return []; // Collection doesn't exist
180
+ }
181
+ const files = await fs.readdir(collectionDir);
182
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
183
+ // Read all documents
184
+ const documents = [];
185
+ for (const file of jsonFiles) {
186
+ try {
187
+ const filePath = path.join(collectionDir, file);
188
+ const fileContent = await fs.readFile(filePath, 'utf8');
189
+ const documentFile = JSON.parse(fileContent);
190
+ documents.push(this.documentFileToDocument(documentFile, collection));
191
+ }
192
+ catch (error) {
193
+ this.logger.warn(`Skipping invalid document file ${file}`, error);
194
+ continue;
195
+ }
196
+ }
197
+ // Apply filters
198
+ let filteredDocuments = documents;
199
+ if (options.filter) {
200
+ filteredDocuments = documents.filter(doc => {
201
+ for (const [key, value] of Object.entries(options.filter)) {
202
+ if (doc[key] !== value) {
203
+ return false;
204
+ }
205
+ }
206
+ return true;
207
+ });
208
+ }
209
+ // Apply sorting - default to newest first (_createdAt desc)
210
+ if (options.sort) {
211
+ const sortFields = Array.isArray(options.sort) ? options.sort : [options.sort];
212
+ filteredDocuments.sort((a, b) => {
213
+ for (const field of sortFields) {
214
+ let fieldName = field;
215
+ let direction = 'asc';
216
+ // Handle descending sort (field prefixed with -)
217
+ // Two grammars reach here. The routes layer normalises every incoming sort into
218
+ // `field.asc` / `field.desc` (routes/handlers/documents.ts), which is what the
219
+ // postgres adapter parses. This adapter understood only the `-field` form, so it
220
+ // looked for a property literally named "field.asc", found none, and returned
221
+ // documents unsorted -- silently, for every API request.
222
+ if (field.startsWith('-')) {
223
+ fieldName = field.substring(1);
224
+ direction = 'desc';
225
+ }
226
+ else if (field.includes('.')) {
227
+ const [name, dir] = field.split('.');
228
+ fieldName = name;
229
+ direction = dir?.toLowerCase() === 'desc' ? 'desc' : 'asc';
230
+ }
231
+ const aVal = a[fieldName];
232
+ const bVal = b[fieldName];
233
+ let comparison = 0;
234
+ if (aVal < bVal)
235
+ comparison = -1;
236
+ if (aVal > bVal)
237
+ comparison = 1;
238
+ if (comparison !== 0) {
239
+ return direction === 'desc' ? -comparison : comparison;
240
+ }
241
+ }
242
+ return 0;
243
+ });
244
+ }
245
+ else {
246
+ // Default sort: newest first (_createdAt descending)
247
+ filteredDocuments.sort((a, b) => {
248
+ const aDate = new Date(a._createdAt).getTime();
249
+ const bDate = new Date(b._createdAt).getTime();
250
+ return bDate - aDate; // Descending order (newest first)
251
+ });
252
+ }
253
+ // Apply pagination. No implicit default limit -- an unbounded call returns
254
+ // everything -- but never more than MAX_LIST_LIMIT rows in one response.
255
+ const { offset = 0, limit } = options;
256
+ const start = offset;
257
+ const effectiveLimit = Math.min(limit || this.MAX_LIST_LIMIT, this.MAX_LIST_LIMIT);
258
+ return filteredDocuments.slice(start, start + effectiveLimit);
259
+ }
260
+ catch (error) {
261
+ this.logger.error(`Failed to list documents in collection ${collection}`, error);
262
+ throw new Error(`Failed to list documents: ${error instanceof Error ? error.message : 'Unknown error'}`);
263
+ }
264
+ }
265
+ async countDocuments(collection, filter) {
266
+ try {
267
+ SecurityValidator.validateCollectionName(collection);
268
+ const collectionDir = path.join(this.config.contentDir, collection);
269
+ try {
270
+ await fs.access(collectionDir, constants.F_OK);
271
+ }
272
+ catch {
273
+ return 0;
274
+ }
275
+ const files = await fs.readdir(collectionDir);
276
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
277
+ if (!filter || Object.keys(filter).length === 0) {
278
+ return jsonFiles.length;
279
+ }
280
+ // With filter, we need to read files to check values
281
+ let count = 0;
282
+ for (const file of jsonFiles) {
283
+ try {
284
+ const filePath = path.join(collectionDir, file);
285
+ const fileContent = await fs.readFile(filePath, 'utf8');
286
+ const documentFile = JSON.parse(fileContent);
287
+ const doc = this.documentFileToDocument(documentFile, collection);
288
+ // Same strict, type-preserving equality as listDocuments, now against
289
+ // the full document shape rather than a four-field projection.
290
+ const matches = Object.entries(filter).every(([key, value]) => doc[key] === value);
291
+ if (matches)
292
+ count++;
293
+ }
294
+ catch {
295
+ continue;
296
+ }
297
+ }
298
+ return count;
299
+ }
300
+ catch (error) {
301
+ this.logger.error(`Failed to count documents in collection ${collection}`, error);
302
+ throw error;
303
+ }
304
+ }
305
+ async deleteDocument(collection, id) {
306
+ try {
307
+ SecurityValidator.validateCollectionName(collection);
308
+ SecurityValidator.validateDocumentId(id);
309
+ const filePath = this.getDocumentPath(collection, id);
310
+ try {
311
+ await fs.unlink(filePath);
312
+ if (!this.config.silent) {
313
+ this.logger.info('Document deleted', { collection, id });
314
+ }
315
+ }
316
+ catch (error) {
317
+ if (error.code === 'ENOENT') {
318
+ throw new Error(`Document ${id} not found in collection ${collection}`);
319
+ }
320
+ throw error;
321
+ }
322
+ }
323
+ catch (error) {
324
+ this.logger.error(`Failed to delete document ${id} from collection ${collection}`, error);
325
+ throw error;
326
+ }
327
+ }
328
+ // ==========================================================================
329
+ // USER OPERATIONS
330
+ // ==========================================================================
331
+ async getUser(id) {
332
+ try {
333
+ SecurityValidator.validateDocumentId(id);
334
+ const filePath = this.getUserPath(id);
335
+ try {
336
+ await fs.access(filePath, constants.F_OK);
337
+ }
338
+ catch {
339
+ return null;
340
+ }
341
+ const fileContent = await fs.readFile(filePath, 'utf8');
342
+ const userFile = JSON.parse(fileContent);
343
+ return this.userFileToUser(userFile);
344
+ }
345
+ catch (error) {
346
+ this.logger.error(`Failed to get user ${id}`, error);
347
+ throw new Error(`Failed to get user: ${error instanceof Error ? error.message : 'Unknown error'}`);
348
+ }
349
+ }
350
+ async saveUser(id, userData) {
351
+ SecurityValidator.validateDocumentId(id);
352
+ return this.withUserLock(id, () => this.writeUserFile(id, userData));
353
+ }
354
+ async saveUserIf(id, userData, condition) {
355
+ SecurityValidator.validateDocumentId(id);
356
+ return this.withUserLock(id, async () => {
357
+ const filePath = this.getUserPath(id);
358
+ let existingUser;
359
+ try {
360
+ const existingContent = await fs.readFile(filePath, 'utf8');
361
+ existingUser = JSON.parse(existingContent);
362
+ }
363
+ catch (error) {
364
+ // A missing user is a condition miss; anything else (permissions, I/O,
365
+ // corrupt JSON) is a storage failure and must surface as one.
366
+ if (error.code === 'ENOENT') {
367
+ return null;
368
+ }
369
+ this.logger.error(`Failed to read user ${id} for conditional update`, error);
370
+ throw new Error(`Failed to read user: ${error instanceof Error ? error.message : 'Unknown error'}`);
371
+ }
372
+ if (existingUser.passwordHash !== condition.passwordHash) {
373
+ return null;
374
+ }
375
+ return this.writeUserFile(id, userData);
376
+ });
377
+ }
378
+ async writeUserFile(id, userData) {
379
+ try {
380
+ await fsExtra.ensureDir(this.config.usersDir, { mode: this.config.dirMode });
381
+ const filePath = this.getUserPath(id);
382
+ let userFile;
383
+ let isUpdate = false;
384
+ // Check if user exists
385
+ try {
386
+ await fs.access(filePath, constants.F_OK);
387
+ const existingContent = await fs.readFile(filePath, 'utf8');
388
+ const existingUser = JSON.parse(existingContent);
389
+ // User exists, this is an update
390
+ isUpdate = true;
391
+ const updateData = userData;
392
+ // A plain spread copies explicitly-`undefined` keys over the existing
393
+ // value, which would clear a field the caller never meant to touch.
394
+ // Only keys that are actually present AND not `undefined` take effect;
395
+ // an explicit `null` still gets through, and clears the field.
396
+ const presentUpdates = Object.fromEntries(Object.entries(updateData).filter(([, value]) => value !== undefined));
397
+ userFile = {
398
+ ...existingUser,
399
+ ...presentUpdates,
400
+ id,
401
+ updatedAt: new Date().toISOString()
402
+ };
403
+ }
404
+ catch {
405
+ // User doesn't exist, this is a new user.
406
+ // Persist every supplied field: the create branch used to build the
407
+ // record field by field and silently drop `mfa`, `passkeys` and
408
+ // `lastLoginAt`, so `trokky restore` lost them.
409
+ const createData = userData;
410
+ userFile = {
411
+ id,
412
+ username: createData.username,
413
+ email: createData.email,
414
+ passwordHash: createData.passwordHash || '', // Use provided passwordHash from caller
415
+ firstName: createData.firstName,
416
+ lastName: createData.lastName,
417
+ role: createData.role,
418
+ permissions: createData.permissions || [],
419
+ isActive: createData.isActive ?? true,
420
+ profileImage: createData.profileImage,
421
+ preferences: createData.preferences,
422
+ oauthProviders: createData.oauthProviders,
423
+ mfa: createData.mfa,
424
+ passkeys: createData.passkeys,
425
+ lastLoginAt: createData.lastLoginAt,
426
+ createdAt: new Date().toISOString(),
427
+ updatedAt: new Date().toISOString()
428
+ };
429
+ }
430
+ // Write user file
431
+ const jsonContent = this.formatJson(userFile);
432
+ await this.atomicWriteFile(filePath, jsonContent);
433
+ if (!this.config.silent) {
434
+ this.logger.info(`User ${isUpdate ? 'updated' : 'created'}`, {
435
+ id,
436
+ username: userFile.username,
437
+ email: userFile.email
438
+ });
439
+ }
440
+ return this.userFileToUser(userFile);
441
+ }
442
+ catch (error) {
443
+ this.logger.error(`Failed to save user ${id}`, error);
444
+ throw error;
445
+ }
446
+ }
447
+ async listUsers(options = {}) {
448
+ try {
449
+ // Ensure users directory exists
450
+ try {
451
+ await fs.access(this.config.usersDir, constants.F_OK);
452
+ }
453
+ catch {
454
+ return [];
455
+ }
456
+ const files = await fs.readdir(this.config.usersDir);
457
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
458
+ const users = [];
459
+ for (const file of jsonFiles) {
460
+ try {
461
+ const filePath = path.join(this.config.usersDir, file);
462
+ const fileContent = await fs.readFile(filePath, 'utf8');
463
+ const userFile = JSON.parse(fileContent);
464
+ const user = this.userFileToUser(userFile);
465
+ users.push(user);
466
+ }
467
+ catch (error) {
468
+ this.logger.warn(`Skipping invalid user file ${file}`, error);
469
+ continue;
470
+ }
471
+ }
472
+ // Apply filters
473
+ let filteredUsers = users;
474
+ if (options.role) {
475
+ filteredUsers = filteredUsers.filter(user => user.role === options.role);
476
+ }
477
+ if (options.isActive !== undefined) {
478
+ filteredUsers = filteredUsers.filter(user => user.isActive === options.isActive);
479
+ }
480
+ // Order newest first. Directory order is whatever readdir returns, which
481
+ // is arbitrary and differs from the postgres adapter's `createdAt DESC`.
482
+ filteredUsers.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
483
+ // Apply pagination. No implicit default limit, capped at MAX_LIST_LIMIT.
484
+ const { offset = 0, limit } = options;
485
+ const start = offset;
486
+ const effectiveLimit = Math.min(limit || this.MAX_LIST_LIMIT, this.MAX_LIST_LIMIT);
487
+ return filteredUsers.slice(start, start + effectiveLimit);
488
+ }
489
+ catch (error) {
490
+ this.logger.error('Failed to list users', error);
491
+ throw new Error(`Failed to list users: ${error instanceof Error ? error.message : 'Unknown error'}`);
492
+ }
493
+ }
494
+ async deleteUser(id) {
495
+ try {
496
+ SecurityValidator.validateDocumentId(id);
497
+ const filePath = this.getUserPath(id);
498
+ try {
499
+ await fs.unlink(filePath);
500
+ if (!this.config.silent) {
501
+ this.logger.info('User deleted', { id });
502
+ }
503
+ }
504
+ catch (error) {
505
+ if (error.code === 'ENOENT') {
506
+ throw new Error(`User ${id} not found`);
507
+ }
508
+ throw error;
509
+ }
510
+ }
511
+ catch (error) {
512
+ this.logger.error(`Failed to delete user ${id}`, error);
513
+ throw error;
514
+ }
515
+ }
516
+ async getUserByUsername(username) {
517
+ try {
518
+ const users = await this.listUsers();
519
+ return users.find(user => user.username === username) || null;
520
+ }
521
+ catch (error) {
522
+ this.logger.error(`Failed to get user by username ${username}`, error);
523
+ throw new Error(`Failed to get user by username: ${error instanceof Error ? error.message : 'Unknown error'}`);
524
+ }
525
+ }
526
+ async getUserByEmail(email) {
527
+ try {
528
+ const users = await this.listUsers();
529
+ return users.find(user => user.email === email) || null;
530
+ }
531
+ catch (error) {
532
+ this.logger.error(`Failed to get user by email ${email}`, error);
533
+ throw new Error(`Failed to get user by email: ${error instanceof Error ? error.message : 'Unknown error'}`);
534
+ }
535
+ }
536
+ async isUsernameAvailable(username) {
537
+ const user = await this.getUserByUsername(username);
538
+ return user === null;
539
+ }
540
+ async isEmailAvailable(email) {
541
+ const user = await this.getUserByEmail(email);
542
+ return user === null;
543
+ }
544
+ async getUserByOAuthProvider(provider, providerId) {
545
+ if (!provider || !providerId) {
546
+ return null;
547
+ }
548
+ try {
549
+ const users = await this.listUsers();
550
+ return users.find(user => user.oauthProviders?.some(p => p.provider === provider && p.providerId === providerId)) || null;
551
+ }
552
+ catch (error) {
553
+ this.logger.error(`Failed to get user by OAuth provider ${provider}:${providerId}`, error);
554
+ throw new Error(`Failed to get user by OAuth provider: ${error instanceof Error ? error.message : 'Unknown error'}`);
555
+ }
556
+ }
557
+ // ==========================================================================
558
+ // APP TOKEN OPERATIONS
559
+ // ==========================================================================
560
+ async getAppToken(id) {
561
+ try {
562
+ SecurityValidator.validateDocumentId(id);
563
+ const filePath = this.getTokenPath(id);
564
+ try {
565
+ await fs.access(filePath, constants.F_OK);
566
+ }
567
+ catch {
568
+ return null;
569
+ }
570
+ const fileContent = await fs.readFile(filePath, 'utf8');
571
+ const tokenFile = JSON.parse(fileContent);
572
+ return this.tokenFileToAppToken(tokenFile);
573
+ }
574
+ catch (error) {
575
+ this.logger.error(`Failed to get app token ${id}`, error);
576
+ throw new Error(`Failed to get app token: ${error instanceof Error ? error.message : 'Unknown error'}`);
577
+ }
578
+ }
579
+ async saveAppToken(id, tokenData) {
580
+ try {
581
+ SecurityValidator.validateDocumentId(id);
582
+ await fsExtra.ensureDir(this.config.tokensDir, { mode: this.config.dirMode });
583
+ const filePath = this.getTokenPath(id);
584
+ let tokenFile;
585
+ let isUpdate = false;
586
+ // Check if token exists
587
+ try {
588
+ await fs.access(filePath, constants.F_OK);
589
+ const existingContent = await fs.readFile(filePath, 'utf8');
590
+ const existingToken = JSON.parse(existingContent);
591
+ // Token exists, this is an update
592
+ isUpdate = true;
593
+ const updateData = tokenData;
594
+ tokenFile = {
595
+ ...existingToken,
596
+ ...updateData,
597
+ id,
598
+ updatedAt: new Date().toISOString()
599
+ };
600
+ }
601
+ catch {
602
+ // Token doesn't exist, this is a new token
603
+ // Check if we received a complete AppToken object (from core engine)
604
+ if ('tokenHash' in tokenData && 'createdBy' in tokenData) {
605
+ // Complete AppToken object from core engine
606
+ const completeToken = tokenData;
607
+ tokenFile = {
608
+ ...completeToken,
609
+ id // Ensure ID matches
610
+ };
611
+ }
612
+ else {
613
+ // CreateAppTokenData from API
614
+ const createData = tokenData;
615
+ tokenFile = {
616
+ id,
617
+ name: createData.name,
618
+ description: createData.description,
619
+ tokenHash: createData.tokenHash || '', // Use provided tokenHash from caller
620
+ permissions: createData.permissions,
621
+ createdBy: createData.createdBy || '', // Use provided createdBy from caller
622
+ isActive: true,
623
+ usageCount: 0,
624
+ expiresAt: createData.expiresAt,
625
+ createdAt: new Date().toISOString(),
626
+ updatedAt: new Date().toISOString()
627
+ };
628
+ }
629
+ }
630
+ // Write token file
631
+ const jsonContent = this.formatJson(tokenFile);
632
+ await this.atomicWriteFile(filePath, jsonContent);
633
+ if (!this.config.silent) {
634
+ this.logger.debug(`App token ${isUpdate ? 'updated' : 'created'}`, {
635
+ id,
636
+ name: tokenFile.name
637
+ });
638
+ }
639
+ return this.tokenFileToAppToken(tokenFile);
640
+ }
641
+ catch (error) {
642
+ this.logger.error(`Failed to save app token ${id}`, error);
643
+ throw error;
644
+ }
645
+ }
646
+ async listAppTokens(options = {}) {
647
+ try {
648
+ // Ensure tokens directory exists
649
+ try {
650
+ await fs.access(this.config.tokensDir, constants.F_OK);
651
+ }
652
+ catch {
653
+ return [];
654
+ }
655
+ const files = await fs.readdir(this.config.tokensDir);
656
+ const jsonFiles = files.filter(file => file.endsWith('.json'));
657
+ const tokens = [];
658
+ for (const file of jsonFiles) {
659
+ try {
660
+ const filePath = path.join(this.config.tokensDir, file);
661
+ const fileContent = await fs.readFile(filePath, 'utf8');
662
+ const tokenFile = JSON.parse(fileContent);
663
+ const token = this.tokenFileToAppToken(tokenFile);
664
+ tokens.push(token);
665
+ }
666
+ catch (error) {
667
+ this.logger.warn(`Skipping invalid token file ${file}`, error);
668
+ continue;
669
+ }
670
+ }
671
+ // Apply filters
672
+ let filteredTokens = tokens;
673
+ if (options.createdBy) {
674
+ filteredTokens = filteredTokens.filter(token => token.createdBy === options.createdBy);
675
+ }
676
+ if (options.isActive !== undefined) {
677
+ filteredTokens = filteredTokens.filter(token => token.isActive === options.isActive);
678
+ }
679
+ // Apply pagination
680
+ const { offset = 0, limit } = options;
681
+ const start = offset;
682
+ const end = limit ? start + limit : undefined;
683
+ return filteredTokens.slice(start, end);
684
+ }
685
+ catch (error) {
686
+ this.logger.error('Failed to list app tokens', error);
687
+ throw new Error(`Failed to list app tokens: ${error instanceof Error ? error.message : 'Unknown error'}`);
688
+ }
689
+ }
690
+ async deleteAppToken(id) {
691
+ try {
692
+ SecurityValidator.validateDocumentId(id);
693
+ const filePath = this.getTokenPath(id);
694
+ try {
695
+ await fs.unlink(filePath);
696
+ if (!this.config.silent) {
697
+ this.logger.debug('App token deleted', { id });
698
+ }
699
+ }
700
+ catch (error) {
701
+ if (error.code === 'ENOENT') {
702
+ throw new Error(`App token ${id} not found`);
703
+ }
704
+ throw error;
705
+ }
706
+ }
707
+ catch (error) {
708
+ this.logger.error(`Failed to delete app token ${id}`, error);
709
+ throw error;
710
+ }
711
+ }
712
+ async getAppTokenByHash(hash) {
713
+ try {
714
+ const tokens = await this.listAppTokens();
715
+ return tokens.find(token => token.tokenHash === hash) || null;
716
+ }
717
+ catch (error) {
718
+ this.logger.error(`Failed to get app token by hash`, error);
719
+ throw new Error(`Failed to get app token by hash: ${error instanceof Error ? error.message : 'Unknown error'}`);
720
+ }
721
+ }
722
+ // ==========================================================================
723
+ // WEBHOOK OPERATIONS
724
+ // ==========================================================================
725
+ async getWebhook(id) {
726
+ try {
727
+ const filePath = this.getWebhookPath(id);
728
+ // Check if file exists
729
+ try {
730
+ await fs.access(filePath, constants.F_OK);
731
+ }
732
+ catch {
733
+ return null;
734
+ }
735
+ const content = await fs.readFile(filePath, 'utf-8');
736
+ const webhook = this.safeParseJSON(content, this.dateReviver);
737
+ this.logger.debug(`Webhook retrieved: ${id}`);
738
+ return webhook;
739
+ }
740
+ catch (error) {
741
+ this.logger.error(`Failed to get webhook ${id}`, error);
742
+ throw new Error(`Failed to get webhook ${id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
743
+ }
744
+ }
745
+ async saveWebhook(id, webhookData) {
746
+ try {
747
+ SecurityValidator.validateDocumentId(id);
748
+ const filePath = this.getWebhookPath(id);
749
+ const webhookDir = path.dirname(filePath);
750
+ // Ensure webhook directory exists
751
+ await fsExtra.ensureDir(webhookDir, { mode: this.config.dirMode });
752
+ // Check if webhook already exists
753
+ let existingWebhook = null;
754
+ try {
755
+ existingWebhook = await this.getWebhook(id);
756
+ }
757
+ catch {
758
+ // Webhook doesn't exist, this is a new webhook
759
+ }
760
+ const now = new Date();
761
+ const isUpdate = existingWebhook !== null;
762
+ const webhook = {
763
+ id,
764
+ name: webhookData.name || existingWebhook?.name || 'Untitled Webhook',
765
+ url: webhookData.url || existingWebhook?.url || '',
766
+ events: webhookData.events || existingWebhook?.events || [],
767
+ active: webhookData.active !== undefined ? webhookData.active : (existingWebhook?.active ?? true),
768
+ secret: webhookData.secret || existingWebhook?.secret || '',
769
+ headers: webhookData.headers || existingWebhook?.headers || {},
770
+ retryPolicy: webhookData.retryPolicy || existingWebhook?.retryPolicy || {
771
+ maxRetries: 3,
772
+ backoffType: 'exponential',
773
+ baseDelay: 1000,
774
+ maxDelay: 30000,
775
+ retryOnStatus: [500, 502, 503, 504, 408, 429]
776
+ },
777
+ createdBy: webhookData.createdBy || existingWebhook?.createdBy || 'system',
778
+ createdAt: isUpdate ? existingWebhook.createdAt : now,
779
+ updatedAt: now
780
+ };
781
+ // Write to file with Date serialization
782
+ const jsonContent = this.config.prettyJson
783
+ ? JSON.stringify(webhook, this.dateReplacer, this.config.jsonSpaces)
784
+ : JSON.stringify(webhook, this.dateReplacer);
785
+ await fs.writeFile(filePath, jsonContent, { mode: this.config.fileMode });
786
+ if (this.config.syncWrites) {
787
+ const fd = await fs.open(filePath, 'r+');
788
+ await fd.sync();
789
+ await fd.close();
790
+ }
791
+ this.logger.info(`Webhook saved: ${id} (${isUpdate ? 'updated' : 'created'})`);
792
+ return webhook;
793
+ }
794
+ catch (error) {
795
+ this.logger.error(`Failed to save webhook ${id}`, error);
796
+ throw new Error(`Failed to save webhook ${id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
797
+ }
798
+ }
799
+ async listWebhooks(options = {}) {
800
+ try {
801
+ // Ensure webhooks directory exists
802
+ await fsExtra.ensureDir(this.config.webhooksDir, { mode: this.config.dirMode });
803
+ const files = await fs.readdir(this.config.webhooksDir);
804
+ const webhookFiles = files.filter(file => file.endsWith('.json'));
805
+ const webhooks = [];
806
+ for (const file of webhookFiles) {
807
+ try {
808
+ const id = path.basename(file, '.json');
809
+ const webhook = await this.getWebhook(id);
810
+ if (webhook) {
811
+ webhooks.push(webhook);
812
+ }
813
+ }
814
+ catch (error) {
815
+ // Skip invalid webhook files but log the issue
816
+ if (!this.config.silent) {
817
+ this.logger.warn(`Could not read webhook file ${file}`, error);
818
+ }
819
+ }
820
+ }
821
+ // Apply filters
822
+ let filteredWebhooks = webhooks;
823
+ if (options.active !== undefined) {
824
+ filteredWebhooks = filteredWebhooks.filter(webhook => webhook.active === options.active);
825
+ }
826
+ if (options.events && options.events.length > 0) {
827
+ filteredWebhooks = filteredWebhooks.filter(webhook => options.events.some((eventPattern) => webhook.events.some((webhookEvent) => this.matchesEventPattern(webhookEvent, eventPattern))));
828
+ }
829
+ if (options.createdBy) {
830
+ filteredWebhooks = filteredWebhooks.filter(webhook => webhook.createdBy === options.createdBy);
831
+ }
832
+ // Sort by creation date (newest first)
833
+ filteredWebhooks.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
834
+ // Apply pagination
835
+ if (options.offset) {
836
+ filteredWebhooks = filteredWebhooks.slice(options.offset);
837
+ }
838
+ if (options.limit) {
839
+ filteredWebhooks = filteredWebhooks.slice(0, options.limit);
840
+ }
841
+ this.logger.debug(`Listed webhooks: ${filteredWebhooks.length} results`);
842
+ return filteredWebhooks;
843
+ }
844
+ catch (error) {
845
+ this.logger.error('Failed to list webhooks', error);
846
+ throw new Error(`Failed to list webhooks: ${error instanceof Error ? error.message : 'Unknown error'}`);
847
+ }
848
+ }
849
+ async deleteWebhook(id) {
850
+ try {
851
+ const filePath = this.getWebhookPath(id);
852
+ // Check if webhook exists
853
+ try {
854
+ await fs.access(filePath, constants.F_OK);
855
+ }
856
+ catch {
857
+ // Webhook doesn't exist, nothing to delete
858
+ this.logger.debug(`Webhook ${id} does not exist, nothing to delete`);
859
+ return;
860
+ }
861
+ await fs.unlink(filePath);
862
+ this.logger.info(`Webhook deleted: ${id}`);
863
+ }
864
+ catch (error) {
865
+ this.logger.error(`Failed to delete webhook ${id}`, error);
866
+ throw new Error(`Failed to delete webhook ${id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
867
+ }
868
+ }
869
+ // ==========================================================================
870
+ // PRIVATE HELPER METHODS FOR WEBHOOKS
871
+ // ==========================================================================
872
+ getWebhookPath(id) {
873
+ SecurityValidator.validateDocumentId(id);
874
+ const webhookPath = path.join(this.config.webhooksDir, `${id}.json`);
875
+ this.validateSecurePath(webhookPath, this.config.webhooksDir);
876
+ return webhookPath;
877
+ }
878
+ matchesEventPattern(webhookEvent, pattern) {
879
+ if (pattern === '*')
880
+ return true;
881
+ if (pattern.endsWith('*')) {
882
+ return webhookEvent.startsWith(pattern.slice(0, -1));
883
+ }
884
+ return webhookEvent === pattern;
885
+ }
886
+ // ==========================================================================
887
+ // UTILITY OPERATIONS
888
+ // ==========================================================================
889
+ async healthCheck() {
890
+ try {
891
+ // Directories may still be being created; healthCheck is the documented way to wait.
892
+ await this.ready;
893
+ // Test directory access
894
+ await fs.access(this.config.contentDir, constants.R_OK | constants.W_OK);
895
+ await fs.access(this.config.usersDir, constants.R_OK | constants.W_OK);
896
+ await fs.access(this.config.tokensDir, constants.R_OK | constants.W_OK);
897
+ return true;
898
+ }
899
+ catch (error) {
900
+ this.logger.error('Health check failed', error);
901
+ return false;
902
+ }
903
+ }
904
+ async migrate(migrations) {
905
+ // Basic migration support - implement as needed
906
+ this.logger.info('Migration support not implemented for filesystem adapter', {
907
+ migrationsCount: migrations.length
908
+ });
909
+ }
910
+ // ==========================================================================
911
+ // PRIVATE HELPER METHODS
912
+ // ==========================================================================
913
+ getDocumentPath(collection, id) {
914
+ return path.join(this.config.contentDir, collection, `${id}.json`);
915
+ }
916
+ /**
917
+ * The single stored-file -> Document projection.
918
+ *
919
+ * Every read path (get, save, list, count) goes through this so the shape
920
+ * cannot drift again: `listDocuments` used to drop `_createdBy`/`_updatedBy`
921
+ * and the `*ByType` fields, and `countDocuments` filtered against a partial
922
+ * document with only `id`/`_status`/`_createdAt`/`_updatedAt`, so a filter on
923
+ * `_createdBy` or `_revision` never matched anything.
924
+ */
925
+ documentFileToDocument(documentFile, collection) {
926
+ return {
927
+ id: documentFile.id,
928
+ // Both identities, same value: consumers read one or the other.
929
+ _id: documentFile.id,
930
+ _collection: collection,
931
+ _createdAt: new Date(documentFile.metadata.createdAt),
932
+ _updatedAt: new Date(documentFile.metadata.updatedAt),
933
+ _revision: documentFile.metadata.revision,
934
+ _status: documentFile.metadata.status,
935
+ _createdBy: documentFile.metadata.createdBy,
936
+ _updatedBy: documentFile.metadata.updatedBy,
937
+ _createdByType: documentFile.metadata.createdByType,
938
+ _updatedByType: documentFile.metadata.updatedByType,
939
+ ...documentFile.data
940
+ };
941
+ }
942
+ /**
943
+ * Serialize writes to a single user file within this process so that
944
+ * compare-and-set operations (saveUserIf) cannot interleave with saveUser.
945
+ */
946
+ async withUserLock(id, operation) {
947
+ const previous = this.userWriteLocks.get(id) ?? Promise.resolve();
948
+ const current = previous.then(operation, operation);
949
+ const settled = current.then(() => undefined, () => undefined);
950
+ this.userWriteLocks.set(id, settled);
951
+ try {
952
+ return await current;
953
+ }
954
+ finally {
955
+ if (this.userWriteLocks.get(id) === settled) {
956
+ this.userWriteLocks.delete(id);
957
+ }
958
+ }
959
+ }
960
+ getUserPath(id) {
961
+ return path.join(this.config.usersDir, `${id}.json`);
962
+ }
963
+ getTokenPath(id) {
964
+ return path.join(this.config.tokensDir, `${id}.json`);
965
+ }
966
+ formatJson(data) {
967
+ if (this.config.prettyJson) {
968
+ return JSON.stringify(data, null, this.config.jsonSpaces);
969
+ }
970
+ return JSON.stringify(data);
971
+ }
972
+ async atomicWriteFile(filePath, content) {
973
+ const tempPath = `${filePath}.tmp`;
974
+ try {
975
+ await fs.writeFile(tempPath, content, {
976
+ mode: this.config.fileMode,
977
+ encoding: 'utf8'
978
+ });
979
+ if (this.config.syncWrites) {
980
+ const fd = await fs.open(tempPath, 'r+');
981
+ await fd.sync();
982
+ await fd.close();
983
+ }
984
+ await fs.rename(tempPath, filePath);
985
+ }
986
+ catch (error) {
987
+ // Clean up temp file on error
988
+ try {
989
+ await fs.unlink(tempPath);
990
+ }
991
+ catch {
992
+ // Ignore cleanup errors
993
+ }
994
+ throw error;
995
+ }
996
+ }
997
+ /**
998
+ * A field cleared by writing `null` round-trips through JSON as `null`, but
999
+ * the contract says an absent or cleared user field reads back `undefined` on
1000
+ * every adapter. Normalise on the way out.
1001
+ */
1002
+ orUndefined(value) {
1003
+ return value ?? undefined;
1004
+ }
1005
+ userFileToUser(userFile) {
1006
+ return {
1007
+ id: userFile.id,
1008
+ username: userFile.username,
1009
+ email: userFile.email,
1010
+ passwordHash: userFile.passwordHash,
1011
+ firstName: userFile.firstName,
1012
+ lastName: userFile.lastName,
1013
+ role: userFile.role,
1014
+ // Created users always get `[]`; only an update that explicitly clears
1015
+ // permissions makes them absent, and absent reads back as `undefined`.
1016
+ permissions: this.orUndefined(userFile.permissions),
1017
+ isActive: userFile.isActive,
1018
+ profileImage: this.orUndefined(userFile.profileImage),
1019
+ preferences: this.orUndefined(userFile.preferences),
1020
+ oauthProviders: this.orUndefined(userFile.oauthProviders),
1021
+ // Written to disk by the spread in writeUserFile but previously dropped
1022
+ // here, so every read returned a user without them: TOTP enrolment always
1023
+ // failed with "No pending TOTP setup found" and passkeys never persisted.
1024
+ mfa: this.orUndefined(userFile.mfa),
1025
+ passkeys: this.orUndefined(userFile.passkeys),
1026
+ lastLoginAt: this.orUndefined(userFile.lastLoginAt),
1027
+ createdAt: userFile.createdAt,
1028
+ updatedAt: userFile.updatedAt
1029
+ };
1030
+ }
1031
+ tokenFileToAppToken(tokenFile) {
1032
+ return {
1033
+ id: tokenFile.id,
1034
+ name: tokenFile.name,
1035
+ description: tokenFile.description,
1036
+ tokenHash: tokenFile.tokenHash,
1037
+ permissions: tokenFile.permissions,
1038
+ createdBy: tokenFile.createdBy,
1039
+ isActive: tokenFile.isActive,
1040
+ lastUsedAt: tokenFile.lastUsedAt,
1041
+ usageCount: tokenFile.usageCount,
1042
+ expiresAt: tokenFile.expiresAt,
1043
+ createdAt: tokenFile.createdAt,
1044
+ updatedAt: tokenFile.updatedAt
1045
+ };
1046
+ }
1047
+ // JSON parsing and serialization helpers
1048
+ safeParseJSON(content, reviver) {
1049
+ // Check content size to prevent DoS attacks
1050
+ if (content.length > this.MAX_DOCUMENT_SIZE) {
1051
+ throw new InvalidInputError(`JSON content size ${content.length} exceeds maximum allowed size of ${this.MAX_DOCUMENT_SIZE} bytes`, 'size');
1052
+ }
1053
+ try {
1054
+ return JSON.parse(content, reviver);
1055
+ }
1056
+ catch (error) {
1057
+ throw new InvalidInputError(`Invalid JSON format: ${error}`, 'json');
1058
+ }
1059
+ }
1060
+ dateReplacer = (key, value) => {
1061
+ // Convert Date objects to ISO strings
1062
+ if (value instanceof Date) {
1063
+ return value.toISOString();
1064
+ }
1065
+ return value;
1066
+ };
1067
+ dateReviver = (key, value) => {
1068
+ // Convert ISO date strings back to Date objects
1069
+ if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
1070
+ return new Date(value);
1071
+ }
1072
+ return value;
1073
+ };
1074
+ validateSecurePath(targetPath, basePath) {
1075
+ const resolvedTarget = path.resolve(targetPath);
1076
+ const resolvedBase = path.resolve(basePath);
1077
+ if (!resolvedTarget.startsWith(resolvedBase + path.sep) && resolvedTarget !== resolvedBase) {
1078
+ throw new InvalidInputError('Path traversal attempt detected', 'path');
1079
+ }
1080
+ }
1081
+ // ==========================================================================
1082
+ // SETTINGS OPERATIONS
1083
+ // ==========================================================================
1084
+ /**
1085
+ * Get studio settings
1086
+ */
1087
+ async getSettings() {
1088
+ try {
1089
+ const settingsPath = path.join(this.config.settingsDir, 'studio.json');
1090
+ // Check if settings file exists
1091
+ try {
1092
+ await fs.access(settingsPath, constants.F_OK);
1093
+ }
1094
+ catch {
1095
+ // Settings file doesn't exist
1096
+ return null;
1097
+ }
1098
+ const fileData = await fs.readFile(settingsPath, 'utf-8');
1099
+ const parsed = JSON.parse(fileData, this.dateReviver);
1100
+ this.logger.debug('Settings loaded', { path: settingsPath });
1101
+ return parsed;
1102
+ }
1103
+ catch (error) {
1104
+ this.logger.error('Failed to load settings', { error });
1105
+ throw new InvalidInputError(`Failed to load settings: ${error}`, 'settings');
1106
+ }
1107
+ }
1108
+ /**
1109
+ * Save studio settings
1110
+ */
1111
+ async saveSettings(settings) {
1112
+ try {
1113
+ // Validate settings object
1114
+ if (!settings || typeof settings !== 'object') {
1115
+ throw new InvalidInputError('Settings must be a valid object', 'settings');
1116
+ }
1117
+ if (!settings.id || !settings.publicUrl || !settings.studioTitle || !settings.defaultTheme) {
1118
+ throw new InvalidInputError('Settings missing required fields', 'settings');
1119
+ }
1120
+ const settingsPath = path.join(this.config.settingsDir, 'studio.json');
1121
+ // Ensure settings directory exists
1122
+ await fsExtra.ensureDir(this.config.settingsDir, { mode: this.config.dirMode });
1123
+ // Validate secure path
1124
+ this.validateSecurePath(settingsPath, this.config.settingsDir);
1125
+ const jsonData = this.formatJson(settings);
1126
+ if (this.config.syncWrites) {
1127
+ await fs.writeFile(settingsPath, jsonData, {
1128
+ encoding: 'utf-8',
1129
+ mode: this.config.fileMode,
1130
+ flag: 'w'
1131
+ });
1132
+ }
1133
+ else {
1134
+ await fs.writeFile(settingsPath, jsonData, {
1135
+ encoding: 'utf-8',
1136
+ mode: this.config.fileMode
1137
+ });
1138
+ }
1139
+ this.logger.info('Settings saved', {
1140
+ path: settingsPath,
1141
+ settingsId: settings.id
1142
+ });
1143
+ }
1144
+ catch (error) {
1145
+ this.logger.error('Failed to save settings', { error, settingsId: settings.id });
1146
+ throw new InvalidInputError(`Failed to save settings: ${error}`, 'settings');
1147
+ }
1148
+ }
1149
+ // ==========================================================================
1150
+ // AUDIT LOG OPERATIONS
1151
+ // ==========================================================================
1152
+ /**
1153
+ * Create an audit log entry
1154
+ */
1155
+ async createAuditLog(auditLog) {
1156
+ try {
1157
+ const auditLogId = `audit-${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
1158
+ const auditLogPath = path.join(this.config.auditLogsDir, `${auditLogId}.json`);
1159
+ // Ensure audit logs directory exists
1160
+ await fsExtra.ensureDir(this.config.auditLogsDir, { mode: this.config.dirMode });
1161
+ // Validate secure path
1162
+ this.validateSecurePath(auditLogPath, this.config.auditLogsDir);
1163
+ const auditLogFile = {
1164
+ id: auditLogId,
1165
+ documentId: auditLog.documentId,
1166
+ collection: auditLog.collection,
1167
+ operation: auditLog.operation,
1168
+ actorId: auditLog.actorId,
1169
+ actorType: auditLog.actorType,
1170
+ actorUsername: auditLog.actorUsername,
1171
+ changes: auditLog.changes,
1172
+ timestamp: auditLog.timestamp.toISOString(),
1173
+ revision: auditLog.revision,
1174
+ ipAddress: auditLog.ipAddress,
1175
+ userAgent: auditLog.userAgent,
1176
+ sessionId: auditLog.sessionId,
1177
+ metadata: auditLog.metadata
1178
+ };
1179
+ const jsonData = this.formatJson(auditLogFile);
1180
+ if (this.config.syncWrites) {
1181
+ await fs.writeFile(auditLogPath, jsonData, {
1182
+ encoding: 'utf-8',
1183
+ mode: this.config.fileMode,
1184
+ flag: 'w'
1185
+ });
1186
+ }
1187
+ else {
1188
+ await fs.writeFile(auditLogPath, jsonData, {
1189
+ encoding: 'utf-8',
1190
+ mode: this.config.fileMode
1191
+ });
1192
+ }
1193
+ this.logger.debug('Audit log created', {
1194
+ auditLogId,
1195
+ documentId: auditLog.documentId,
1196
+ operation: auditLog.operation,
1197
+ actorId: auditLog.actorId
1198
+ });
1199
+ return {
1200
+ ...auditLog,
1201
+ id: auditLogId
1202
+ };
1203
+ }
1204
+ catch (error) {
1205
+ this.logger.error('Failed to create audit log', { error, auditLog });
1206
+ throw new InvalidInputError(`Failed to create audit log: ${error}`, 'auditLog');
1207
+ }
1208
+ }
1209
+ /**
1210
+ * Get audit logs for a specific document
1211
+ */
1212
+ async getDocumentAuditLogs(documentId, options = {}) {
1213
+ try {
1214
+ const auditLogs = [];
1215
+ const auditLogFiles = await fs.readdir(this.config.auditLogsDir);
1216
+ // Filter files by document ID (read each file to check)
1217
+ for (const filename of auditLogFiles) {
1218
+ if (!filename.endsWith('.json'))
1219
+ continue;
1220
+ const auditLogPath = path.join(this.config.auditLogsDir, filename);
1221
+ this.validateSecurePath(auditLogPath, this.config.auditLogsDir);
1222
+ try {
1223
+ const fileContent = await fs.readFile(auditLogPath, 'utf-8');
1224
+ const auditLogFile = JSON.parse(fileContent);
1225
+ if (auditLogFile.documentId === documentId) {
1226
+ auditLogs.push({
1227
+ id: auditLogFile.id,
1228
+ documentId: auditLogFile.documentId,
1229
+ collection: auditLogFile.collection,
1230
+ operation: auditLogFile.operation,
1231
+ actorId: auditLogFile.actorId,
1232
+ actorType: auditLogFile.actorType,
1233
+ actorUsername: auditLogFile.actorUsername,
1234
+ changes: auditLogFile.changes,
1235
+ timestamp: new Date(auditLogFile.timestamp),
1236
+ revision: auditLogFile.revision,
1237
+ ipAddress: auditLogFile.ipAddress,
1238
+ userAgent: auditLogFile.userAgent,
1239
+ sessionId: auditLogFile.sessionId,
1240
+ metadata: auditLogFile.metadata
1241
+ });
1242
+ }
1243
+ }
1244
+ catch (parseError) {
1245
+ this.logger.warn('Failed to parse audit log file', { filename, parseError });
1246
+ }
1247
+ }
1248
+ // Sort by timestamp (newest first)
1249
+ auditLogs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
1250
+ // Apply pagination
1251
+ const offset = options.offset || 0;
1252
+ const limit = options.limit || 100;
1253
+ return auditLogs.slice(offset, offset + limit);
1254
+ }
1255
+ catch (error) {
1256
+ this.logger.error('Failed to get document audit logs', { error, documentId });
1257
+ throw new InvalidInputError(`Failed to get document audit logs: ${error}`, 'documentId');
1258
+ }
1259
+ }
1260
+ /**
1261
+ * Get audit logs for a collection
1262
+ */
1263
+ async getCollectionAuditLogs(collection, options = {}) {
1264
+ try {
1265
+ const auditLogs = [];
1266
+ const auditLogFiles = await fs.readdir(this.config.auditLogsDir);
1267
+ // Filter files by collection (read each file to check)
1268
+ for (const filename of auditLogFiles) {
1269
+ if (!filename.endsWith('.json'))
1270
+ continue;
1271
+ const auditLogPath = path.join(this.config.auditLogsDir, filename);
1272
+ this.validateSecurePath(auditLogPath, this.config.auditLogsDir);
1273
+ try {
1274
+ const fileContent = await fs.readFile(auditLogPath, 'utf-8');
1275
+ const auditLogFile = JSON.parse(fileContent);
1276
+ if (auditLogFile.collection === collection) {
1277
+ auditLogs.push({
1278
+ id: auditLogFile.id,
1279
+ documentId: auditLogFile.documentId,
1280
+ collection: auditLogFile.collection,
1281
+ operation: auditLogFile.operation,
1282
+ actorId: auditLogFile.actorId,
1283
+ actorType: auditLogFile.actorType,
1284
+ actorUsername: auditLogFile.actorUsername,
1285
+ changes: auditLogFile.changes,
1286
+ timestamp: new Date(auditLogFile.timestamp),
1287
+ revision: auditLogFile.revision,
1288
+ ipAddress: auditLogFile.ipAddress,
1289
+ userAgent: auditLogFile.userAgent,
1290
+ sessionId: auditLogFile.sessionId,
1291
+ metadata: auditLogFile.metadata
1292
+ });
1293
+ }
1294
+ }
1295
+ catch (parseError) {
1296
+ this.logger.warn('Failed to parse audit log file', { filename, parseError });
1297
+ }
1298
+ }
1299
+ // Sort by timestamp (newest first)
1300
+ auditLogs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
1301
+ // Apply pagination
1302
+ const offset = options.offset || 0;
1303
+ const limit = options.limit || 100;
1304
+ return auditLogs.slice(offset, offset + limit);
1305
+ }
1306
+ catch (error) {
1307
+ this.logger.error('Failed to get collection audit logs', { error, collection });
1308
+ throw new InvalidInputError(`Failed to get collection audit logs: ${error}`, 'collection');
1309
+ }
1310
+ }
1311
+ /**
1312
+ * Get audit logs by actor
1313
+ */
1314
+ async getActorAuditLogs(actorId, options = {}) {
1315
+ try {
1316
+ const auditLogs = [];
1317
+ const auditLogFiles = await fs.readdir(this.config.auditLogsDir);
1318
+ // Filter files by actor ID (read each file to check)
1319
+ for (const filename of auditLogFiles) {
1320
+ if (!filename.endsWith('.json'))
1321
+ continue;
1322
+ const auditLogPath = path.join(this.config.auditLogsDir, filename);
1323
+ this.validateSecurePath(auditLogPath, this.config.auditLogsDir);
1324
+ try {
1325
+ const fileContent = await fs.readFile(auditLogPath, 'utf-8');
1326
+ const auditLogFile = JSON.parse(fileContent);
1327
+ if (auditLogFile.actorId === actorId) {
1328
+ auditLogs.push({
1329
+ id: auditLogFile.id,
1330
+ documentId: auditLogFile.documentId,
1331
+ collection: auditLogFile.collection,
1332
+ operation: auditLogFile.operation,
1333
+ actorId: auditLogFile.actorId,
1334
+ actorType: auditLogFile.actorType,
1335
+ actorUsername: auditLogFile.actorUsername,
1336
+ changes: auditLogFile.changes,
1337
+ timestamp: new Date(auditLogFile.timestamp),
1338
+ revision: auditLogFile.revision,
1339
+ ipAddress: auditLogFile.ipAddress,
1340
+ userAgent: auditLogFile.userAgent,
1341
+ sessionId: auditLogFile.sessionId,
1342
+ metadata: auditLogFile.metadata
1343
+ });
1344
+ }
1345
+ }
1346
+ catch (parseError) {
1347
+ this.logger.warn('Failed to parse audit log file', { filename, parseError });
1348
+ }
1349
+ }
1350
+ // Sort by timestamp (newest first)
1351
+ auditLogs.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
1352
+ // Apply pagination
1353
+ const offset = options.offset || 0;
1354
+ const limit = options.limit || 100;
1355
+ return auditLogs.slice(offset, offset + limit);
1356
+ }
1357
+ catch (error) {
1358
+ this.logger.error('Failed to get actor audit logs', { error, actorId });
1359
+ throw new InvalidInputError(`Failed to get actor audit logs: ${error}`, 'actorId');
1360
+ }
1361
+ }
1362
+ }
1363
+ //# sourceMappingURL=filesystem-data-adapter.js.map