@gettersethya/mira 0.1.0-alpha.2

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 (338) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/app/app.d.ts +96 -0
  3. package/dist/src/app/app.d.ts.map +1 -0
  4. package/dist/src/app/app.js +139 -0
  5. package/dist/src/app/app.js.map +1 -0
  6. package/dist/src/app/builder.d.ts +106 -0
  7. package/dist/src/app/builder.d.ts.map +1 -0
  8. package/dist/src/app/builder.js +122 -0
  9. package/dist/src/app/builder.js.map +1 -0
  10. package/dist/src/app/index.d.ts +31 -0
  11. package/dist/src/app/index.d.ts.map +1 -0
  12. package/dist/src/app/index.js +29 -0
  13. package/dist/src/app/index.js.map +1 -0
  14. package/dist/src/app/types.d.ts +55 -0
  15. package/dist/src/app/types.d.ts.map +1 -0
  16. package/dist/src/app/types.js +2 -0
  17. package/dist/src/app/types.js.map +1 -0
  18. package/dist/src/cache/cached-collection.d.ts +30 -0
  19. package/dist/src/cache/cached-collection.d.ts.map +1 -0
  20. package/dist/src/cache/cached-collection.js +107 -0
  21. package/dist/src/cache/cached-collection.js.map +1 -0
  22. package/dist/src/cache/collection-cache.d.ts +17 -0
  23. package/dist/src/cache/collection-cache.d.ts.map +1 -0
  24. package/dist/src/cache/collection-cache.js +37 -0
  25. package/dist/src/cache/collection-cache.js.map +1 -0
  26. package/dist/src/cache/index.d.ts +4 -0
  27. package/dist/src/cache/index.d.ts.map +1 -0
  28. package/dist/src/cache/index.js +3 -0
  29. package/dist/src/cache/index.js.map +1 -0
  30. package/dist/src/cache/types.d.ts +12 -0
  31. package/dist/src/cache/types.d.ts.map +1 -0
  32. package/dist/src/cache/types.js +2 -0
  33. package/dist/src/cache/types.js.map +1 -0
  34. package/dist/src/collection-service/collection-service.d.ts +62 -0
  35. package/dist/src/collection-service/collection-service.d.ts.map +1 -0
  36. package/dist/src/collection-service/collection-service.js +267 -0
  37. package/dist/src/collection-service/collection-service.js.map +1 -0
  38. package/dist/src/collection-service/context.d.ts +17 -0
  39. package/dist/src/collection-service/context.d.ts.map +1 -0
  40. package/dist/src/collection-service/context.js +2 -0
  41. package/dist/src/collection-service/context.js.map +1 -0
  42. package/dist/src/collection-service/decode.d.ts +6 -0
  43. package/dist/src/collection-service/decode.d.ts.map +1 -0
  44. package/dist/src/collection-service/decode.js +26 -0
  45. package/dist/src/collection-service/decode.js.map +1 -0
  46. package/dist/src/collection-service/errors.d.ts +67 -0
  47. package/dist/src/collection-service/errors.d.ts.map +1 -0
  48. package/dist/src/collection-service/errors.js +43 -0
  49. package/dist/src/collection-service/errors.js.map +1 -0
  50. package/dist/src/collection-service/index.d.ts +5 -0
  51. package/dist/src/collection-service/index.d.ts.map +1 -0
  52. package/dist/src/collection-service/index.js +3 -0
  53. package/dist/src/collection-service/index.js.map +1 -0
  54. package/dist/src/collection-service/input-schema.d.ts +10 -0
  55. package/dist/src/collection-service/input-schema.d.ts.map +1 -0
  56. package/dist/src/collection-service/input-schema.js +109 -0
  57. package/dist/src/collection-service/input-schema.js.map +1 -0
  58. package/dist/src/collection-service/where.d.ts +35 -0
  59. package/dist/src/collection-service/where.d.ts.map +1 -0
  60. package/dist/src/collection-service/where.js +88 -0
  61. package/dist/src/collection-service/where.js.map +1 -0
  62. package/dist/src/config/config.d.ts +44 -0
  63. package/dist/src/config/config.d.ts.map +1 -0
  64. package/dist/src/config/config.js +89 -0
  65. package/dist/src/config/config.js.map +1 -0
  66. package/dist/src/config/index.d.ts +3 -0
  67. package/dist/src/config/index.d.ts.map +1 -0
  68. package/dist/src/config/index.js +2 -0
  69. package/dist/src/config/index.js.map +1 -0
  70. package/dist/src/config/types.d.ts +29 -0
  71. package/dist/src/config/types.d.ts.map +1 -0
  72. package/dist/src/config/types.js +2 -0
  73. package/dist/src/config/types.js.map +1 -0
  74. package/dist/src/crypto/crypto.d.ts +11 -0
  75. package/dist/src/crypto/crypto.d.ts.map +1 -0
  76. package/dist/src/crypto/crypto.js +4 -0
  77. package/dist/src/crypto/crypto.js.map +1 -0
  78. package/dist/src/crypto/index.d.ts +3 -0
  79. package/dist/src/crypto/index.d.ts.map +1 -0
  80. package/dist/src/crypto/index.js +3 -0
  81. package/dist/src/crypto/index.js.map +1 -0
  82. package/dist/src/crypto/node.d.ts +4 -0
  83. package/dist/src/crypto/node.d.ts.map +1 -0
  84. package/dist/src/crypto/node.js +9 -0
  85. package/dist/src/crypto/node.js.map +1 -0
  86. package/dist/src/databases/index.d.ts +2 -0
  87. package/dist/src/databases/index.d.ts.map +1 -0
  88. package/dist/src/databases/index.js +2 -0
  89. package/dist/src/databases/index.js.map +1 -0
  90. package/dist/src/databases/sqlite.d.ts +24 -0
  91. package/dist/src/databases/sqlite.d.ts.map +1 -0
  92. package/dist/src/databases/sqlite.js +27 -0
  93. package/dist/src/databases/sqlite.js.map +1 -0
  94. package/dist/src/http/auth-node.d.ts +4 -0
  95. package/dist/src/http/auth-node.d.ts.map +1 -0
  96. package/dist/src/http/auth-node.js +34 -0
  97. package/dist/src/http/auth-node.js.map +1 -0
  98. package/dist/src/http/auth.d.ts +38 -0
  99. package/dist/src/http/auth.d.ts.map +1 -0
  100. package/dist/src/http/auth.js +66 -0
  101. package/dist/src/http/auth.js.map +1 -0
  102. package/dist/src/http/errors.d.ts +31 -0
  103. package/dist/src/http/errors.d.ts.map +1 -0
  104. package/dist/src/http/errors.js +36 -0
  105. package/dist/src/http/errors.js.map +1 -0
  106. package/dist/src/http/file-serve.d.ts +10 -0
  107. package/dist/src/http/file-serve.d.ts.map +1 -0
  108. package/dist/src/http/file-serve.js +159 -0
  109. package/dist/src/http/file-serve.js.map +1 -0
  110. package/dist/src/http/file-token.d.ts +7 -0
  111. package/dist/src/http/file-token.d.ts.map +1 -0
  112. package/dist/src/http/file-token.js +55 -0
  113. package/dist/src/http/file-token.js.map +1 -0
  114. package/dist/src/http/files.d.ts +11 -0
  115. package/dist/src/http/files.d.ts.map +1 -0
  116. package/dist/src/http/files.js +85 -0
  117. package/dist/src/http/files.js.map +1 -0
  118. package/dist/src/http/index.d.ts +10 -0
  119. package/dist/src/http/index.d.ts.map +1 -0
  120. package/dist/src/http/index.js +8 -0
  121. package/dist/src/http/index.js.map +1 -0
  122. package/dist/src/http/ip-middleware.d.ts +4 -0
  123. package/dist/src/http/ip-middleware.d.ts.map +1 -0
  124. package/dist/src/http/ip-middleware.js +21 -0
  125. package/dist/src/http/ip-middleware.js.map +1 -0
  126. package/dist/src/http/params.d.ts +15 -0
  127. package/dist/src/http/params.d.ts.map +1 -0
  128. package/dist/src/http/params.js +48 -0
  129. package/dist/src/http/params.js.map +1 -0
  130. package/dist/src/http/router.d.ts +11 -0
  131. package/dist/src/http/router.d.ts.map +1 -0
  132. package/dist/src/http/router.js +216 -0
  133. package/dist/src/http/router.js.map +1 -0
  134. package/dist/src/http/server-factory-node.d.ts +4 -0
  135. package/dist/src/http/server-factory-node.d.ts.map +1 -0
  136. package/dist/src/http/server-factory-node.js +8 -0
  137. package/dist/src/http/server-factory-node.js.map +1 -0
  138. package/dist/src/http/server-factory.d.ts +11 -0
  139. package/dist/src/http/server-factory.d.ts.map +1 -0
  140. package/dist/src/http/server-factory.js +4 -0
  141. package/dist/src/http/server-factory.js.map +1 -0
  142. package/dist/src/index.d.ts +8 -0
  143. package/dist/src/index.d.ts.map +1 -0
  144. package/dist/src/index.js +10 -0
  145. package/dist/src/index.js.map +1 -0
  146. package/dist/src/migrator/dialect-sqlite.d.ts +9 -0
  147. package/dist/src/migrator/dialect-sqlite.d.ts.map +1 -0
  148. package/dist/src/migrator/dialect-sqlite.js +102 -0
  149. package/dist/src/migrator/dialect-sqlite.js.map +1 -0
  150. package/dist/src/migrator/dialect.d.ts +24 -0
  151. package/dist/src/migrator/dialect.d.ts.map +1 -0
  152. package/dist/src/migrator/dialect.js +5 -0
  153. package/dist/src/migrator/dialect.js.map +1 -0
  154. package/dist/src/migrator/index.d.ts +8 -0
  155. package/dist/src/migrator/index.d.ts.map +1 -0
  156. package/dist/src/migrator/index.js +6 -0
  157. package/dist/src/migrator/index.js.map +1 -0
  158. package/dist/src/migrator/migrator.d.ts +36 -0
  159. package/dist/src/migrator/migrator.d.ts.map +1 -0
  160. package/dist/src/migrator/migrator.js +200 -0
  161. package/dist/src/migrator/migrator.js.map +1 -0
  162. package/dist/src/migrator/schema-diff.d.ts +25 -0
  163. package/dist/src/migrator/schema-diff.d.ts.map +1 -0
  164. package/dist/src/migrator/schema-diff.js +157 -0
  165. package/dist/src/migrator/schema-diff.js.map +1 -0
  166. package/dist/src/migrator/types.d.ts +91 -0
  167. package/dist/src/migrator/types.d.ts.map +1 -0
  168. package/dist/src/migrator/types.js +13 -0
  169. package/dist/src/migrator/types.js.map +1 -0
  170. package/dist/src/platforms/node.d.ts +40 -0
  171. package/dist/src/platforms/node.d.ts.map +1 -0
  172. package/dist/src/platforms/node.js +45 -0
  173. package/dist/src/platforms/node.js.map +1 -0
  174. package/dist/src/repository/id.d.ts +5 -0
  175. package/dist/src/repository/id.d.ts.map +1 -0
  176. package/dist/src/repository/id.js +9 -0
  177. package/dist/src/repository/id.js.map +1 -0
  178. package/dist/src/repository/index.d.ts +3 -0
  179. package/dist/src/repository/index.d.ts.map +1 -0
  180. package/dist/src/repository/index.js +2 -0
  181. package/dist/src/repository/index.js.map +1 -0
  182. package/dist/src/repository/repository.d.ts +53 -0
  183. package/dist/src/repository/repository.d.ts.map +1 -0
  184. package/dist/src/repository/repository.js +150 -0
  185. package/dist/src/repository/repository.js.map +1 -0
  186. package/dist/src/repository/types.d.ts +42 -0
  187. package/dist/src/repository/types.d.ts.map +1 -0
  188. package/dist/src/repository/types.js +2 -0
  189. package/dist/src/repository/types.js.map +1 -0
  190. package/dist/src/rule/compiler.d.ts +52 -0
  191. package/dist/src/rule/compiler.d.ts.map +1 -0
  192. package/dist/src/rule/compiler.js +113 -0
  193. package/dist/src/rule/compiler.js.map +1 -0
  194. package/dist/src/rule/enforcer.d.ts +47 -0
  195. package/dist/src/rule/enforcer.d.ts.map +1 -0
  196. package/dist/src/rule/enforcer.js +60 -0
  197. package/dist/src/rule/enforcer.js.map +1 -0
  198. package/dist/src/rule/index.d.ts +3 -0
  199. package/dist/src/rule/index.d.ts.map +1 -0
  200. package/dist/src/rule/index.js +3 -0
  201. package/dist/src/rule/index.js.map +1 -0
  202. package/dist/src/storage/index.d.ts +3 -0
  203. package/dist/src/storage/index.d.ts.map +1 -0
  204. package/dist/src/storage/index.js +3 -0
  205. package/dist/src/storage/index.js.map +1 -0
  206. package/dist/src/storage/local-preset.d.ts +21 -0
  207. package/dist/src/storage/local-preset.d.ts.map +1 -0
  208. package/dist/src/storage/local-preset.js +21 -0
  209. package/dist/src/storage/local-preset.js.map +1 -0
  210. package/dist/src/storage/storage.d.ts +83 -0
  211. package/dist/src/storage/storage.d.ts.map +1 -0
  212. package/dist/src/storage/storage.js +120 -0
  213. package/dist/src/storage/storage.js.map +1 -0
  214. package/dist/src/telemetry/index.d.ts +6 -0
  215. package/dist/src/telemetry/index.d.ts.map +1 -0
  216. package/dist/src/telemetry/index.js +7 -0
  217. package/dist/src/telemetry/index.js.map +1 -0
  218. package/dist/src/telemetry/logger.d.ts +4 -0
  219. package/dist/src/telemetry/logger.d.ts.map +1 -0
  220. package/dist/src/telemetry/logger.js +27 -0
  221. package/dist/src/telemetry/logger.js.map +1 -0
  222. package/dist/src/telemetry/tracer.d.ts +16 -0
  223. package/dist/src/telemetry/tracer.d.ts.map +1 -0
  224. package/dist/src/telemetry/tracer.js +106 -0
  225. package/dist/src/telemetry/tracer.js.map +1 -0
  226. package/dist/src/thumbnail/index.d.ts +4 -0
  227. package/dist/src/thumbnail/index.d.ts.map +1 -0
  228. package/dist/src/thumbnail/index.js +3 -0
  229. package/dist/src/thumbnail/index.js.map +1 -0
  230. package/dist/src/thumbnail/photon.d.ts +9 -0
  231. package/dist/src/thumbnail/photon.d.ts.map +1 -0
  232. package/dist/src/thumbnail/photon.js +73 -0
  233. package/dist/src/thumbnail/photon.js.map +1 -0
  234. package/dist/src/thumbnail/types.d.ts +23 -0
  235. package/dist/src/thumbnail/types.d.ts.map +1 -0
  236. package/dist/src/thumbnail/types.js +22 -0
  237. package/dist/src/thumbnail/types.js.map +1 -0
  238. package/dist/tests/app/app.test.d.ts +2 -0
  239. package/dist/tests/app/app.test.d.ts.map +1 -0
  240. package/dist/tests/app/app.test.js +78 -0
  241. package/dist/tests/app/app.test.js.map +1 -0
  242. package/dist/tests/app/builder.test.d.ts +2 -0
  243. package/dist/tests/app/builder.test.d.ts.map +1 -0
  244. package/dist/tests/app/builder.test.js +69 -0
  245. package/dist/tests/app/builder.test.js.map +1 -0
  246. package/dist/tests/cache/cached-collection.test.d.ts +2 -0
  247. package/dist/tests/cache/cached-collection.test.d.ts.map +1 -0
  248. package/dist/tests/cache/cached-collection.test.js +244 -0
  249. package/dist/tests/cache/cached-collection.test.js.map +1 -0
  250. package/dist/tests/cache/collection-cache.test.d.ts +2 -0
  251. package/dist/tests/cache/collection-cache.test.d.ts.map +1 -0
  252. package/dist/tests/cache/collection-cache.test.js +73 -0
  253. package/dist/tests/cache/collection-cache.test.js.map +1 -0
  254. package/dist/tests/collection-service/collection-service.test.d.ts +2 -0
  255. package/dist/tests/collection-service/collection-service.test.d.ts.map +1 -0
  256. package/dist/tests/collection-service/collection-service.test.js +805 -0
  257. package/dist/tests/collection-service/collection-service.test.js.map +1 -0
  258. package/dist/tests/collection-service/input-schema.test.d.ts +2 -0
  259. package/dist/tests/collection-service/input-schema.test.d.ts.map +1 -0
  260. package/dist/tests/collection-service/input-schema.test.js +260 -0
  261. package/dist/tests/collection-service/input-schema.test.js.map +1 -0
  262. package/dist/tests/config/config.test.d.ts +2 -0
  263. package/dist/tests/config/config.test.d.ts.map +1 -0
  264. package/dist/tests/config/config.test.js +84 -0
  265. package/dist/tests/config/config.test.js.map +1 -0
  266. package/dist/tests/crypto/crypto.test.d.ts +2 -0
  267. package/dist/tests/crypto/crypto.test.d.ts.map +1 -0
  268. package/dist/tests/crypto/crypto.test.js +56 -0
  269. package/dist/tests/crypto/crypto.test.js.map +1 -0
  270. package/dist/tests/http/file-serve.test.d.ts +2 -0
  271. package/dist/tests/http/file-serve.test.d.ts.map +1 -0
  272. package/dist/tests/http/file-serve.test.js +210 -0
  273. package/dist/tests/http/file-serve.test.js.map +1 -0
  274. package/dist/tests/http/file-token.test.d.ts +2 -0
  275. package/dist/tests/http/file-token.test.d.ts.map +1 -0
  276. package/dist/tests/http/file-token.test.js +155 -0
  277. package/dist/tests/http/file-token.test.js.map +1 -0
  278. package/dist/tests/http/ip-middleware.test.d.ts +2 -0
  279. package/dist/tests/http/ip-middleware.test.d.ts.map +1 -0
  280. package/dist/tests/http/ip-middleware.test.js +58 -0
  281. package/dist/tests/http/ip-middleware.test.js.map +1 -0
  282. package/dist/tests/http/router.test.d.ts +2 -0
  283. package/dist/tests/http/router.test.d.ts.map +1 -0
  284. package/dist/tests/http/router.test.js +252 -0
  285. package/dist/tests/http/router.test.js.map +1 -0
  286. package/dist/tests/migrator/dialect-sqlite.test.d.ts +2 -0
  287. package/dist/tests/migrator/dialect-sqlite.test.d.ts.map +1 -0
  288. package/dist/tests/migrator/dialect-sqlite.test.js +90 -0
  289. package/dist/tests/migrator/dialect-sqlite.test.js.map +1 -0
  290. package/dist/tests/migrator/integration.test.d.ts +2 -0
  291. package/dist/tests/migrator/integration.test.d.ts.map +1 -0
  292. package/dist/tests/migrator/integration.test.js +180 -0
  293. package/dist/tests/migrator/integration.test.js.map +1 -0
  294. package/dist/tests/migrator/migrator.test.d.ts +2 -0
  295. package/dist/tests/migrator/migrator.test.d.ts.map +1 -0
  296. package/dist/tests/migrator/migrator.test.js +225 -0
  297. package/dist/tests/migrator/migrator.test.js.map +1 -0
  298. package/dist/tests/migrator/schema-diff.test.d.ts +2 -0
  299. package/dist/tests/migrator/schema-diff.test.d.ts.map +1 -0
  300. package/dist/tests/migrator/schema-diff.test.js +323 -0
  301. package/dist/tests/migrator/schema-diff.test.js.map +1 -0
  302. package/dist/tests/repository/repository.test.d.ts +2 -0
  303. package/dist/tests/repository/repository.test.d.ts.map +1 -0
  304. package/dist/tests/repository/repository.test.js +255 -0
  305. package/dist/tests/repository/repository.test.js.map +1 -0
  306. package/dist/tests/rule/compiler.test.d.ts +2 -0
  307. package/dist/tests/rule/compiler.test.d.ts.map +1 -0
  308. package/dist/tests/rule/compiler.test.js +226 -0
  309. package/dist/tests/rule/compiler.test.js.map +1 -0
  310. package/dist/tests/rule/enforcer.test.d.ts +2 -0
  311. package/dist/tests/rule/enforcer.test.d.ts.map +1 -0
  312. package/dist/tests/rule/enforcer.test.js +190 -0
  313. package/dist/tests/rule/enforcer.test.js.map +1 -0
  314. package/dist/tests/storage/storage.test.d.ts +2 -0
  315. package/dist/tests/storage/storage.test.d.ts.map +1 -0
  316. package/dist/tests/storage/storage.test.js +76 -0
  317. package/dist/tests/storage/storage.test.js.map +1 -0
  318. package/dist/tests/telemetry/instrumentation.test.d.ts +2 -0
  319. package/dist/tests/telemetry/instrumentation.test.d.ts.map +1 -0
  320. package/dist/tests/telemetry/instrumentation.test.js +226 -0
  321. package/dist/tests/telemetry/instrumentation.test.js.map +1 -0
  322. package/dist/tests/telemetry/logger.test.d.ts +2 -0
  323. package/dist/tests/telemetry/logger.test.d.ts.map +1 -0
  324. package/dist/tests/telemetry/logger.test.js +56 -0
  325. package/dist/tests/telemetry/logger.test.js.map +1 -0
  326. package/dist/tests/telemetry/tracer.test.d.ts +2 -0
  327. package/dist/tests/telemetry/tracer.test.d.ts.map +1 -0
  328. package/dist/tests/telemetry/tracer.test.js +63 -0
  329. package/dist/tests/telemetry/tracer.test.js.map +1 -0
  330. package/dist/tests/thumbnail/photon.test.d.ts +2 -0
  331. package/dist/tests/thumbnail/photon.test.d.ts.map +1 -0
  332. package/dist/tests/thumbnail/photon.test.js +478 -0
  333. package/dist/tests/thumbnail/photon.test.js.map +1 -0
  334. package/dist/tests/thumbnail/thumbnail.test.d.ts +2 -0
  335. package/dist/tests/thumbnail/thumbnail.test.d.ts.map +1 -0
  336. package/dist/tests/thumbnail/thumbnail.test.js +35 -0
  337. package/dist/tests/thumbnail/thumbnail.test.js.map +1 -0
  338. package/package.json +46 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACtD,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { AppConfig, AppConfigLive } from "./config.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,29 @@
