happy2 0.0.5

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 (373) hide show
  1. package/README.md +82 -0
  2. package/package.json +74 -0
  3. package/packages/happy2-server/Dockerfile +18 -0
  4. package/packages/happy2-server/README.md +418 -0
  5. package/packages/happy2-server/bin/happy2.js +3 -0
  6. package/packages/happy2-server/dist/cli.d.ts +3 -0
  7. package/packages/happy2-server/dist/cli.d.ts.map +1 -0
  8. package/packages/happy2-server/dist/cli.js +44 -0
  9. package/packages/happy2-server/dist/cli.js.map +1 -0
  10. package/packages/happy2-server/dist/index.d.ts +24 -0
  11. package/packages/happy2-server/dist/index.d.ts.map +1 -0
  12. package/packages/happy2-server/dist/index.js +22 -0
  13. package/packages/happy2-server/dist/index.js.map +1 -0
  14. package/packages/happy2-server/dist/modules/agents/builtin-images.d.ts +8 -0
  15. package/packages/happy2-server/dist/modules/agents/builtin-images.d.ts.map +1 -0
  16. package/packages/happy2-server/dist/modules/agents/builtin-images.js +283 -0
  17. package/packages/happy2-server/dist/modules/agents/builtin-images.js.map +1 -0
  18. package/packages/happy2-server/dist/modules/agents/command.d.ts +3 -0
  19. package/packages/happy2-server/dist/modules/agents/command.d.ts.map +1 -0
  20. package/packages/happy2-server/dist/modules/agents/command.js +7 -0
  21. package/packages/happy2-server/dist/modules/agents/command.js.map +1 -0
  22. package/packages/happy2-server/dist/modules/agents/daemon.d.ts +106 -0
  23. package/packages/happy2-server/dist/modules/agents/daemon.d.ts.map +1 -0
  24. package/packages/happy2-server/dist/modules/agents/daemon.js +522 -0
  25. package/packages/happy2-server/dist/modules/agents/daemon.js.map +1 -0
  26. package/packages/happy2-server/dist/modules/agents/docker.d.ts +51 -0
  27. package/packages/happy2-server/dist/modules/agents/docker.d.ts.map +1 -0
  28. package/packages/happy2-server/dist/modules/agents/docker.js +256 -0
  29. package/packages/happy2-server/dist/modules/agents/docker.js.map +1 -0
  30. package/packages/happy2-server/dist/modules/agents/docker.test.d.ts +2 -0
  31. package/packages/happy2-server/dist/modules/agents/docker.test.d.ts.map +1 -0
  32. package/packages/happy2-server/dist/modules/agents/docker.test.js +155 -0
  33. package/packages/happy2-server/dist/modules/agents/docker.test.js.map +1 -0
  34. package/packages/happy2-server/dist/modules/agents/index.d.ts +5 -0
  35. package/packages/happy2-server/dist/modules/agents/index.d.ts.map +1 -0
  36. package/packages/happy2-server/dist/modules/agents/index.js +4 -0
  37. package/packages/happy2-server/dist/modules/agents/index.js.map +1 -0
  38. package/packages/happy2-server/dist/modules/agents/service.d.ts +140 -0
  39. package/packages/happy2-server/dist/modules/agents/service.d.ts.map +1 -0
  40. package/packages/happy2-server/dist/modules/agents/service.js +1283 -0
  41. package/packages/happy2-server/dist/modules/agents/service.js.map +1 -0
  42. package/packages/happy2-server/dist/modules/auth/cloudflare-access.d.ts +15 -0
  43. package/packages/happy2-server/dist/modules/auth/cloudflare-access.d.ts.map +1 -0
  44. package/packages/happy2-server/dist/modules/auth/cloudflare-access.js +49 -0
  45. package/packages/happy2-server/dist/modules/auth/cloudflare-access.js.map +1 -0
  46. package/packages/happy2-server/dist/modules/auth/crypto.d.ts +4 -0
  47. package/packages/happy2-server/dist/modules/auth/crypto.d.ts.map +1 -0
  48. package/packages/happy2-server/dist/modules/auth/crypto.js +31 -0
  49. package/packages/happy2-server/dist/modules/auth/crypto.js.map +1 -0
  50. package/packages/happy2-server/dist/modules/auth/crypto.test.d.ts +2 -0
  51. package/packages/happy2-server/dist/modules/auth/crypto.test.d.ts.map +1 -0
  52. package/packages/happy2-server/dist/modules/auth/crypto.test.js +11 -0
  53. package/packages/happy2-server/dist/modules/auth/crypto.test.js.map +1 -0
  54. package/packages/happy2-server/dist/modules/auth/email.d.ts +3 -0
  55. package/packages/happy2-server/dist/modules/auth/email.d.ts.map +1 -0
  56. package/packages/happy2-server/dist/modules/auth/email.js +22 -0
  57. package/packages/happy2-server/dist/modules/auth/email.js.map +1 -0
  58. package/packages/happy2-server/dist/modules/auth/metadata.d.ts +5 -0
  59. package/packages/happy2-server/dist/modules/auth/metadata.d.ts.map +1 -0
  60. package/packages/happy2-server/dist/modules/auth/metadata.js +28 -0
  61. package/packages/happy2-server/dist/modules/auth/metadata.js.map +1 -0
  62. package/packages/happy2-server/dist/modules/auth/metadata.test.d.ts +2 -0
  63. package/packages/happy2-server/dist/modules/auth/metadata.test.d.ts.map +1 -0
  64. package/packages/happy2-server/dist/modules/auth/metadata.test.js +10 -0
  65. package/packages/happy2-server/dist/modules/auth/metadata.test.js.map +1 -0
  66. package/packages/happy2-server/dist/modules/auth/methods.d.ts +10 -0
  67. package/packages/happy2-server/dist/modules/auth/methods.d.ts.map +1 -0
  68. package/packages/happy2-server/dist/modules/auth/methods.js +21 -0
  69. package/packages/happy2-server/dist/modules/auth/methods.js.map +1 -0
  70. package/packages/happy2-server/dist/modules/auth/methods.test.d.ts +2 -0
  71. package/packages/happy2-server/dist/modules/auth/methods.test.d.ts.map +1 -0
  72. package/packages/happy2-server/dist/modules/auth/methods.test.js +16 -0
  73. package/packages/happy2-server/dist/modules/auth/methods.test.js.map +1 -0
  74. package/packages/happy2-server/dist/modules/auth/oidc.d.ts +9 -0
  75. package/packages/happy2-server/dist/modules/auth/oidc.d.ts.map +1 -0
  76. package/packages/happy2-server/dist/modules/auth/oidc.js +68 -0
  77. package/packages/happy2-server/dist/modules/auth/oidc.js.map +1 -0
  78. package/packages/happy2-server/dist/modules/auth/service.d.ts +44 -0
  79. package/packages/happy2-server/dist/modules/auth/service.d.ts.map +1 -0
  80. package/packages/happy2-server/dist/modules/auth/service.js +208 -0
  81. package/packages/happy2-server/dist/modules/auth/service.js.map +1 -0
  82. package/packages/happy2-server/dist/modules/auth/tokens.d.ts +21 -0
  83. package/packages/happy2-server/dist/modules/auth/tokens.d.ts.map +1 -0
  84. package/packages/happy2-server/dist/modules/auth/tokens.js +79 -0
  85. package/packages/happy2-server/dist/modules/auth/tokens.js.map +1 -0
  86. package/packages/happy2-server/dist/modules/automation/repository.d.ts +127 -0
  87. package/packages/happy2-server/dist/modules/automation/repository.d.ts.map +1 -0
  88. package/packages/happy2-server/dist/modules/automation/repository.js +821 -0
  89. package/packages/happy2-server/dist/modules/automation/repository.js.map +1 -0
  90. package/packages/happy2-server/dist/modules/collaboration/repository.d.ts +839 -0
  91. package/packages/happy2-server/dist/modules/collaboration/repository.d.ts.map +1 -0
  92. package/packages/happy2-server/dist/modules/collaboration/repository.js +5795 -0
  93. package/packages/happy2-server/dist/modules/collaboration/repository.js.map +1 -0
  94. package/packages/happy2-server/dist/modules/collaboration/repository.test.d.ts +2 -0
  95. package/packages/happy2-server/dist/modules/collaboration/repository.test.d.ts.map +1 -0
  96. package/packages/happy2-server/dist/modules/collaboration/repository.test.js +620 -0
  97. package/packages/happy2-server/dist/modules/collaboration/repository.test.js.map +1 -0
  98. package/packages/happy2-server/dist/modules/collaboration/types.d.ts +243 -0
  99. package/packages/happy2-server/dist/modules/collaboration/types.d.ts.map +1 -0
  100. package/packages/happy2-server/dist/modules/collaboration/types.js +8 -0
  101. package/packages/happy2-server/dist/modules/collaboration/types.js.map +1 -0
  102. package/packages/happy2-server/dist/modules/config/defaults.d.ts +8 -0
  103. package/packages/happy2-server/dist/modules/config/defaults.d.ts.map +1 -0
  104. package/packages/happy2-server/dist/modules/config/defaults.js +70 -0
  105. package/packages/happy2-server/dist/modules/config/defaults.js.map +1 -0
  106. package/packages/happy2-server/dist/modules/config/defaults.test.d.ts +2 -0
  107. package/packages/happy2-server/dist/modules/config/defaults.test.d.ts.map +1 -0
  108. package/packages/happy2-server/dist/modules/config/defaults.test.js +27 -0
  109. package/packages/happy2-server/dist/modules/config/defaults.test.js.map +1 -0
  110. package/packages/happy2-server/dist/modules/config/environment.d.ts +4 -0
  111. package/packages/happy2-server/dist/modules/config/environment.d.ts.map +1 -0
  112. package/packages/happy2-server/dist/modules/config/environment.js +92 -0
  113. package/packages/happy2-server/dist/modules/config/environment.js.map +1 -0
  114. package/packages/happy2-server/dist/modules/config/loader.d.ts +5 -0
  115. package/packages/happy2-server/dist/modules/config/loader.d.ts.map +1 -0
  116. package/packages/happy2-server/dist/modules/config/loader.js +251 -0
  117. package/packages/happy2-server/dist/modules/config/loader.js.map +1 -0
  118. package/packages/happy2-server/dist/modules/config/loader.test.d.ts +2 -0
  119. package/packages/happy2-server/dist/modules/config/loader.test.d.ts.map +1 -0
  120. package/packages/happy2-server/dist/modules/config/loader.test.js +108 -0
  121. package/packages/happy2-server/dist/modules/config/loader.test.js.map +1 -0
  122. package/packages/happy2-server/dist/modules/config/paths.d.ts +7 -0
  123. package/packages/happy2-server/dist/modules/config/paths.d.ts.map +1 -0
  124. package/packages/happy2-server/dist/modules/config/paths.js +15 -0
  125. package/packages/happy2-server/dist/modules/config/paths.js.map +1 -0
  126. package/packages/happy2-server/dist/modules/config/paths.test.d.ts +2 -0
  127. package/packages/happy2-server/dist/modules/config/paths.test.d.ts.map +1 -0
  128. package/packages/happy2-server/dist/modules/config/paths.test.js +22 -0
  129. package/packages/happy2-server/dist/modules/config/paths.test.js.map +1 -0
  130. package/packages/happy2-server/dist/modules/config/runtime.d.ts +6 -0
  131. package/packages/happy2-server/dist/modules/config/runtime.d.ts.map +1 -0
  132. package/packages/happy2-server/dist/modules/config/runtime.js +14 -0
  133. package/packages/happy2-server/dist/modules/config/runtime.js.map +1 -0
  134. package/packages/happy2-server/dist/modules/config/type.d.ts +88 -0
  135. package/packages/happy2-server/dist/modules/config/type.d.ts.map +1 -0
  136. package/packages/happy2-server/dist/modules/config/type.js +2 -0
  137. package/packages/happy2-server/dist/modules/config/type.js.map +1 -0
  138. package/packages/happy2-server/dist/modules/database.d.ts +105 -0
  139. package/packages/happy2-server/dist/modules/database.d.ts.map +1 -0
  140. package/packages/happy2-server/dist/modules/database.js +583 -0
  141. package/packages/happy2-server/dist/modules/database.js.map +1 -0
  142. package/packages/happy2-server/dist/modules/drizzle.d.ts +11 -0
  143. package/packages/happy2-server/dist/modules/drizzle.d.ts.map +1 -0
  144. package/packages/happy2-server/dist/modules/drizzle.js +31 -0
  145. package/packages/happy2-server/dist/modules/drizzle.js.map +1 -0
  146. package/packages/happy2-server/dist/modules/files/lock.d.ts +3 -0
  147. package/packages/happy2-server/dist/modules/files/lock.d.ts.map +1 -0
  148. package/packages/happy2-server/dist/modules/files/lock.js +83 -0
  149. package/packages/happy2-server/dist/modules/files/lock.js.map +1 -0
  150. package/packages/happy2-server/dist/modules/files/media.d.ts +18 -0
  151. package/packages/happy2-server/dist/modules/files/media.d.ts.map +1 -0
  152. package/packages/happy2-server/dist/modules/files/media.js +352 -0
  153. package/packages/happy2-server/dist/modules/files/media.js.map +1 -0
  154. package/packages/happy2-server/dist/modules/files/provider.d.ts +130 -0
  155. package/packages/happy2-server/dist/modules/files/provider.d.ts.map +1 -0
  156. package/packages/happy2-server/dist/modules/files/provider.js +448 -0
  157. package/packages/happy2-server/dist/modules/files/provider.js.map +1 -0
  158. package/packages/happy2-server/dist/modules/files/quota.d.ts +58 -0
  159. package/packages/happy2-server/dist/modules/files/quota.d.ts.map +1 -0
  160. package/packages/happy2-server/dist/modules/files/quota.js +180 -0
  161. package/packages/happy2-server/dist/modules/files/quota.js.map +1 -0
  162. package/packages/happy2-server/dist/modules/files/routes.test.d.ts +2 -0
  163. package/packages/happy2-server/dist/modules/files/routes.test.d.ts.map +1 -0
  164. package/packages/happy2-server/dist/modules/files/routes.test.js +163 -0
  165. package/packages/happy2-server/dist/modules/files/routes.test.js.map +1 -0
  166. package/packages/happy2-server/dist/modules/files/scanner.d.ts +27 -0
  167. package/packages/happy2-server/dist/modules/files/scanner.d.ts.map +1 -0
  168. package/packages/happy2-server/dist/modules/files/scanner.js +66 -0
  169. package/packages/happy2-server/dist/modules/files/scanner.js.map +1 -0
  170. package/packages/happy2-server/dist/modules/files/storage.d.ts +95 -0
  171. package/packages/happy2-server/dist/modules/files/storage.d.ts.map +1 -0
  172. package/packages/happy2-server/dist/modules/files/storage.js +855 -0
  173. package/packages/happy2-server/dist/modules/files/storage.js.map +1 -0
  174. package/packages/happy2-server/dist/modules/files/storage.test.d.ts +2 -0
  175. package/packages/happy2-server/dist/modules/files/storage.test.d.ts.map +1 -0
  176. package/packages/happy2-server/dist/modules/files/storage.test.js +170 -0
  177. package/packages/happy2-server/dist/modules/files/storage.test.js.map +1 -0
  178. package/packages/happy2-server/dist/modules/integrations/index.d.ts +6 -0
  179. package/packages/happy2-server/dist/modules/integrations/index.d.ts.map +1 -0
  180. package/packages/happy2-server/dist/modules/integrations/index.js +6 -0
  181. package/packages/happy2-server/dist/modules/integrations/index.js.map +1 -0
  182. package/packages/happy2-server/dist/modules/integrations/repository.d.ts +136 -0
  183. package/packages/happy2-server/dist/modules/integrations/repository.d.ts.map +1 -0
  184. package/packages/happy2-server/dist/modules/integrations/repository.js +1229 -0
  185. package/packages/happy2-server/dist/modules/integrations/repository.js.map +1 -0
  186. package/packages/happy2-server/dist/modules/integrations/repository.test.d.ts +2 -0
  187. package/packages/happy2-server/dist/modules/integrations/repository.test.d.ts.map +1 -0
  188. package/packages/happy2-server/dist/modules/integrations/repository.test.js +345 -0
  189. package/packages/happy2-server/dist/modules/integrations/repository.test.js.map +1 -0
  190. package/packages/happy2-server/dist/modules/integrations/routes.test.d.ts +2 -0
  191. package/packages/happy2-server/dist/modules/integrations/routes.test.d.ts.map +1 -0
  192. package/packages/happy2-server/dist/modules/integrations/routes.test.js +161 -0
  193. package/packages/happy2-server/dist/modules/integrations/routes.test.js.map +1 -0
  194. package/packages/happy2-server/dist/modules/integrations/secrets.d.ts +19 -0
  195. package/packages/happy2-server/dist/modules/integrations/secrets.d.ts.map +1 -0
  196. package/packages/happy2-server/dist/modules/integrations/secrets.js +69 -0
  197. package/packages/happy2-server/dist/modules/integrations/secrets.js.map +1 -0
  198. package/packages/happy2-server/dist/modules/integrations/ssrf.d.ts +32 -0
  199. package/packages/happy2-server/dist/modules/integrations/ssrf.d.ts.map +1 -0
  200. package/packages/happy2-server/dist/modules/integrations/ssrf.js +185 -0
  201. package/packages/happy2-server/dist/modules/integrations/ssrf.js.map +1 -0
  202. package/packages/happy2-server/dist/modules/integrations/ssrf.test.d.ts +2 -0
  203. package/packages/happy2-server/dist/modules/integrations/ssrf.test.d.ts.map +1 -0
  204. package/packages/happy2-server/dist/modules/integrations/ssrf.test.js +56 -0
  205. package/packages/happy2-server/dist/modules/integrations/ssrf.test.js.map +1 -0
  206. package/packages/happy2-server/dist/modules/integrations/transport.d.ts +21 -0
  207. package/packages/happy2-server/dist/modules/integrations/transport.d.ts.map +1 -0
  208. package/packages/happy2-server/dist/modules/integrations/transport.js +108 -0
  209. package/packages/happy2-server/dist/modules/integrations/transport.js.map +1 -0
  210. package/packages/happy2-server/dist/modules/integrations/transport.test.d.ts +2 -0
  211. package/packages/happy2-server/dist/modules/integrations/transport.test.d.ts.map +1 -0
  212. package/packages/happy2-server/dist/modules/integrations/transport.test.js +66 -0
  213. package/packages/happy2-server/dist/modules/integrations/transport.test.js.map +1 -0
  214. package/packages/happy2-server/dist/modules/integrations/types.d.ts +155 -0
  215. package/packages/happy2-server/dist/modules/integrations/types.d.ts.map +1 -0
  216. package/packages/happy2-server/dist/modules/integrations/types.js +26 -0
  217. package/packages/happy2-server/dist/modules/integrations/types.js.map +1 -0
  218. package/packages/happy2-server/dist/modules/operations/export-worker.d.ts +16 -0
  219. package/packages/happy2-server/dist/modules/operations/export-worker.d.ts.map +1 -0
  220. package/packages/happy2-server/dist/modules/operations/export-worker.js +56 -0
  221. package/packages/happy2-server/dist/modules/operations/export-worker.js.map +1 -0
  222. package/packages/happy2-server/dist/modules/operations/repository.d.ts +215 -0
  223. package/packages/happy2-server/dist/modules/operations/repository.d.ts.map +1 -0
  224. package/packages/happy2-server/dist/modules/operations/repository.js +2011 -0
  225. package/packages/happy2-server/dist/modules/operations/repository.js.map +1 -0
  226. package/packages/happy2-server/dist/modules/operations/repository.test.d.ts +2 -0
  227. package/packages/happy2-server/dist/modules/operations/repository.test.d.ts.map +1 -0
  228. package/packages/happy2-server/dist/modules/operations/repository.test.js +610 -0
  229. package/packages/happy2-server/dist/modules/operations/repository.test.js.map +1 -0
  230. package/packages/happy2-server/dist/modules/operations/types.d.ts +144 -0
  231. package/packages/happy2-server/dist/modules/operations/types.d.ts.map +1 -0
  232. package/packages/happy2-server/dist/modules/operations/types.js +9 -0
  233. package/packages/happy2-server/dist/modules/operations/types.js.map +1 -0
  234. package/packages/happy2-server/dist/modules/realtime/events.d.ts +99 -0
  235. package/packages/happy2-server/dist/modules/realtime/events.d.ts.map +1 -0
  236. package/packages/happy2-server/dist/modules/realtime/events.js +173 -0
  237. package/packages/happy2-server/dist/modules/realtime/events.js.map +1 -0
  238. package/packages/happy2-server/dist/modules/realtime/index.d.ts +4 -0
  239. package/packages/happy2-server/dist/modules/realtime/index.d.ts.map +1 -0
  240. package/packages/happy2-server/dist/modules/realtime/index.js +4 -0
  241. package/packages/happy2-server/dist/modules/realtime/index.js.map +1 -0
  242. package/packages/happy2-server/dist/modules/realtime/local.d.ts +37 -0
  243. package/packages/happy2-server/dist/modules/realtime/local.d.ts.map +1 -0
  244. package/packages/happy2-server/dist/modules/realtime/local.js +181 -0
  245. package/packages/happy2-server/dist/modules/realtime/local.js.map +1 -0
  246. package/packages/happy2-server/dist/modules/realtime/local.test.d.ts +2 -0
  247. package/packages/happy2-server/dist/modules/realtime/local.test.d.ts.map +1 -0
  248. package/packages/happy2-server/dist/modules/realtime/local.test.js +186 -0
  249. package/packages/happy2-server/dist/modules/realtime/local.test.js.map +1 -0
  250. package/packages/happy2-server/dist/modules/realtime/pubsub.d.ts +26 -0
  251. package/packages/happy2-server/dist/modules/realtime/pubsub.d.ts.map +1 -0
  252. package/packages/happy2-server/dist/modules/realtime/pubsub.js +7 -0
  253. package/packages/happy2-server/dist/modules/realtime/pubsub.js.map +1 -0
  254. package/packages/happy2-server/dist/modules/request/database-idempotency.d.ts +15 -0
  255. package/packages/happy2-server/dist/modules/request/database-idempotency.d.ts.map +1 -0
  256. package/packages/happy2-server/dist/modules/request/database-idempotency.js +141 -0
  257. package/packages/happy2-server/dist/modules/request/database-idempotency.js.map +1 -0
  258. package/packages/happy2-server/dist/modules/request/idempotency-hook.d.ts +12 -0
  259. package/packages/happy2-server/dist/modules/request/idempotency-hook.d.ts.map +1 -0
  260. package/packages/happy2-server/dist/modules/request/idempotency-hook.js +98 -0
  261. package/packages/happy2-server/dist/modules/request/idempotency-hook.js.map +1 -0
  262. package/packages/happy2-server/dist/modules/request/idempotency.d.ts +146 -0
  263. package/packages/happy2-server/dist/modules/request/idempotency.d.ts.map +1 -0
  264. package/packages/happy2-server/dist/modules/request/idempotency.js +312 -0
  265. package/packages/happy2-server/dist/modules/request/idempotency.js.map +1 -0
  266. package/packages/happy2-server/dist/modules/request/idempotency.test.d.ts +2 -0
  267. package/packages/happy2-server/dist/modules/request/idempotency.test.d.ts.map +1 -0
  268. package/packages/happy2-server/dist/modules/request/idempotency.test.js +191 -0
  269. package/packages/happy2-server/dist/modules/request/idempotency.test.js.map +1 -0
  270. package/packages/happy2-server/dist/modules/request/index.d.ts +5 -0
  271. package/packages/happy2-server/dist/modules/request/index.d.ts.map +1 -0
  272. package/packages/happy2-server/dist/modules/request/index.js +5 -0
  273. package/packages/happy2-server/dist/modules/request/index.js.map +1 -0
  274. package/packages/happy2-server/dist/modules/request/rate-limit.d.ts +100 -0
  275. package/packages/happy2-server/dist/modules/request/rate-limit.d.ts.map +1 -0
  276. package/packages/happy2-server/dist/modules/request/rate-limit.js +239 -0
  277. package/packages/happy2-server/dist/modules/request/rate-limit.js.map +1 -0
  278. package/packages/happy2-server/dist/modules/request/rate-limit.test.d.ts +2 -0
  279. package/packages/happy2-server/dist/modules/request/rate-limit.test.d.ts.map +1 -0
  280. package/packages/happy2-server/dist/modules/request/rate-limit.test.js +123 -0
  281. package/packages/happy2-server/dist/modules/request/rate-limit.test.js.map +1 -0
  282. package/packages/happy2-server/dist/modules/schema.d.ts +31454 -0
  283. package/packages/happy2-server/dist/modules/schema.d.ts.map +1 -0
  284. package/packages/happy2-server/dist/modules/schema.js +1081 -0
  285. package/packages/happy2-server/dist/modules/schema.js.map +1 -0
  286. package/packages/happy2-server/dist/modules/workspace/index.d.ts +3 -0
  287. package/packages/happy2-server/dist/modules/workspace/index.d.ts.map +1 -0
  288. package/packages/happy2-server/dist/modules/workspace/index.js +3 -0
  289. package/packages/happy2-server/dist/modules/workspace/index.js.map +1 -0
  290. package/packages/happy2-server/dist/modules/workspace/service.d.ts +54 -0
  291. package/packages/happy2-server/dist/modules/workspace/service.d.ts.map +1 -0
  292. package/packages/happy2-server/dist/modules/workspace/service.js +997 -0
  293. package/packages/happy2-server/dist/modules/workspace/service.js.map +1 -0
  294. package/packages/happy2-server/dist/modules/workspace/types.d.ts +58 -0
  295. package/packages/happy2-server/dist/modules/workspace/types.d.ts.map +1 -0
  296. package/packages/happy2-server/dist/modules/workspace/types.js +11 -0
  297. package/packages/happy2-server/dist/modules/workspace/types.js.map +1 -0
  298. package/packages/happy2-server/dist/routes/agents.d.ts +5 -0
  299. package/packages/happy2-server/dist/routes/agents.d.ts.map +1 -0
  300. package/packages/happy2-server/dist/routes/agents.js +225 -0
  301. package/packages/happy2-server/dist/routes/agents.js.map +1 -0
  302. package/packages/happy2-server/dist/routes/auth.d.ts +5 -0
  303. package/packages/happy2-server/dist/routes/auth.d.ts.map +1 -0
  304. package/packages/happy2-server/dist/routes/auth.js +128 -0
  305. package/packages/happy2-server/dist/routes/auth.js.map +1 -0
  306. package/packages/happy2-server/dist/routes/automation.d.ts +6 -0
  307. package/packages/happy2-server/dist/routes/automation.d.ts.map +1 -0
  308. package/packages/happy2-server/dist/routes/automation.js +315 -0
  309. package/packages/happy2-server/dist/routes/automation.js.map +1 -0
  310. package/packages/happy2-server/dist/routes/basic.d.ts +3 -0
  311. package/packages/happy2-server/dist/routes/basic.d.ts.map +1 -0
  312. package/packages/happy2-server/dist/routes/basic.js +5 -0
  313. package/packages/happy2-server/dist/routes/basic.js.map +1 -0
  314. package/packages/happy2-server/dist/routes/collaboration.d.ts +7 -0
  315. package/packages/happy2-server/dist/routes/collaboration.d.ts.map +1 -0
  316. package/packages/happy2-server/dist/routes/collaboration.js +1285 -0
  317. package/packages/happy2-server/dist/routes/collaboration.js.map +1 -0
  318. package/packages/happy2-server/dist/routes/files.d.ts +9 -0
  319. package/packages/happy2-server/dist/routes/files.d.ts.map +1 -0
  320. package/packages/happy2-server/dist/routes/files.js +397 -0
  321. package/packages/happy2-server/dist/routes/files.js.map +1 -0
  322. package/packages/happy2-server/dist/routes/integrations.d.ts +6 -0
  323. package/packages/happy2-server/dist/routes/integrations.d.ts.map +1 -0
  324. package/packages/happy2-server/dist/routes/integrations.js +486 -0
  325. package/packages/happy2-server/dist/routes/integrations.js.map +1 -0
  326. package/packages/happy2-server/dist/routes/operations.d.ts +5 -0
  327. package/packages/happy2-server/dist/routes/operations.d.ts.map +1 -0
  328. package/packages/happy2-server/dist/routes/operations.js +588 -0
  329. package/packages/happy2-server/dist/routes/operations.js.map +1 -0
  330. package/packages/happy2-server/dist/routes/operations.test.d.ts +2 -0
  331. package/packages/happy2-server/dist/routes/operations.test.d.ts.map +1 -0
  332. package/packages/happy2-server/dist/routes/operations.test.js +106 -0
  333. package/packages/happy2-server/dist/routes/operations.test.js.map +1 -0
  334. package/packages/happy2-server/dist/routes/sync.d.ts +6 -0
  335. package/packages/happy2-server/dist/routes/sync.d.ts.map +1 -0
  336. package/packages/happy2-server/dist/routes/sync.js +549 -0
  337. package/packages/happy2-server/dist/routes/sync.js.map +1 -0
  338. package/packages/happy2-server/dist/routes/workspace.d.ts +5 -0
  339. package/packages/happy2-server/dist/routes/workspace.d.ts.map +1 -0
  340. package/packages/happy2-server/dist/routes/workspace.js +240 -0
  341. package/packages/happy2-server/dist/routes/workspace.js.map +1 -0
  342. package/packages/happy2-server/dist/runner.d.ts +3 -0
  343. package/packages/happy2-server/dist/runner.d.ts.map +1 -0
  344. package/packages/happy2-server/dist/runner.js +30 -0
  345. package/packages/happy2-server/dist/runner.js.map +1 -0
  346. package/packages/happy2-server/dist/server.d.ts +32 -0
  347. package/packages/happy2-server/dist/server.d.ts.map +1 -0
  348. package/packages/happy2-server/dist/server.js +284 -0
  349. package/packages/happy2-server/dist/server.js.map +1 -0
  350. package/packages/happy2-server/dist/standalone.d.ts +13 -0
  351. package/packages/happy2-server/dist/standalone.d.ts.map +1 -0
  352. package/packages/happy2-server/dist/standalone.js +99 -0
  353. package/packages/happy2-server/dist/standalone.js.map +1 -0
  354. package/packages/happy2-server/dist/web/assets/figtree-latin-wght-normal-D_ZTVpCC.woff2 +0 -0
  355. package/packages/happy2-server/dist/web/assets/happy-otter-Q2O_g-DT.png +0 -0
  356. package/packages/happy2-server/dist/web/assets/index-COvbX8JP.css +2 -0
  357. package/packages/happy2-server/dist/web/assets/index-DdpugxMO.js +57 -0
  358. package/packages/happy2-server/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  359. package/packages/happy2-server/dist/web/assets/onboarding-CRUpq9xl.png +0 -0
  360. package/packages/happy2-server/dist/web/index.html +13 -0
  361. package/packages/happy2-server/drizzle/0000_initial.sql +13 -0
  362. package/packages/happy2-server/drizzle/0001_accounts_profiles_files.sql +42 -0
  363. package/packages/happy2-server/drizzle/0002_file_visibility_and_uploader.sql +5 -0
  364. package/packages/happy2-server/drizzle/0003_collaboration.sql +565 -0
  365. package/packages/happy2-server/drizzle/0004_collaboration_expansion.sql +1099 -0
  366. package/packages/happy2-server/drizzle/0005_ai_agents.sql +85 -0
  367. package/packages/happy2-server/drizzle/0006_rig_global_event_queue.sql +10 -0
  368. package/packages/happy2-server/drizzle/0007_rename_server_identity.sql +3 -0
  369. package/packages/happy2-server/drizzle/0008_agent_message_streaming.sql +47 -0
  370. package/packages/happy2-server/drizzle/0009_chat_workspace_binding_lookup.sql +2 -0
  371. package/packages/happy2-server/drizzle/0010_rig_secret_assignments.sql +21 -0
  372. package/packages/happy2-server/drizzle/meta/_journal.json +83 -0
  373. package/packages/happy2-server/happy2.example.toml +104 -0