1
+ import type { Option, Redacted } from "effect";
2
+ /**
3
+ * Optional S3 storage configuration.
4
+ * Only loaded when `useS3` is true in AppConfig.
5
+ *
6
+ * @see AppConfig.useS3 — whether S3 is enabled
7
+ * @see AppConfig.s3Config — the Option<S3Config>
8
+ */
9
+ export interface S3Config {
10
+ readonly endpoint: Redacted.Redacted<string>;
11
+ readonly bucketName: string;
12
+ readonly regionName: string;
13
+ readonly accessKey: Redacted.Redacted<string>;
14
+ readonly secret: Redacted.Redacted<string>;
15
+ }
16
+ /**
17
+ * Shape of the application configuration stored in the `_config` DB table.
18
+ *
19
+ * @see AppConfig — the Context.Tag that provides this
20
+ */
21
+ export interface AppConfigShape {
22
+ readonly appName: string;
23
+ readonly port: number;
24
+ readonly applicationUrl: string;
25
+ readonly jwtSecret: Redacted.Redacted<string>;
26
+ readonly useS3: boolean;
27
+ readonly s3Config: Option.Option<S3Config>;
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC7C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;CAC3C;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC7C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;CAC3C"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/config/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { Context, Effect } from "effect";
2
+ declare const CryptoService_base: Context.TagClass<CryptoService, "CryptoService", {
3
+ /** Synchronous variant — for use in callbacks that cannot yield an Effect. */
4
+ randomBytesSync(size: number): Uint8Array;
5
+ randomBytes(size: number): Effect.Effect<Uint8Array>;
6
+ randomUUID(): Effect.Effect<string>;
7
+ }>;
8
+ export declare class CryptoService extends CryptoService_base {
9
+ }
10
+ export {};
11
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../src/crypto/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;;IAKpC,8EAA8E;0BACxD,MAAM,GAAG,UAAU;sBACvB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;kBACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;;AANvC,qBAAa,aAAc,SAAQ,kBAQhC;CAAG"}
@@ -0,0 +1,4 @@
1
+ import { Context } from "effect";
2
+ export class CryptoService extends Context.Tag("CryptoService")() {
3
+ }
4
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/crypto/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,MAAM,QAAQ,CAAA;AAExC,MAAM,OAAO,aAAc,SAAQ,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAQ5D;CAAG"}
@@ -0,0 +1,3 @@
1
+ export { CryptoService } from "./crypto.js";
2
+ export { NodeCryptoLayer } from "./node.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/crypto/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { CryptoService } from "./crypto.js";
2
+ export { NodeCryptoLayer } from "./node.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/crypto/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { Layer } from "effect";
2
+ import { CryptoService } from "./crypto.js";
3
+ export declare const NodeCryptoLayer: Layer.Layer<CryptoService, never, never>;
4
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/crypto/node.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,eAAO,MAAM,eAAe,0CAO3B,CAAA"}
@@ -0,0 +1,9 @@
1
+ import * as nodeCrypto from "node:crypto";
2
+ import { Effect, Layer } from "effect";
3
+ import { CryptoService } from "./crypto.js";
4
+ export const NodeCryptoLayer = Layer.succeed(CryptoService, CryptoService.of({
5
+ randomBytesSync: (size) => nodeCrypto.randomBytes(size),
6
+ randomBytes: (size) => Effect.sync(() => nodeCrypto.randomBytes(size)),
7
+ randomUUID: () => Effect.sync(() => nodeCrypto.randomUUID())
8
+ }));
9
+ //# sourceMappingURL=node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../src/crypto/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAC1C,aAAa,EACb,aAAa,CAAC,EAAE,CAAC;IACf,eAAe,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC;IACvD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACtE,UAAU,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC;CAC7D,CAAC,CACH,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { SqliteDatabase } from "./sqlite.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/databases/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { SqliteDatabase } from "./sqlite.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/databases/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,24 @@
1
+ import type { MiraDatabase } from "../app/types.js";
2
+ /**
3
+ * SQLite database preset using `@effect/sql-sqlite-node`.
4
+ *
5
+ * Creates a `MiraDatabase` that provides both the SQLite `SqlClient`
6
+ * and the `Dialect` (for schema generation). The layer uses `Layer.orDie`
7
+ * to convert `ConfigError` into a runtime panic — if the config is wrong,
8
+ * the server should not start.
9
+ *
10
+ * @param config.filename - Path to the SQLite database file (use `":memory:"` for testing)
11
+ * @returns A MiraDatabase preset
12
+ *
13
+ * @example
14
+ * Mira.builder()
15
+ * .database(SqliteDatabase({ filename: "data.db" }))
16
+ * // or for testing:
17
+ * .database(SqliteDatabase({ filename: ":memory:" }))
18
+ *
19
+ * @see MiraDatabase — the interface SqliteDatabase implements
20
+ */
21
+ export declare const SqliteDatabase: (config: {
22
+ filename: string;
23
+ }) => MiraDatabase;
24
+ //# sourceMappingURL=sqlite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../../src/databases/sqlite.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAAG,YAK5D,CAAA"}
@@ -0,0 +1,27 @@
1
+ import { SqliteClient } from "@effect/sql-sqlite-node";
2
+ import { Layer } from "effect";
3
+ import { Dialect } from "../migrator/dialect.js";
4
+ import { sqliteDialect } from "../migrator/dialect-sqlite.js";
5
+ /**
6
+ * SQLite database preset using `@effect/sql-sqlite-node`.
7
+ *
8
+ * Creates a `MiraDatabase` that provides both the SQLite `SqlClient`
9
+ * and the `Dialect` (for schema generation). The layer uses `Layer.orDie`
10
+ * to convert `ConfigError` into a runtime panic — if the config is wrong,
11
+ * the server should not start.
12
+ *
13
+ * @param config.filename - Path to the SQLite database file (use `":memory:"` for testing)
14
+ * @returns A MiraDatabase preset
15
+ *
16
+ * @example
17
+ * Mira.builder()
18
+ * .database(SqliteDatabase({ filename: "data.db" }))
19
+ * // or for testing:
20
+ * .database(SqliteDatabase({ filename: ":memory:" }))
21
+ *
22
+ * @see MiraDatabase — the interface SqliteDatabase implements
23
+ */
24
+ export const SqliteDatabase = (config) => ({
25
+ layer: Layer.mergeAll(SqliteClient.layer({ filename: config.filename }).pipe(Layer.orDie), Layer.succeed(Dialect, sqliteDialect)),
26
+ });
27
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../../src/databases/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAA;AAG5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAA4B,EAAgB,EAAE,CAAC,CAAC;IAC7E,KAAK,EAAE,KAAK,CAAC,QAAQ,CACnB,YAAY,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EACnE,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,aAAa,CAAC,CACtC;CACF,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { Layer } from "effect";
2
+ import { AuthService } from "./auth.js";
3
+ export declare const NodeAuthServiceLayer: Layer.Layer<AuthService, never, never>;
4
+ //# sourceMappingURL=auth-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-node.d.ts","sourceRoot":"","sources":["../../../src/http/auth-node.ts"],"names":[],"mappings":"AACA,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAcvC,eAAO,MAAM,oBAAoB,wCAoBhC,CAAA"}
@@ -0,0 +1,34 @@
1
+ import * as nodeCrypto from "node:crypto";
2
+ import { Effect, Layer } from "effect";
3
+ import { AuthService } from "./auth.js";
4
+ function scryptHash(plain, salt) {
5
+ return Effect.async((resume) => {
6
+ nodeCrypto.scrypt(plain, salt, 64, (err, key) => {
7
+ if (err) {
8
+ resume(Effect.die(err));
9
+ }
10
+ else {
11
+ resume(Effect.succeed(key));
12
+ }
13
+ });
14
+ }).pipe(Effect.map((key) => key.toString("hex")));
15
+ }
16
+ export const NodeAuthServiceLayer = Layer.succeed(AuthService, AuthService.of({
17
+ hashPassword: (plain) => Effect.gen(function* () {
18
+ const salt = nodeCrypto.randomBytes(16).toString("hex");
19
+ const hash = yield* scryptHash(plain, salt);
20
+ return `scrypt$${salt}$${hash}`;
21
+ }),
22
+ verifyPassword: (plain, stored) => Effect.gen(function* () {
23
+ const parts = stored.split("$");
24
+ if (parts.length !== 3 || parts[0] !== "scrypt")
25
+ return false;
26
+ const salt = parts[1];
27
+ const expected = parts[2];
28
+ if (salt === undefined || expected === undefined)
29
+ return false;
30
+ const hash = yield* scryptHash(plain, salt);
31
+ return hash === expected;
32
+ })
33
+ }));
34
+ //# sourceMappingURL=auth-node.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-node.js","sourceRoot":"","sources":["../../../src/http/auth-node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAEvC,SAAS,UAAU,CAAC,KAAa,EAAE,IAAY;IAC7C,OAAO,MAAM,CAAC,KAAK,CAAgB,CAAC,MAAM,EAAE,EAAE;QAC5C,UAAU,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC9C,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;YACzB,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAA;YAC7B,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACnD,CAAC;AAED,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC,OAAO,CAC/C,WAAW,EACX,WAAW,CAAC,EAAE,CAAC;IACb,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CACtB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACvD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAC3C,OAAO,UAAU,IAAI,IAAI,IAAI,EAAE,CAAA;IACjC,CAAC,CAAC;IACJ,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,CAChC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAA;QAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACrB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACzB,IAAI,IAAI,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QAC3C,OAAO,IAAI,KAAK,QAAQ,CAAA;IAC1B,CAAC,CAAC;CACL,CAAC,CACH,CAAA"}
@@ -0,0 +1,38 @@
1
+ import { Context, Effect } from "effect";
2
+ export type JwtPayload = {
3
+ sub: string;
4
+ col: string;
5
+ };
6
+ export type FileTokenPayload = {
7
+ sub: string;
8
+ col: string;
9
+ filecol: string;
10
+ };
11
+ declare const AuthError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
12
+ readonly _tag: "AuthError";
13
+ } & Readonly<A>;
14
+ export declare class AuthError extends AuthError_base<{
15
+ reason: string;
16
+ }> {
17
+ }
18
+ declare const AuthService_base: Context.TagClass<AuthService, "AuthService", {
19
+ hashPassword(plain: string): Effect.Effect<string>;
20
+ verifyPassword(plain: string, stored: string): Effect.Effect<boolean>;
21
+ }>;
22
+ export declare class AuthService extends AuthService_base {
23
+ }
24
+ export declare const hashPassword: (plain: string) => Effect.Effect<string, never, AuthService>;
25
+ export declare const verifyPassword: (plain: string, stored: string) => Effect.Effect<boolean, never, AuthService>;
26
+ export declare function signJwt(payload: JwtPayload, secret: string): Effect.Effect<string, AuthError, never>;
27
+ export declare function signFileToken(payload: FileTokenPayload, secret: string): Effect.Effect<string, AuthError, never>;
28
+ export declare function verifyFileToken(token: string, secret: string): Effect.Effect<{
29
+ sub: string;
30
+ col: string;
31
+ filecol: string;
32
+ }, AuthError, never>;
33
+ export declare function verifyJwt(token: string, secret: string): Effect.Effect<{
34
+ sub: string;
35
+ col: string;
36
+ }, AuthError, never>;
37
+ export {};
38
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/http/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE9C,MAAM,MAAM,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AACrD,MAAM,MAAM,gBAAgB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;;;;AAE5E,qBAAa,SAAU,SAAQ,eAA8B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;CAAG;;wBAK3D,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;0BAC5B,MAAM,UAAU,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;;AAJzE,qBAAa,WAAY,SAAQ,gBAM9B;CAAG;AAEN,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,8CAAoE,CAAA;AAE9G,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,EAAE,QAAQ,MAAM,+CACe,CAAA;AAE3E,wBAAgB,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,2CAY1D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,2CAYtE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;qBAe5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;qBActD"}
@@ -0,0 +1,66 @@
1
+ import * as jose from "jose";
2
+ import { Context, Data, Effect } from "effect";
3
+ export class AuthError extends Data.TaggedError("AuthError") {
4
+ }
5
+ export class AuthService extends Context.Tag("AuthService")() {
6
+ }
7
+ export const hashPassword = (plain) => Effect.flatMap(AuthService, (auth) => auth.hashPassword(plain));
8
+ export const verifyPassword = (plain, stored) => Effect.flatMap(AuthService, (auth) => auth.verifyPassword(plain, stored));
9
+ export function signJwt(payload, secret) {
10
+ return Effect.tryPromise({
11
+ try: async () => {
12
+ const encoder = new TextEncoder();
13
+ return await new jose.SignJWT({ sub: payload.sub, col: payload.col })
14
+ .setProtectedHeader({ alg: "HS256" })
15
+ .setIssuedAt()
16
+ .setExpirationTime("72h")
17
+ .sign(encoder.encode(secret));
18
+ },
19
+ catch: (e) => new AuthError({ reason: String(e) })
20
+ });
21
+ }
22
+ export function signFileToken(payload, secret) {
23
+ return Effect.tryPromise({
24
+ try: async () => {
25
+ const encoder = new TextEncoder();
26
+ return await new jose.SignJWT({ sub: payload.sub, col: payload.col, filecol: payload.filecol })
27
+ .setProtectedHeader({ alg: "HS256" })
28
+ .setIssuedAt()
29
+ .setExpirationTime("5m")
30
+ .sign(encoder.encode(secret));
31
+ },
32
+ catch: (e) => new AuthError({ reason: String(e) })
33
+ });
34
+ }
35
+ export function verifyFileToken(token, secret) {
36
+ return Effect.tryPromise({
37
+ try: async () => {
38
+ const encoder = new TextEncoder();
39
+ const { payload } = await jose.jwtVerify(token, encoder.encode(secret));
40
+ const sub = payload.sub;
41
+ const col = payload["col"];
42
+ const filecol = payload["filecol"];
43
+ if (typeof sub !== "string" || typeof col !== "string" || typeof filecol !== "string") {
44
+ throw new Error("Missing sub, col, or filecol in file token");
45
+ }
46
+ return { sub, col, filecol };
47
+ },
48
+ catch: (e) => new AuthError({ reason: String(e) })
49
+ });
50
+ }
51
+ export function verifyJwt(token, secret) {
52
+ return Effect.tryPromise({
53
+ try: async () => {
54
+ const encoder = new TextEncoder();
55
+ const { payload } = await jose.jwtVerify(token, encoder.encode(secret));
56
+ const sub = payload.sub;
57
+ const col = payload["col"];
58
+ if (typeof sub !== "string" || typeof col !== "string") {
59
+ throw new Error("Missing sub or col in JWT");
60
+ }
61
+ return { sub, col };
62
+ },
63
+ catch: (e) => new AuthError({ reason: String(e) })
64
+ });
65
+ }
66
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/http/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { HttpServerResponse } from "@effect/platform";
2
+ import { Effect } from "effect";
3
+ import type { CollectionError } from "../collection-service/errors.js";
4
+ import type { FileStorageError } from "../storage/storage.js";
5
+ /**
6
+ * Effect error handler that maps `CollectionError` and `FileStorageError` variants
7
+ * to HTTP response objects with appropriate status codes.
8
+ *
9
+ * | Error | HTTP Status |
10
+ * |---|---|
11
+ * | `NotFoundError` | 404 |
12
+ * | `ForbiddenError` | 403 |
13
+ * | `ValidationError` | 422 |
14
+ * | `ReadOnlyError` | 405 |
15
+ * | `FileStorageError` | 502 |
16
+ * | `SqlError` | 500 |
17
+ *
18
+ * Use this as a final `.pipe()` call in route handlers to convert domain errors
19
+ * to HTTP responses.
20
+ *
21
+ * @example
22
+ * import { catchCollectionErrors } from "@gettersethya/mira"
23
+ *
24
+ * const handler = Effect.gen(function* () { ... }).pipe(
25
+ * catchCollectionErrors
26
+ * )
27
+ *
28
+ * @see CollectionError — the union type this handler catches
29
+ */
30
+ export declare const catchCollectionErrors: <A, R>(eff: Effect.Effect<A, CollectionError | FileStorageError, R>) => Effect.Effect<A, HttpServerResponse.HttpServerResponse, R>;
31
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/http/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAA;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,qBAAqB,GAAI,CAAC,EAAE,CAAC,EACxC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,eAAe,GAAG,gBAAgB,EAAE,CAAC,CAAC,KAC3D,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,kBAAkB,CAAC,kBAAkB,EAAE,CAAC,CAmCxD,CAAA"}
@@ -0,0 +1,36 @@
1
+ import { HttpServerResponse } from "@effect/platform";
2
+ import { Effect } from "effect";
3
+ /**
4
+ * Effect error handler that maps `CollectionError` and `FileStorageError` variants
5
+ * to HTTP response objects with appropriate status codes.
6
+ *
7
+ * | Error | HTTP Status |
8
+ * |---|---|
9
+ * | `NotFoundError` | 404 |
10
+ * | `ForbiddenError` | 403 |
11
+ * | `ValidationError` | 422 |
12
+ * | `ReadOnlyError` | 405 |
13
+ * | `FileStorageError` | 502 |
14
+ * | `SqlError` | 500 |
15
+ *
16
+ * Use this as a final `.pipe()` call in route handlers to convert domain errors
17
+ * to HTTP responses.
18
+ *
19
+ * @example
20
+ * import { catchCollectionErrors } from "@gettersethya/mira"
21
+ *
22
+ * const handler = Effect.gen(function* () { ... }).pipe(
23
+ * catchCollectionErrors
24
+ * )
25
+ *
26
+ * @see CollectionError — the union type this handler catches
27
+ */
28
+ export const catchCollectionErrors = (eff) => Effect.catchTags(eff, {
29
+ NotFoundError: (e) => Effect.fail(HttpServerResponse.unsafeJson({ error: "not_found", message: `Record not found in "${e.collection}"` }, { status: 404 })),
30
+ ForbiddenError: () => Effect.fail(HttpServerResponse.unsafeJson({ error: "forbidden" }, { status: 403 })),
31
+ ValidationError: (e) => Effect.fail(HttpServerResponse.unsafeJson({ error: "validation_failed", issues: e.issues }, { status: 422 })),
32
+ ReadOnlyError: () => Effect.fail(HttpServerResponse.unsafeJson({ error: "read_only" }, { status: 405 })),
33
+ FileStorageError: (e) => Effect.fail(HttpServerResponse.unsafeJson({ error: "storage_error", reason: e.reason }, { status: 502 })),
34
+ SqlError: () => Effect.fail(HttpServerResponse.unsafeJson({ error: "internal_error" }, { status: 500 })),
35
+ });
36
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/http/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAI/B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,GAA4D,EACA,EAAE,CAC9D,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE;IACpB,aAAa,EAAE,CAAC,CAAC,EAAE,EAAE,CACnB,MAAM,CAAC,IAAI,CACT,kBAAkB,CAAC,UAAU,CAC3B,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,wBAAwB,CAAC,CAAC,UAAU,GAAG,EAAE,EACxE,EAAE,MAAM,EAAE,GAAY,EAAE,CACzB,CACF;IACH,cAAc,EAAE,GAAG,EAAE,CACnB,MAAM,CAAC,IAAI,CACT,kBAAkB,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAY,EAAE,CAAC,CAChF;IACH,eAAe,EAAE,CAAC,CAAC,EAAE,EAAE,CACrB,MAAM,CAAC,IAAI,CACT,kBAAkB,CAAC,UAAU,CAC3B,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAChD,EAAE,MAAM,EAAE,GAAY,EAAE,CACzB,CACF;IACH,aAAa,EAAE,GAAG,EAAE,CAClB,MAAM,CAAC,IAAI,CACT,kBAAkB,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,GAAY,EAAE,CAAC,CAChF;IACH,gBAAgB,EAAE,CAAC,CAAC,EAAE,EAAE,CACtB,MAAM,CAAC,IAAI,CACT,kBAAkB,CAAC,UAAU,CAC3B,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAC5C,EAAE,MAAM,EAAE,GAAY,EAAE,CACzB,CACF;IACH,QAAQ,EAAE,GAAG,EAAE,CACb,MAAM,CAAC,IAAI,CACT,kBAAkB,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,EAAE,GAAY,EAAE,CAAC,CACrF;CACJ,CAAC,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "@effect/platform";
2
+ import { Effect } from "effect";
3
+ import type { AnyCollectionDef } from "@gettersethya/mira-client";
4
+ import { Repository } from "../repository/repository.js";
5
+ import { FileStorage } from "../storage/storage.js";
6
+ import { ThumbnailService } from "../thumbnail/types.js";
7
+ import { AppConfig } from "../config/index.js";
8
+ export type FileServeServices = Repository | FileStorage | ThumbnailService | AppConfig;
9
+ export declare function makeFileServeRoute(collections: ReadonlyArray<AnyCollectionDef>): Effect.Effect<HttpServerResponse.HttpServerResponse, never, FileServeServices | HttpServerRequest.HttpServerRequest | HttpRouter.RouteContext>;
10
+ //# sourceMappingURL=file-serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-serve.d.ts","sourceRoot":"","sources":["../../../src/http/file-serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACpF,OAAO,EAAS,MAAM,EAAoB,MAAM,QAAQ,CAAA;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAGjE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAkB,MAAM,sBAAsB,CAAA;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAI7C,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,GAAG,SAAS,CAAA;AAmCvF,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAC3C,MAAM,CAAC,MAAM,CACd,kBAAkB,CAAC,kBAAkB,EACrC,KAAK,EACL,iBAAiB,GAAG,iBAAiB,CAAC,iBAAiB,GAAG,UAAU,CAAC,YAAY,CAClF,CA+BA"}
@@ -0,0 +1,159 @@
1
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "@effect/platform";
2
+ import { Chunk, Effect, Redacted, Stream } from "effect";
3
+ import { andWhere, idClause, resolveCtxPlaceholders } from "../collection-service/where.js";
4
+ import { Repository } from "../repository/repository.js";
5
+ import { FileStorage } from "../storage/storage.js";
6
+ import { ThumbnailService, parseThumbSpec } from "../thumbnail/types.js";
7
+ import { AppConfig } from "../config/index.js";
8
+ import { enforcerForAction } from "../rule/enforcer.js";
9
+ import { verifyFileToken } from "./auth.js";
10
+ const MIME_MAP = {
11
+ jpg: "image/jpeg",
12
+ jpeg: "image/jpeg",
13
+ png: "image/png",
14
+ gif: "image/gif",
15
+ webp: "image/webp",
16
+ avif: "image/avif",
17
+ svg: "image/svg+xml",
18
+ pdf: "application/pdf",
19
+ txt: "text/plain",
20
+ html: "text/html",
21
+ css: "text/css",
22
+ js: "application/javascript",
23
+ json: "application/json",
24
+ mp4: "video/mp4",
25
+ mp3: "audio/mpeg",
26
+ wav: "audio/wav",
27
+ bin: "application/octet-stream"
28
+ };
29
+ function mimeFromKey(key) {
30
+ const ext = key.split(".").pop()?.toLowerCase() ?? "";
31
+ return MIME_MAP[ext] ?? "application/octet-stream";
32
+ }
33
+ function getQueryParam(req, name) {
34
+ const queryStr = req.url.includes("?") ? req.url.split("?")[1] : "";
35
+ return new URLSearchParams(queryStr).get(name) ?? undefined;
36
+ }
37
+ export function makeFileServeRoute(collections) {
38
+ const collectionMap = new Map(collections.map((c) => [c.name, c]));
39
+ return Effect.flatMap(HttpRouter.RouteContext, (routeCtx) => {
40
+ const colName = routeCtx.params["collection"];
41
+ const recordId = routeCtx.params["id"];
42
+ const filename = routeCtx.params["filename"];
43
+ if (colName === undefined || recordId === undefined || filename === undefined) {
44
+ return Effect.succeed(HttpServerResponse.unsafeJson({ error: "not_found" }, { status: 404 }));
45
+ }
46
+ const collection = collectionMap.get(colName);
47
+ if (collection === undefined) {
48
+ return Effect.succeed(HttpServerResponse.unsafeJson({ error: "not_found", message: `Unknown collection "${colName}"` }, { status: 404 }));
49
+ }
50
+ return Effect.flatMap(HttpServerRequest.HttpServerRequest, (req) => Effect.catchAllCause(serveFile(collection, recordId, filename, req), () => Effect.succeed(HttpServerResponse.unsafeJson({ error: "internal" }, { status: 500 }))));
51
+ });
52
+ }
53
+ function serveFile(collection, recordId, filename, req) {
54
+ return Effect.gen(function* () {
55
+ const config = yield* AppConfig;
56
+ const jwtSecret = Redacted.value(config.jwtSecret);
57
+ const repo = yield* Repository;
58
+ const fileStorage = yield* FileStorage;
59
+ const thumbSvc = yield* ThumbnailService;
60
+ // Step 2: fetch record directly (no rule enforcement yet)
61
+ const rows = yield* repo
62
+ .viewFilter(collection.name, { where: idClause(recordId) })
63
+ .pipe(Effect.orElseSucceed(() => []));
64
+ if (rows.length === 0) {
65
+ return HttpServerResponse.unsafeJson({ error: "not_found" }, { status: 404 });
66
+ }
67
+ const record = rows[0];
68
+ // Step 3: find the file field whose stored value equals `filename`
69
+ let fieldName;
70
+ let fieldProp;
71
+ for (const [k, prop] of Object.entries(collection.schema.properties)) {
72
+ if (prop["x-kind"] === "file" && record[k] === filename) {
73
+ fieldName = k;
74
+ fieldProp = prop;
75
+ break;
76
+ }
77
+ }
78
+ if (fieldName === undefined || fieldProp === undefined) {
79
+ return HttpServerResponse.unsafeJson({ error: "not_found" }, { status: 404 });
80
+ }
81
+ // Step 4: protected field — verify token and enforce view rule
82
+ if (fieldProp["x-protected"] === true) {
83
+ const tokenParam = getQueryParam(req, "token");
84
+ if (tokenParam === undefined) {
85
+ return HttpServerResponse.unsafeJson({ error: "forbidden" }, { status: 403 });
86
+ }
87
+ const tokenPayload = yield* verifyFileToken(tokenParam, jwtSecret).pipe(Effect.orElseSucceed(() => null));
88
+ if (tokenPayload === null || tokenPayload.filecol !== collection.name) {
89
+ return HttpServerResponse.unsafeJson({ error: "forbidden" }, { status: 403 });
90
+ }
91
+ // Reconstruct auth context from file token sub + col
92
+ const authRows = yield* repo
93
+ .viewFilter(tokenPayload.col, { where: idClause(tokenPayload.sub) })
94
+ .pipe(Effect.orElseSucceed(() => []));
95
+ if (authRows.length === 0) {
96
+ return HttpServerResponse.unsafeJson({ error: "forbidden" }, { status: 403 });
97
+ }
98
+ const ctx = {
99
+ auth: { collection: tokenPayload.col, record: authRows[0] },
100
+ headers: {},
101
+ query: {}
102
+ };
103
+ // Enforce the collection's view rule against the specific record
104
+ const ruleResult = enforcerForAction(collection.schema, "view");
105
+ if (ruleResult === null) {
106
+ return HttpServerResponse.unsafeJson({ error: "forbidden" }, { status: 403 });
107
+ }
108
+ const ruleWhere = yield* resolveCtxPlaceholders(ruleResult, ctx, collection.name, "view").pipe(Effect.catchTag("ForbiddenError", () => Effect.succeed(null)));
109
+ if (ruleWhere === null) {
110
+ return HttpServerResponse.unsafeJson({ error: "forbidden" }, { status: 403 });
111
+ }
112
+ const allowed = yield* repo
113
+ .viewFilter(collection.name, { where: andWhere(ruleWhere, idClause(recordId)) })
114
+ .pipe(Effect.orElseSucceed(() => []));
115
+ if (allowed.length === 0) {
116
+ return HttpServerResponse.unsafeJson({ error: "forbidden" }, { status: 403 });
117
+ }
118
+ }
119
+ // Steps 5-8: derive MIME, handle thumbnail with persistent cache, serve bytes
120
+ const mimeType = mimeFromKey(filename);
121
+ const rawThumb = getQueryParam(req, "thumb");
122
+ const thumbSpec = rawThumb !== undefined ? parseThumbSpec(rawThumb) : null;
123
+ let bytes;
124
+ if (thumbSpec !== null && rawThumb !== undefined && thumbSvc.supported(mimeType)) {
125
+ const cacheKey = `_thumbs/${filename}/${rawThumb}`;
126
+ const hit = yield* fileStorage.exists(cacheKey).pipe(Effect.orElseSucceed(() => false));
127
+ if (hit) {
128
+ bytes = yield* fileStorage.read(cacheKey).pipe(Effect.orDie);
129
+ }
130
+ else {
131
+ const original = yield* fileStorage.read(filename).pipe(Effect.catchTag("FileStorageNotFound", () => Effect.succeed(null)), Effect.orDie);
132
+ if (original === null) {
133
+ return HttpServerResponse.unsafeJson({ error: "not_found" }, { status: 404 });
134
+ }
135
+ // On thumb error fall back to original rather than returning an error
136
+ const resized = yield* thumbSvc
137
+ .resize(original, mimeType, thumbSpec)
138
+ .pipe(Effect.orElse(() => Effect.succeed(original)));
139
+ // Best-effort: persist the thumbnail for future requests
140
+ yield* fileStorage
141
+ .upload(cacheKey, Stream.fromChunk(Chunk.of(resized)), mimeType)
142
+ .pipe(Effect.orElse(() => Effect.void));
143
+ bytes = resized;
144
+ }
145
+ }
146
+ else {
147
+ const result = yield* fileStorage.read(filename).pipe(Effect.catchTag("FileStorageNotFound", () => Effect.succeed(null)), Effect.orDie);
148
+ if (result === null) {
149
+ return HttpServerResponse.unsafeJson({ error: "not_found" }, { status: 404 });
150
+ }
151
+ bytes = result;
152
+ }
153
+ return HttpServerResponse.uint8Array(bytes, {
154
+ status: 200,
155
+ headers: { "content-type": mimeType }
156
+ });
157
+ }).pipe(Effect.orDie);
158
+ }
159
+ //# sourceMappingURL=file-serve.js.map