@@ -0,0 +1,855 @@
1
+ import { basename, join } from "node:path";
2
+ import { Transform } from "node:stream";
3
+ import { pipeline } from "node:stream/promises";
4
+ import { createId } from "@paralleldrive/cuid2";
5
+ import sharp from "sharp";
6
+ import { rgbaToThumbHash } from "thumbhash";
7
+ import { BuiltinMediaProcessor } from "./media.js";
8
+ import { LocalFileStorageProvider, parseCompletion, parseUpload, UploadIncompleteError, UploadLimitError, UploadNotFoundError, UploadOffsetError, } from "./provider.js";
9
+ import { LocalFileQuotaPolicy, QuotaExceededError, } from "./quota.js";
10
+ import { CommandMalwareScanner, DisabledMalwareScanner } from "./scanner.js";
11
+ const MAX_AVATAR_BYTES = 10 * 1024 * 1024;
12
+ const MAX_AVATAR_SIDE = 2048;
13
+ export class InvalidUploadError extends Error {
14
+ constructor(message, options) {
15
+ super(message, options);
16
+ this.name = "InvalidUploadError";
17
+ }
18
+ }
19
+ export class UploadRejectedError extends Error {
20
+ reason;
21
+ constructor(reason) {
22
+ super("Upload was rejected by the file safety policy");
23
+ this.reason = reason;
24
+ this.name = "UploadRejectedError";
25
+ }
26
+ }
27
+ export class FileQuotaExceededError extends InvalidUploadError {
28
+ scope;
29
+ limit;
30
+ constructor(scope, limit) {
31
+ super(`${scope === "user" ? "User" : "Server"} file quota exceeded`);
32
+ this.scope = scope;
33
+ this.limit = limit;
34
+ this.name = "FileQuotaExceededError";
35
+ }
36
+ }
37
+ export class FileStorage {
38
+ config;
39
+ database;
40
+ provider;
41
+ quota;
42
+ scanner;
43
+ mediaProcessor;
44
+ constructor(config, database, optionsOrFileSystem = {}) {
45
+ this.config = config;
46
+ this.database = database;
47
+ const options = isFileStorageFileSystem(optionsOrFileSystem)
48
+ ? {
49
+ provider: new FileSystemStorageProvider(config.files.directory, optionsOrFileSystem),
50
+ quota: new MemoryFileQuotaPolicy({
51
+ perUserBytes: config.files.perUserQuotaBytes,
52
+ serverBytes: config.files.serverQuotaBytes,
53
+ }, optionsOrFileSystem),
54
+ mediaProcessor: new FileSystemMediaProcessor(optionsOrFileSystem),
55
+ }
56
+ : optionsOrFileSystem;
57
+ this.provider = options.provider ?? new LocalFileStorageProvider(config.files.directory);
58
+ this.quota =
59
+ options.quota ??
60
+ new LocalFileQuotaPolicy(config.files.directory, {
61
+ perUserBytes: config.files.perUserQuotaBytes,
62
+ serverBytes: config.files.serverQuotaBytes,
63
+ }, () => this.provider.committedBytes());
64
+ this.scanner = options.scanner ?? configuredScanner(config);
65
+ this.mediaProcessor = options.mediaProcessor ?? new BuiltinMediaProcessor();
66
+ }
67
+ async saveAvatarUpload(user, input, isPublic) {
68
+ if (input.length === 0 || input.length > MAX_AVATAR_BYTES)
69
+ throw new InvalidUploadError("Avatar file must be at most 10 MB");
70
+ const image = sharp(input, { limitInputPixels: MAX_AVATAR_SIDE * MAX_AVATAR_SIDE });
71
+ let metadata;
72
+ try {
73
+ metadata = await image.metadata();
74
+ }
75
+ catch (error) {
76
+ throw new InvalidUploadError("Avatar must be a valid image", { cause: error });
77
+ }
78
+ if (!metadata.width ||
79
+ !metadata.height ||
80
+ metadata.width > MAX_AVATAR_SIDE ||
81
+ metadata.height > MAX_AVATAR_SIDE)
82
+ throw new InvalidUploadError("Avatar dimensions must not exceed 2048px");
83
+ let thumbnail;
84
+ let encoded;
85
+ try {
86
+ [thumbnail, encoded] = await Promise.all([avatarThumbnail(image), encodeAvatar(image)]);
87
+ }
88
+ catch (error) {
89
+ throw new InvalidUploadError("Avatar could not be decoded", { cause: error });
90
+ }
91
+ const thumbhash = Buffer.from(rgbaToThumbHash(thumbnail.info.width, thumbnail.info.height, thumbnail.data)).toString("base64url");
92
+ const reservationId = createId();
93
+ await this.quota.reserve({
94
+ id: reservationId,
95
+ ownerUserId: user.id,
96
+ bytes: encoded.data.length,
97
+ });
98
+ let staged;
99
+ try {
100
+ staged = await this.provider.stageBuffer(encoded.data, reservationId);
101
+ }
102
+ catch (error) {
103
+ await this.quota.release(reservationId);
104
+ throw mapQuotaError(error);
105
+ }
106
+ const file = {
107
+ id: reservationId,
108
+ userId: user.id,
109
+ uploadedByUserId: user.id,
110
+ isPublic,
111
+ storageName: `${reservationId}.jpg`,
112
+ contentType: "image/jpeg",
113
+ size: encoded.data.length,
114
+ width: encoded.info.width,
115
+ height: encoded.info.height,
116
+ thumbhash,
117
+ kind: "photo",
118
+ originalName: "avatar.jpg",
119
+ };
120
+ try {
121
+ const scan = await this.assertSafe(staged);
122
+ return await this.commitFile(file, staged, {}, file.size, scan);
123
+ }
124
+ catch (error) {
125
+ await this.provider.discard(staged);
126
+ if (!(error instanceof UploadRejectedError))
127
+ await this.quota.release(file.id);
128
+ throw mapQuotaError(error);
129
+ }
130
+ }
131
+ async saveAttachmentUpload(user, input, upload) {
132
+ const reservationId = createId();
133
+ let staged;
134
+ try {
135
+ await this.quota.reserve({ id: reservationId, ownerUserId: user.id, bytes: 1 });
136
+ staged = await this.provider.stage(input, this.config.files.maxUploadBytes, {
137
+ id: reservationId,
138
+ onBytes: (bytes) => this.quota.resizeReservation(reservationId, bytes),
139
+ });
140
+ }
141
+ catch (error) {
142
+ await this.quota.release(reservationId);
143
+ if (error instanceof UploadLimitError ||
144
+ error.code === "FST_REQ_FILE_TOO_LARGE")
145
+ throw new InvalidUploadError("Attachment exceeds the configured upload limit", {
146
+ cause: error,
147
+ });
148
+ throw mapQuotaError(error);
149
+ }
150
+ if (staged.size === 0) {
151
+ await this.provider.discard(staged);
152
+ await this.quota.release(reservationId);
153
+ throw new InvalidUploadError("Attachment must not be empty");
154
+ }
155
+ return this.saveStagedAttachment(user, staged, {
156
+ filename: upload.filename,
157
+ contentType: upload.contentType,
158
+ }, reservationId);
159
+ }
160
+ async createResumableUpload(user, input) {
161
+ if (!Number.isSafeInteger(input.size) ||
162
+ input.size < 1 ||
163
+ input.size > this.config.files.maxUploadBytes)
164
+ throw new InvalidUploadError("Upload size is outside the configured limit");
165
+ const id = createId();
166
+ try {
167
+ await this.quota.reserve({ id, ownerUserId: user.id, bytes: input.size });
168
+ return await this.provider.createResumable({
169
+ id,
170
+ ownerUserId: user.id,
171
+ filename: normalizedFilename(input.filename),
172
+ contentType: safeContentType(input.contentType),
173
+ size: input.size,
174
+ });
175
+ }
176
+ catch (error) {
177
+ await this.quota.release(id);
178
+ throw mapQuotaError(error);
179
+ }
180
+ }
181
+ async resumableUploadState(userId, uploadId) {
182
+ const upload = await this.provider.resumableState(uploadId);
183
+ return upload?.ownerUserId === userId ? upload : undefined;
184
+ }
185
+ async appendResumableUpload(userId, uploadId, expectedOffset, input) {
186
+ const state = await this.resumableUploadState(userId, uploadId);
187
+ if (!state)
188
+ return undefined;
189
+ return this.provider.appendResumable(uploadId, expectedOffset, input, this.config.files.resumableChunkBytes);
190
+ }
191
+ async completeResumableUpload(user, uploadId) {
192
+ const completed = await this.provider.completedResumable(uploadId);
193
+ if (completed)
194
+ return completed.ownerUserId === user.id
195
+ ? await this.database.findFile(completed.fileId)
196
+ : undefined;
197
+ const state = await this.resumableUploadState(user.id, uploadId);
198
+ if (!state)
199
+ return undefined;
200
+ const durable = await this.database.findFile(uploadId);
201
+ if (durable) {
202
+ await this.provider
203
+ .recordResumableCompletion({
204
+ uploadId,
205
+ ownerUserId: user.id,
206
+ fileId: durable.id,
207
+ completedAt: new Date().toISOString(),
208
+ })
209
+ .catch(() => undefined);
210
+ return durable;
211
+ }
212
+ const { upload, staged } = await this.provider.finishResumable(uploadId);
213
+ let file;
214
+ try {
215
+ file = await this.saveStagedAttachment(user, staged, { filename: upload.filename, contentType: upload.contentType }, upload.id);
216
+ }
217
+ catch (error) {
218
+ await this.quota.release(upload.id);
219
+ throw error;
220
+ }
221
+ await this.provider
222
+ .recordResumableCompletion({
223
+ uploadId: upload.id,
224
+ ownerUserId: user.id,
225
+ fileId: file.id,
226
+ completedAt: new Date().toISOString(),
227
+ })
228
+ .catch(() => undefined);
229
+ return file;
230
+ }
231
+ async cancelResumableUpload(userId, uploadId) {
232
+ const state = await this.resumableUploadState(userId, uploadId);
233
+ if (!state)
234
+ return false;
235
+ const cancelled = await this.provider.cancelResumable(uploadId);
236
+ if (cancelled)
237
+ await this.quota.release(uploadId);
238
+ return cancelled;
239
+ }
240
+ open(file, range) {
241
+ return this.provider.open(file.storageName, range);
242
+ }
243
+ async variant(file, variant) {
244
+ const size = await this.provider.variantSize(file.storageName, variant);
245
+ return size === undefined
246
+ ? undefined
247
+ : {
248
+ size,
249
+ contentType: "image/webp",
250
+ stream: this.provider.openVariant(file.storageName, variant),
251
+ };
252
+ }
253
+ async variantSizes(file) {
254
+ const [thumbnail, preview] = await Promise.allSettled([
255
+ this.provider.variantSize(file.storageName, "thumbnail"),
256
+ this.provider.variantSize(file.storageName, "preview"),
257
+ ]);
258
+ return {
259
+ thumbnail: thumbnail.status === "fulfilled" ? thumbnail.value : undefined,
260
+ preview: preview.status === "fulfilled" ? preview.value : undefined,
261
+ };
262
+ }
263
+ /** Local-only compatibility helper; consumers should prefer open(). */
264
+ pathFor(file) {
265
+ if (!this.provider.pathFor)
266
+ throw new Error("Storage provider does not expose local paths");
267
+ return this.provider.pathFor(file.storageName);
268
+ }
269
+ /** Call only after the durable file record and references have been removed. */
270
+ async deleteStoredFile(file) {
271
+ await this.provider.delete(file.storageName);
272
+ await this.quota.release(file.id);
273
+ }
274
+ /**
275
+ * Maintenance is intentionally caller-driven so deployments can run it
276
+ * under a distributed lease. It cleans abandoned uploads/quarantine and,
277
+ * when given the authoritative file records, unreferenced local objects.
278
+ */
279
+ async runMaintenance(input) {
280
+ const now = input.now ?? new Date();
281
+ const temporaryBefore = new Date(now.getTime() - this.config.files.incompleteUploadExpirySeconds * 1000);
282
+ const quarantineBefore = new Date(now.getTime() - this.config.files.quarantineRetentionSeconds * 1000);
283
+ const cleanup = await this.provider.cleanup(temporaryBefore, quarantineBefore);
284
+ await Promise.all([...cleanup.expiredUploadIds, ...cleanup.deletedQuarantineIds].map((id) => this.quota.release(id)));
285
+ let deletedOrphanStorageNames = [];
286
+ if (input.referencedFiles) {
287
+ const files = [...input.referencedFiles];
288
+ deletedOrphanStorageNames = await this.provider.cleanupOrphans(new Set(files.map((file) => file.storageName)), new Date(now.getTime() - (input.orphanGraceMs ?? 24 * 60 * 60 * 1000)));
289
+ await Promise.all(deletedOrphanStorageNames.map(async (storageName) => {
290
+ const id = storageName.replace(/\.(?:blob|jpg)$/, "");
291
+ if (id !== storageName)
292
+ await this.quota.release(id);
293
+ }));
294
+ const committed = await Promise.all(files.map(async (file) => ({
295
+ id: file.id,
296
+ ownerUserId: file.uploadedByUserId,
297
+ bytes: file.size +
298
+ ((await this.provider.variantSize(file.storageName, "thumbnail")) ?? 0) +
299
+ ((await this.provider.variantSize(file.storageName, "preview")) ?? 0),
300
+ })));
301
+ await this.quota.reconcileCommitted(committed);
302
+ }
303
+ return { ...cleanup, deletedOrphanStorageNames };
304
+ }
305
+ async saveStagedAttachment(user, staged, upload, resumableReservationId) {
306
+ let fileId;
307
+ try {
308
+ const scan = await this.assertSafe(staged);
309
+ const media = await this.mediaProcessor.inspect(staged.inspectionPath, upload.contentType);
310
+ fileId = resumableReservationId ?? staged.id;
311
+ const file = {
312
+ id: fileId,
313
+ userId: user.id,
314
+ uploadedByUserId: user.id,
315
+ isPublic: false,
316
+ storageName: `${fileId}.blob`,
317
+ originalName: normalizedFilename(upload.filename),
318
+ contentType: media.contentType,
319
+ kind: media.kind,
320
+ size: staged.size,
321
+ width: media.width,
322
+ height: media.height,
323
+ thumbhash: media.thumbhash,
324
+ durationMs: media.durationMs,
325
+ };
326
+ const storedBytes = staged.size +
327
+ Object.values(media.variants).reduce((total, variant) => total + (variant?.length ?? 0), 0);
328
+ if (resumableReservationId)
329
+ await this.quota.resizeReservation(resumableReservationId, storedBytes);
330
+ else
331
+ await this.prepareQuota(file.id, user.id, storedBytes);
332
+ return await this.commitFile(file, staged, media.variants, storedBytes, scan);
333
+ }
334
+ catch (error) {
335
+ await this.provider.discard(staged);
336
+ if (!(error instanceof UploadRejectedError)) {
337
+ if (fileId)
338
+ await this.quota.release(fileId);
339
+ else if (resumableReservationId)
340
+ await this.quota.release(resumableReservationId);
341
+ }
342
+ throw mapQuotaError(error);
343
+ }
344
+ }
345
+ async prepareQuota(id, ownerUserId, bytes) {
346
+ await this.quota.reserve({ id, ownerUserId, bytes });
347
+ }
348
+ async assertSafe(staged) {
349
+ const result = await this.scanner.scan(staged.inspectionPath);
350
+ if (result.verdict === "clean")
351
+ return {
352
+ status: this.scanner instanceof DisabledMalwareScanner ? "skipped" : "clean",
353
+ };
354
+ if (result.verdict === "error" && this.config.files.malwareScanFailureMode === "allow")
355
+ return { status: "failed", result: { message: result.message } };
356
+ await this.provider.quarantine(staged, result.verdict === "infected" ? "malware" : "scanner_unavailable");
357
+ throw new UploadRejectedError(result.verdict === "infected" ? "malware" : "scanner_unavailable");
358
+ }
359
+ async commitFile(file, staged, variants, storedBytes, scan) {
360
+ let committed = false;
361
+ try {
362
+ await this.provider.commit(staged, file.storageName);
363
+ committed = true;
364
+ for (const [variant, contents] of Object.entries(variants)) {
365
+ if (contents)
366
+ await this.provider.writeVariant(file.storageName, variant, contents);
367
+ }
368
+ await this.database.createFile(file, scan);
369
+ // Once the database row exists the upload is durable. A failed
370
+ // ledger state transition remains a conservative reservation and
371
+ // is repaired by the next authoritative maintenance pass.
372
+ await this.quota.commit(file.id, storedBytes).catch(() => undefined);
373
+ return file;
374
+ }
375
+ catch (error) {
376
+ if (committed)
377
+ await this.provider.delete(file.storageName);
378
+ await this.quota.release(file.id);
379
+ throw error;
380
+ }
381
+ }
382
+ createReadStream(file, range) {
383
+ return this.open(file, range);
384
+ }
385
+ }
386
+ /** Compatibility adapter used by the in-memory gym harness. */
387
+ class FileSystemStorageProvider {
388
+ directory;
389
+ fileSystem;
390
+ sizes = new Map();
391
+ committed = new Map();
392
+ variants = new Map();
393
+ uploads = new Map();
394
+ completions = new Map();
395
+ quarantineTimes = new Map();
396
+ constructor(directory, fileSystem) {
397
+ this.directory = directory;
398
+ this.fileSystem = fileSystem;
399
+ }
400
+ async stage(input, maximumBytes, options = {}) {
401
+ const id = options.id ?? createId();
402
+ const path = this.stagingPath(id);
403
+ await this.fileSystem.mkdir(join(this.directory, ".staging"));
404
+ let size = 0;
405
+ const limiter = new Transform({
406
+ transform(chunk, _encoding, callback) {
407
+ size += chunk.length;
408
+ if (size > maximumBytes) {
409
+ callback(new UploadLimitError("Upload exceeds the configured limit"));
410
+ return;
411
+ }
412
+ if (!options.onBytes) {
413
+ callback(undefined, chunk);
414
+ return;
415
+ }
416
+ void options.onBytes(size).then(() => callback(undefined, chunk), (error) => callback(error));
417
+ },
418
+ });
419
+ try {
420
+ await pipeline(input, limiter, this.fileSystem.createWriteStream(path));
421
+ if (input.truncated)
422
+ throw new UploadLimitError("Upload exceeds the configured limit");
423
+ this.sizes.set(path, size);
424
+ return { id, size, inspectionPath: path };
425
+ }
426
+ catch (error) {
427
+ await this.fileSystem.rm(path);
428
+ this.sizes.delete(path);
429
+ throw error;
430
+ }
431
+ }
432
+ async stageBuffer(input, suppliedId) {
433
+ const id = suppliedId ?? createId();
434
+ const path = this.stagingPath(id);
435
+ await this.fileSystem.mkdir(join(this.directory, ".staging"));
436
+ await this.fileSystem.writeFile(path, input);
437
+ this.sizes.set(path, input.length);
438
+ return { id, size: input.length, inspectionPath: path };
439
+ }
440
+ async commit(staged, storageName) {
441
+ const target = this.objectPath(storageName);
442
+ await this.fileSystem.rename(staged.inspectionPath, target);
443
+ this.sizes.delete(staged.inspectionPath);
444
+ this.sizes.set(target, staged.size);
445
+ this.committed.set(storageName, Date.now());
446
+ }
447
+ async discard(staged) {
448
+ await this.fileSystem.rm(staged.inspectionPath);
449
+ this.sizes.delete(staged.inspectionPath);
450
+ }
451
+ async quarantine(staged) {
452
+ const target = join(this.directory, ".quarantine", `${staged.id}.blob`);
453
+ await this.fileSystem.mkdir(join(this.directory, ".quarantine"));
454
+ await this.fileSystem.rename(staged.inspectionPath, target);
455
+ const size = this.sizes.get(staged.inspectionPath) ?? staged.size;
456
+ this.sizes.delete(staged.inspectionPath);
457
+ this.sizes.set(target, size);
458
+ this.quarantineTimes.set(target, Date.now());
459
+ }
460
+ open(storageName, range) {
461
+ return this.fileSystem.createReadStream(this.objectPath(storageName), range);
462
+ }
463
+ pathFor(storageName) {
464
+ return this.objectPath(storageName);
465
+ }
466
+ async sizeOf(storageName) {
467
+ return this.knownSize(this.objectPath(storageName));
468
+ }
469
+ async writeVariant(storageName, variant, input) {
470
+ const path = this.variantPath(storageName, variant);
471
+ await this.fileSystem.writeFile(path, input);
472
+ this.sizes.set(path, input.length);
473
+ this.variants.set(`${storageName}:${variant}`, input.length);
474
+ }
475
+ openVariant(storageName, variant) {
476
+ return this.fileSystem.createReadStream(this.variantPath(storageName, variant));
477
+ }
478
+ async variantSize(storageName, variant) {
479
+ return (this.variants.get(`${storageName}:${variant}`) ??
480
+ (await this.knownSize(this.variantPath(storageName, variant))));
481
+ }
482
+ async delete(storageName) {
483
+ await Promise.all([
484
+ this.fileSystem.rm(this.objectPath(storageName)),
485
+ this.fileSystem.rm(this.variantPath(storageName, "thumbnail")),
486
+ this.fileSystem.rm(this.variantPath(storageName, "preview")),
487
+ ]);
488
+ this.sizes.delete(this.objectPath(storageName));
489
+ this.committed.delete(storageName);
490
+ for (const variant of ["thumbnail", "preview"]) {
491
+ this.sizes.delete(this.variantPath(storageName, variant));
492
+ this.variants.delete(`${storageName}:${variant}`);
493
+ }
494
+ }
495
+ async committedBytes() {
496
+ let total = 0;
497
+ for (const name of this.committed.keys())
498
+ total += this.sizes.get(this.objectPath(name)) ?? 0;
499
+ for (const size of this.variants.values())
500
+ total += size;
501
+ return total;
502
+ }
503
+ async createResumable(input) {
504
+ if (await this.resumableState(input.id))
505
+ throw new Error("Upload already exists");
506
+ const now = new Date().toISOString();
507
+ const upload = { ...input, offset: 0, createdAt: now, updatedAt: now };
508
+ await this.fileSystem.mkdir(join(this.directory, ".uploads"));
509
+ await this.fileSystem.writeFile(this.uploadPath(input.id), Buffer.alloc(0));
510
+ this.sizes.set(this.uploadPath(input.id), 0);
511
+ await this.writeUploadManifest(upload);
512
+ this.uploads.set(input.id, upload);
513
+ return { ...upload };
514
+ }
515
+ async resumableState(uploadId) {
516
+ const cached = this.uploads.get(uploadId);
517
+ if (cached)
518
+ return { ...cached };
519
+ try {
520
+ const source = await this.fileSystem.imageSource(this.uploadManifestPath(uploadId));
521
+ const upload = parseUpload(Buffer.from(source).toString("utf8"));
522
+ this.uploads.set(uploadId, upload);
523
+ this.sizes.set(this.uploadPath(uploadId), upload.offset);
524
+ return { ...upload };
525
+ }
526
+ catch (error) {
527
+ if (error.code === "ENOENT")
528
+ return undefined;
529
+ throw error;
530
+ }
531
+ }
532
+ async appendResumable(uploadId, expectedOffset, input, maximumChunkBytes) {
533
+ const upload = await this.resumableState(uploadId);
534
+ if (!upload)
535
+ throw new UploadNotFoundError();
536
+ if (upload.offset !== expectedOffset)
537
+ throw new UploadOffsetError(upload.offset);
538
+ const chunk = await readStream(input, maximumChunkBytes);
539
+ if (chunk.length === 0 || upload.offset + chunk.length > upload.size)
540
+ throw new UploadLimitError("Upload chunk exceeds its declared size");
541
+ const path = this.uploadPath(uploadId);
542
+ const existing = await this.fileSystem.imageSource(path);
543
+ const prefix = Buffer.isBuffer(existing) ? existing : Buffer.alloc(0);
544
+ const contents = Buffer.concat([prefix.subarray(0, upload.offset), chunk]);
545
+ await this.fileSystem.writeFile(path, contents);
546
+ upload.offset = contents.length;
547
+ upload.updatedAt = new Date().toISOString();
548
+ await this.writeUploadManifest(upload);
549
+ this.uploads.set(uploadId, upload);
550
+ this.sizes.set(path, contents.length);
551
+ return { ...upload };
552
+ }
553
+ async finishResumable(uploadId) {
554
+ const upload = await this.resumableState(uploadId);
555
+ if (!upload)
556
+ throw new UploadNotFoundError();
557
+ if (upload.offset !== upload.size)
558
+ throw new UploadIncompleteError(upload.offset, upload.size);
559
+ const inspectionPath = this.stagingPath(uploadId);
560
+ try {
561
+ await this.fileSystem.rename(this.uploadPath(uploadId), inspectionPath);
562
+ }
563
+ catch (error) {
564
+ if (error.code !== "ENOENT" ||
565
+ (await this.knownSize(inspectionPath)) !== upload.size)
566
+ throw error;
567
+ }
568
+ this.sizes.delete(this.uploadPath(uploadId));
569
+ this.sizes.set(inspectionPath, upload.size);
570
+ return {
571
+ upload: { ...upload },
572
+ staged: { id: uploadId, size: upload.size, inspectionPath },
573
+ };
574
+ }
575
+ async completedResumable(uploadId) {
576
+ const cached = this.completions.get(uploadId);
577
+ if (cached)
578
+ return { ...cached };
579
+ try {
580
+ const source = await this.fileSystem.imageSource(this.receiptPath(uploadId));
581
+ const completion = parseCompletion(Buffer.from(source).toString("utf8"));
582
+ this.completions.set(uploadId, completion);
583
+ return { ...completion };
584
+ }
585
+ catch (error) {
586
+ if (error.code === "ENOENT")
587
+ return undefined;
588
+ throw error;
589
+ }
590
+ }
591
+ async recordResumableCompletion(completion) {
592
+ await this.fileSystem.mkdir(join(this.directory, ".receipts"));
593
+ await this.fileSystem.writeFile(this.receiptPath(completion.uploadId), Buffer.from(JSON.stringify(completion)));
594
+ await this.fileSystem.rm(this.uploadManifestPath(completion.uploadId));
595
+ this.uploads.delete(completion.uploadId);
596
+ this.completions.set(completion.uploadId, { ...completion });
597
+ }
598
+ async cancelResumable(uploadId) {
599
+ if (!(await this.resumableState(uploadId)))
600
+ return false;
601
+ this.uploads.delete(uploadId);
602
+ await Promise.all([
603
+ this.fileSystem.rm(this.uploadPath(uploadId)),
604
+ this.fileSystem.rm(this.uploadManifestPath(uploadId)),
605
+ this.fileSystem.rm(this.stagingPath(uploadId)),
606
+ ]);
607
+ this.sizes.delete(this.uploadPath(uploadId));
608
+ return true;
609
+ }
610
+ async cleanup(before, quarantineBefore) {
611
+ const expiredUploadIds = [];
612
+ for (const [id, upload] of this.uploads) {
613
+ if (Date.parse(upload.updatedAt) >= before.getTime())
614
+ continue;
615
+ await this.cancelResumable(id);
616
+ expiredUploadIds.push(id);
617
+ }
618
+ let deletedQuarantineObjects = 0;
619
+ const deletedQuarantineIds = [];
620
+ for (const [path, createdAt] of this.quarantineTimes) {
621
+ if (createdAt >= quarantineBefore.getTime())
622
+ continue;
623
+ await this.fileSystem.rm(path);
624
+ this.sizes.delete(path);
625
+ this.quarantineTimes.delete(path);
626
+ deletedQuarantineObjects += 1;
627
+ deletedQuarantineIds.push(basename(path, ".blob"));
628
+ }
629
+ return { expiredUploadIds, deletedQuarantineIds, deletedQuarantineObjects };
630
+ }
631
+ async cleanupOrphans(referencedStorageNames, before) {
632
+ const deleted = [];
633
+ for (const [storageName, createdAt] of this.committed) {
634
+ if (referencedStorageNames.has(storageName) || createdAt >= before.getTime())
635
+ continue;
636
+ await this.delete(storageName);
637
+ deleted.push(storageName);
638
+ }
639
+ return deleted;
640
+ }
641
+ stagingPath(id) {
642
+ return join(this.directory, ".staging", `${id}.part`);
643
+ }
644
+ uploadPath(id) {
645
+ return join(this.directory, ".uploads", `${id}.part`);
646
+ }
647
+ uploadManifestPath(id) {
648
+ return join(this.directory, ".uploads", `${id}.json`);
649
+ }
650
+ receiptPath(id) {
651
+ return join(this.directory, ".receipts", `${id}.json`);
652
+ }
653
+ async writeUploadManifest(upload) {
654
+ await this.fileSystem.writeFile(this.uploadManifestPath(upload.id), Buffer.from(JSON.stringify(upload)));
655
+ }
656
+ async knownSize(path) {
657
+ const cached = this.sizes.get(path);
658
+ if (cached !== undefined)
659
+ return cached;
660
+ try {
661
+ const source = await this.fileSystem.imageSource(path);
662
+ const size = Buffer.byteLength(source);
663
+ this.sizes.set(path, size);
664
+ return size;
665
+ }
666
+ catch (error) {
667
+ if (error.code === "ENOENT")
668
+ return undefined;
669
+ throw error;
670
+ }
671
+ }
672
+ objectPath(storageName) {
673
+ return join(this.directory, storageName);
674
+ }
675
+ variantPath(storageName, variant) {
676
+ return join(this.directory, `.${storageName}.${variant}.webp`);
677
+ }
678
+ }
679
+ const compatibilityQuotaLedgers = new WeakMap();
680
+ class MemoryFileQuotaPolicy {
681
+ limits;
682
+ entries;
683
+ constructor(limits, fileSystem) {
684
+ this.limits = limits;
685
+ const entries = compatibilityQuotaLedgers.get(fileSystem) ?? new Map();
686
+ compatibilityQuotaLedgers.set(fileSystem, entries);
687
+ this.entries = entries;
688
+ }
689
+ async reserve(input) {
690
+ if (this.entries.has(input.id))
691
+ throw new Error("Quota reservation already exists");
692
+ this.assertCapacity(input.ownerUserId, input.bytes);
693
+ this.entries.set(input.id, { ...input, committed: false });
694
+ }
695
+ async resizeReservation(id, actualBytes) {
696
+ const entry = this.required(id);
697
+ this.assertCapacity(entry.ownerUserId, actualBytes - entry.bytes);
698
+ entry.bytes = actualBytes;
699
+ }
700
+ async commit(id, actualBytes) {
701
+ await this.resizeReservation(id, actualBytes);
702
+ this.required(id).committed = true;
703
+ }
704
+ async release(id) {
705
+ this.entries.delete(id);
706
+ }
707
+ async reconcileCommitted(files) {
708
+ for (const [id, entry] of this.entries)
709
+ if (entry.committed)
710
+ this.entries.delete(id);
711
+ for (const file of files)
712
+ this.entries.set(file.id, { ...file, committed: true });
713
+ }
714
+ required(id) {
715
+ const entry = this.entries.get(id);
716
+ if (!entry)
717
+ throw new Error("Quota reservation does not exist");
718
+ return entry;
719
+ }
720
+ assertCapacity(ownerUserId, addedBytes) {
721
+ if (addedBytes <= 0)
722
+ return;
723
+ const userBytes = [...this.entries.values()]
724
+ .filter((entry) => entry.ownerUserId === ownerUserId)
725
+ .reduce((total, entry) => total + entry.bytes, 0);
726
+ if (this.limits.perUserBytes > 0 && userBytes + addedBytes > this.limits.perUserBytes)
727
+ throw new QuotaExceededError("user", this.limits.perUserBytes);
728
+ const serverBytes = [...this.entries.values()].reduce((total, entry) => total + entry.bytes, 0);
729
+ if (this.limits.serverBytes > 0 && serverBytes + addedBytes > this.limits.serverBytes)
730
+ throw new QuotaExceededError("server", this.limits.serverBytes);
731
+ }
732
+ }
733
+ class FileSystemMediaProcessor {
734
+ fileSystem;
735
+ constructor(fileSystem) {
736
+ this.fileSystem = fileSystem;
737
+ }
738
+ async inspect(path, suppliedContentType) {
739
+ const header = await this.fileSystem.readHeader(path, 64);
740
+ const signature = memoryMediaSignature(header);
741
+ const contentType = signature?.contentType ??
742
+ safeContentType(suppliedContentType) ??
743
+ "application/octet-stream";
744
+ const kind = signature?.kind ?? (contentType.startsWith("video/") ? "video" : "file");
745
+ if (kind !== "photo" && kind !== "gif")
746
+ return { kind, contentType, width: 0, height: 0, thumbhash: "", variants: {} };
747
+ try {
748
+ const source = await this.fileSystem.imageSource(path);
749
+ const image = sharp(source, { limitInputPixels: 100_000_000, animated: false });
750
+ const metadata = await image.metadata();
751
+ const thumbhashInput = await image
752
+ .clone()
753
+ .resize(100, 100, { fit: "inside", withoutEnlargement: true })
754
+ .ensureAlpha()
755
+ .raw()
756
+ .toBuffer({ resolveWithObject: true });
757
+ const [thumbnail, preview] = await Promise.all([
758
+ image
759
+ .clone()
760
+ .resize(320, 320, { fit: "inside", withoutEnlargement: true })
761
+ .webp({ quality: 78 })
762
+ .toBuffer(),
763
+ image
764
+ .clone()
765
+ .resize(1600, 1600, { fit: "inside", withoutEnlargement: true })
766
+ .webp({ quality: 84 })
767
+ .toBuffer(),
768
+ ]);
769
+ return {
770
+ kind,
771
+ contentType,
772
+ width: metadata.width ?? 0,
773
+ height: metadata.pageHeight ?? metadata.height ?? 0,
774
+ durationMs: kind === "gif" && metadata.delay?.length
775
+ ? metadata.delay.reduce((total, delay) => total + delay, 0)
776
+ : undefined,
777
+ thumbhash: Buffer.from(rgbaToThumbHash(thumbhashInput.info.width, thumbhashInput.info.height, thumbhashInput.data)).toString("base64url"),
778
+ variants: { thumbnail, preview },
779
+ };
780
+ }
781
+ catch {
782
+ return { kind: "file", contentType, width: 0, height: 0, thumbhash: "", variants: {} };
783
+ }
784
+ }
785
+ }
786
+ function isFileStorageFileSystem(value) {
787
+ return typeof value.createWriteStream === "function";
788
+ }
789
+ function memoryMediaSignature(header) {
790
+ if (header.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff])))
791
+ return { kind: "photo", contentType: "image/jpeg" };
792
+ if (header.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex")))
793
+ return { kind: "photo", contentType: "image/png" };
794
+ if (header.subarray(0, 6).toString("ascii").startsWith("GIF8"))
795
+ return { kind: "gif", contentType: "image/gif" };
796
+ if (header.subarray(0, 4).toString("ascii") === "RIFF" &&
797
+ header.subarray(8, 12).toString("ascii") === "WEBP")
798
+ return { kind: "photo", contentType: "image/webp" };
799
+ if (header.subarray(4, 8).toString("ascii") === "ftyp")
800
+ return { kind: "video", contentType: "video/mp4" };
801
+ if (header.subarray(0, 4).equals(Buffer.from([0x1a, 0x45, 0xdf, 0xa3])))
802
+ return { kind: "video", contentType: "video/webm" };
803
+ return undefined;
804
+ }
805
+ async function readStream(input, maximumBytes) {
806
+ const chunks = [];
807
+ let size = 0;
808
+ for await (const chunk of input) {
809
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
810
+ size += buffer.length;
811
+ if (size > maximumBytes)
812
+ throw new UploadLimitError("Upload chunk exceeds the configured limit");
813
+ chunks.push(buffer);
814
+ }
815
+ return Buffer.concat(chunks);
816
+ }
817
+ function configuredScanner(config) {
818
+ return config.files.malwareScannerCommand
819
+ ? new CommandMalwareScanner(config.files.malwareScannerCommand, config.files.malwareScannerArguments, config.files.malwareScanTimeoutSeconds * 1000)
820
+ : new DisabledMalwareScanner();
821
+ }
822
+ function normalizedFilename(filename) {
823
+ const localName = filename?.trim().replaceAll("\\", "/") || "attachment";
824
+ const normalized = [...basename(localName)]
825
+ .filter((character) => character.charCodeAt(0) >= 32)
826
+ .join("");
827
+ return (normalized || "attachment").slice(0, 255);
828
+ }
829
+ function safeContentType(value) {
830
+ return value && /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/i.test(value)
831
+ ? value.toLowerCase()
832
+ : undefined;
833
+ }
834
+ function mapQuotaError(error) {
835
+ if (error instanceof QuotaExceededError)
836
+ return new FileQuotaExceededError(error.scope, error.limit);
837
+ return error;
838
+ }
839
+ function avatarThumbnail(image) {
840
+ return image
841
+ .clone()
842
+ .resize(100, 100, { fit: "cover" })
843
+ .ensureAlpha()
844
+ .raw()
845
+ .toBuffer({ resolveWithObject: true });
846
+ }
847
+ function encodeAvatar(image) {
848
+ return image
849
+ .clone()
850
+ .rotate()
851
+ .resize(1024, 1024, { fit: "cover" })
852
+ .jpeg({ quality: 88, progressive: true, mozjpeg: true })
853
+ .toBuffer({ resolveWithObject: true });
854
+ }
855
+ //# sourceMappingURL=storage.js.map