@gettersethya/mira 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (371) hide show
  1. package/README.md +174 -0
  2. package/dist/src/app/app.d.ts +45 -26
  3. package/dist/src/app/app.d.ts.map +1 -1
  4. package/dist/src/app/app.js +138 -41
  5. package/dist/src/app/app.js.map +1 -1
  6. package/dist/src/app/builder.d.ts +56 -15
  7. package/dist/src/app/builder.d.ts.map +1 -1
  8. package/dist/src/app/builder.js +56 -4
  9. package/dist/src/app/builder.js.map +1 -1
  10. package/dist/src/app/define-rule.d.ts +70 -0
  11. package/dist/src/app/define-rule.d.ts.map +1 -0
  12. package/dist/src/app/define-rule.js +42 -0
  13. package/dist/src/app/define-rule.js.map +1 -0
  14. package/dist/src/app/index.d.ts +8 -3
  15. package/dist/src/app/index.d.ts.map +1 -1
  16. package/dist/src/app/index.js +3 -0
  17. package/dist/src/app/index.js.map +1 -1
  18. package/dist/src/app/plugin.d.ts +83 -0
  19. package/dist/src/app/plugin.d.ts.map +1 -0
  20. package/dist/src/app/plugin.js +20 -0
  21. package/dist/src/app/plugin.js.map +1 -0
  22. package/dist/src/app/types.d.ts +4 -4
  23. package/dist/src/app/types.d.ts.map +1 -1
  24. package/dist/src/cache/cached-collection.d.ts +7 -5
  25. package/dist/src/cache/cached-collection.d.ts.map +1 -1
  26. package/dist/src/cache/cached-collection.js +8 -5
  27. package/dist/src/cache/cached-collection.js.map +1 -1
  28. package/dist/src/cache/collection-cache.d.ts +1 -1
  29. package/dist/src/cache/collection-cache.d.ts.map +1 -1
  30. package/dist/src/cache/collection-cache.js +1 -1
  31. package/dist/src/cache/collection-cache.js.map +1 -1
  32. package/dist/src/cache/ctx-key.d.ts +15 -0
  33. package/dist/src/cache/ctx-key.d.ts.map +1 -0
  34. package/dist/src/cache/ctx-key.js +37 -0
  35. package/dist/src/cache/ctx-key.js.map +1 -0
  36. package/dist/src/cache/index.d.ts +2 -1
  37. package/dist/src/cache/index.d.ts.map +1 -1
  38. package/dist/src/cache/index.js +2 -1
  39. package/dist/src/cache/index.js.map +1 -1
  40. package/dist/src/collection-service/collection-service.d.ts +7 -5
  41. package/dist/src/collection-service/collection-service.d.ts.map +1 -1
  42. package/dist/src/collection-service/collection-service.js +201 -51
  43. package/dist/src/collection-service/collection-service.js.map +1 -1
  44. package/dist/src/collection-service/context.d.ts +2 -0
  45. package/dist/src/collection-service/context.d.ts.map +1 -1
  46. package/dist/src/collection-service/decode.d.ts +4 -2
  47. package/dist/src/collection-service/decode.d.ts.map +1 -1
  48. package/dist/src/collection-service/decode.js +32 -16
  49. package/dist/src/collection-service/decode.js.map +1 -1
  50. package/dist/src/collection-service/errors.d.ts +1 -1
  51. package/dist/src/collection-service/errors.d.ts.map +1 -1
  52. package/dist/src/collection-service/errors.js +1 -1
  53. package/dist/src/collection-service/errors.js.map +1 -1
  54. package/dist/src/collection-service/index.d.ts +1 -1
  55. package/dist/src/collection-service/index.d.ts.map +1 -1
  56. package/dist/src/collection-service/index.js.map +1 -1
  57. package/dist/src/collection-service/input-schema.d.ts +5 -4
  58. package/dist/src/collection-service/input-schema.d.ts.map +1 -1
  59. package/dist/src/collection-service/input-schema.js +31 -18
  60. package/dist/src/collection-service/input-schema.js.map +1 -1
  61. package/dist/src/collection-service/where.d.ts +14 -2
  62. package/dist/src/collection-service/where.d.ts.map +1 -1
  63. package/dist/src/collection-service/where.js +35 -1
  64. package/dist/src/collection-service/where.js.map +1 -1
  65. package/dist/src/config/config.d.ts +4 -4
  66. package/dist/src/config/config.d.ts.map +1 -1
  67. package/dist/src/config/config.js +34 -5
  68. package/dist/src/config/config.js.map +1 -1
  69. package/dist/src/config/types.d.ts +9 -0
  70. package/dist/src/config/types.d.ts.map +1 -1
  71. package/dist/src/cron/cron-service.d.ts +14 -0
  72. package/dist/src/cron/cron-service.d.ts.map +1 -0
  73. package/dist/src/cron/cron-service.js +135 -0
  74. package/dist/src/cron/cron-service.js.map +1 -0
  75. package/dist/src/cron/index.d.ts +4 -0
  76. package/dist/src/cron/index.d.ts.map +1 -0
  77. package/dist/src/cron/index.js +3 -0
  78. package/dist/src/cron/index.js.map +1 -0
  79. package/dist/src/cron/persistence.d.ts +22 -0
  80. package/dist/src/cron/persistence.d.ts.map +1 -0
  81. package/dist/src/cron/persistence.js +48 -0
  82. package/dist/src/cron/persistence.js.map +1 -0
  83. package/dist/src/cron/types.d.ts +51 -0
  84. package/dist/src/cron/types.d.ts.map +1 -0
  85. package/dist/src/cron/types.js +4 -0
  86. package/dist/src/cron/types.js.map +1 -0
  87. package/dist/src/crypto/crypto.d.ts +3 -2
  88. package/dist/src/crypto/crypto.d.ts.map +1 -1
  89. package/dist/src/crypto/crypto.js +1 -1
  90. package/dist/src/crypto/crypto.js.map +1 -1
  91. package/dist/src/crypto/node.d.ts.map +1 -1
  92. package/dist/src/crypto/node.js.map +1 -1
  93. package/dist/src/databases/sqlite.d.ts.map +1 -1
  94. package/dist/src/databases/sqlite.js +2 -2
  95. package/dist/src/databases/sqlite.js.map +1 -1
  96. package/dist/src/dialect/dialect-sqlite.d.ts +9 -0
  97. package/dist/src/dialect/dialect-sqlite.d.ts.map +1 -0
  98. package/dist/src/dialect/dialect-sqlite.js +109 -0
  99. package/dist/src/dialect/dialect-sqlite.js.map +1 -0
  100. package/dist/src/dialect/dialect.d.ts +26 -0
  101. package/dist/src/dialect/dialect.d.ts.map +1 -0
  102. package/dist/src/dialect/dialect.js +5 -0
  103. package/dist/src/dialect/dialect.js.map +1 -0
  104. package/dist/src/hooks/hook-collection.d.ts +5 -0
  105. package/dist/src/hooks/hook-collection.d.ts.map +1 -0
  106. package/dist/src/hooks/hook-collection.js +77 -0
  107. package/dist/src/hooks/hook-collection.js.map +1 -0
  108. package/dist/src/hooks/hook-service.d.ts +38 -0
  109. package/dist/src/hooks/hook-service.d.ts.map +1 -0
  110. package/dist/src/hooks/hook-service.js +150 -0
  111. package/dist/src/hooks/hook-service.js.map +1 -0
  112. package/dist/src/hooks/index.d.ts +4 -0
  113. package/dist/src/hooks/index.d.ts.map +1 -0
  114. package/dist/src/hooks/index.js +3 -0
  115. package/dist/src/hooks/index.js.map +1 -0
  116. package/dist/src/hooks/types.d.ts +74 -0
  117. package/dist/src/hooks/types.d.ts.map +1 -0
  118. package/dist/src/hooks/types.js +2 -0
  119. package/dist/src/hooks/types.js.map +1 -0
  120. package/dist/src/http/auth-node.d.ts.map +1 -1
  121. package/dist/src/http/auth-node.js +1 -1
  122. package/dist/src/http/auth-node.js.map +1 -1
  123. package/dist/src/http/auth.d.ts +3 -1
  124. package/dist/src/http/auth.d.ts.map +1 -1
  125. package/dist/src/http/auth.js +12 -2
  126. package/dist/src/http/auth.js.map +1 -1
  127. package/dist/src/http/cors.d.ts +94 -0
  128. package/dist/src/http/cors.d.ts.map +1 -0
  129. package/dist/src/http/cors.js +57 -0
  130. package/dist/src/http/cors.js.map +1 -0
  131. package/dist/src/http/errors.d.ts +1 -1
  132. package/dist/src/http/errors.d.ts.map +1 -1
  133. package/dist/src/http/errors.js +7 -7
  134. package/dist/src/http/errors.js.map +1 -1
  135. package/dist/src/http/file-serve.d.ts +4 -4
  136. package/dist/src/http/file-serve.d.ts.map +1 -1
  137. package/dist/src/http/file-serve.js +39 -34
  138. package/dist/src/http/file-serve.js.map +1 -1
  139. package/dist/src/http/file-token.d.ts +4 -3
  140. package/dist/src/http/file-token.d.ts.map +1 -1
  141. package/dist/src/http/file-token.js +19 -17
  142. package/dist/src/http/file-token.js.map +1 -1
  143. package/dist/src/http/files.d.ts +3 -3
  144. package/dist/src/http/files.d.ts.map +1 -1
  145. package/dist/src/http/files.js +5 -5
  146. package/dist/src/http/files.js.map +1 -1
  147. package/dist/src/http/index.d.ts +8 -5
  148. package/dist/src/http/index.d.ts.map +1 -1
  149. package/dist/src/http/index.js +5 -3
  150. package/dist/src/http/index.js.map +1 -1
  151. package/dist/src/http/ip-middleware.d.ts +3 -2
  152. package/dist/src/http/ip-middleware.d.ts.map +1 -1
  153. package/dist/src/http/ip-middleware.js +7 -4
  154. package/dist/src/http/ip-middleware.js.map +1 -1
  155. package/dist/src/http/params.d.ts +3 -3
  156. package/dist/src/http/params.d.ts.map +1 -1
  157. package/dist/src/http/params.js +2 -2
  158. package/dist/src/http/params.js.map +1 -1
  159. package/dist/src/http/port-retry.d.ts +18 -0
  160. package/dist/src/http/port-retry.d.ts.map +1 -0
  161. package/dist/src/http/port-retry.js +43 -0
  162. package/dist/src/http/port-retry.js.map +1 -0
  163. package/dist/src/http/router.d.ts +10 -7
  164. package/dist/src/http/router.d.ts.map +1 -1
  165. package/dist/src/http/router.js +184 -63
  166. package/dist/src/http/router.js.map +1 -1
  167. package/dist/src/http/schema.d.ts +5 -0
  168. package/dist/src/http/schema.d.ts.map +1 -0
  169. package/dist/src/http/schema.js +18 -0
  170. package/dist/src/http/schema.js.map +1 -0
  171. package/dist/src/http/server-factory-node.d.ts.map +1 -1
  172. package/dist/src/http/server-factory-node.js +1 -1
  173. package/dist/src/http/server-factory-node.js.map +1 -1
  174. package/dist/src/http/server-factory.d.ts +3 -3
  175. package/dist/src/http/server-factory.d.ts.map +1 -1
  176. package/dist/src/http/server-factory.js +1 -1
  177. package/dist/src/http/server-factory.js.map +1 -1
  178. package/dist/src/http/telemetry-routes.d.ts +5 -0
  179. package/dist/src/http/telemetry-routes.d.ts.map +1 -0
  180. package/dist/src/http/telemetry-routes.js +220 -0
  181. package/dist/src/http/telemetry-routes.js.map +1 -0
  182. package/dist/src/index.d.ts +18 -1
  183. package/dist/src/index.d.ts.map +1 -1
  184. package/dist/src/index.js +14 -1
  185. package/dist/src/index.js.map +1 -1
  186. package/dist/src/migrator/dialect-sqlite.d.ts.map +1 -1
  187. package/dist/src/migrator/dialect-sqlite.js +7 -1
  188. package/dist/src/migrator/dialect-sqlite.js.map +1 -1
  189. package/dist/src/migrator/index.d.ts +4 -4
  190. package/dist/src/migrator/index.d.ts.map +1 -1
  191. package/dist/src/migrator/index.js +2 -2
  192. package/dist/src/migrator/index.js.map +1 -1
  193. package/dist/src/migrator/migrator.d.ts +8 -8
  194. package/dist/src/migrator/migrator.d.ts.map +1 -1
  195. package/dist/src/migrator/migrator.js +16 -13
  196. package/dist/src/migrator/migrator.js.map +1 -1
  197. package/dist/src/migrator/schema-diff.d.ts +2 -2
  198. package/dist/src/migrator/schema-diff.d.ts.map +1 -1
  199. package/dist/src/migrator/schema-diff.js.map +1 -1
  200. package/dist/src/migrator/types.d.ts +5 -5
  201. package/dist/src/migrator/types.d.ts.map +1 -1
  202. package/dist/src/migrator/types.js +6 -7
  203. package/dist/src/migrator/types.js.map +1 -1
  204. package/dist/src/platforms/node.d.ts +1 -1
  205. package/dist/src/platforms/node.d.ts.map +1 -1
  206. package/dist/src/platforms/node.js +1 -1
  207. package/dist/src/platforms/node.js.map +1 -1
  208. package/dist/src/repository/id.d.ts +1 -1
  209. package/dist/src/repository/id.d.ts.map +1 -1
  210. package/dist/src/repository/id.js +15 -3
  211. package/dist/src/repository/id.js.map +1 -1
  212. package/dist/src/repository/repository.d.ts +3 -2
  213. package/dist/src/repository/repository.d.ts.map +1 -1
  214. package/dist/src/repository/repository.js +11 -7
  215. package/dist/src/repository/repository.js.map +1 -1
  216. package/dist/src/rule/compiler.d.ts.map +1 -1
  217. package/dist/src/rule/compiler.js +2 -0
  218. package/dist/src/rule/compiler.js.map +1 -1
  219. package/dist/src/rule/enforcer.d.ts.map +1 -1
  220. package/dist/src/rule/enforcer.js.map +1 -1
  221. package/dist/src/storage/index.d.ts +1 -1
  222. package/dist/src/storage/index.d.ts.map +1 -1
  223. package/dist/src/storage/index.js +1 -1
  224. package/dist/src/storage/index.js.map +1 -1
  225. package/dist/src/storage/local-preset.d.ts.map +1 -1
  226. package/dist/src/storage/local-preset.js.map +1 -1
  227. package/dist/src/storage/storage.d.ts +2 -2
  228. package/dist/src/storage/storage.d.ts.map +1 -1
  229. package/dist/src/storage/storage.js +8 -26
  230. package/dist/src/storage/storage.js.map +1 -1
  231. package/dist/src/telemetry/collections.d.ts +4 -0
  232. package/dist/src/telemetry/collections.d.ts.map +1 -0
  233. package/dist/src/telemetry/collections.js +19 -0
  234. package/dist/src/telemetry/collections.js.map +1 -0
  235. package/dist/src/telemetry/index.d.ts +17 -3
  236. package/dist/src/telemetry/index.d.ts.map +1 -1
  237. package/dist/src/telemetry/index.js +15 -5
  238. package/dist/src/telemetry/index.js.map +1 -1
  239. package/dist/src/telemetry/logger.d.ts +29 -2
  240. package/dist/src/telemetry/logger.d.ts.map +1 -1
  241. package/dist/src/telemetry/logger.js +52 -15
  242. package/dist/src/telemetry/logger.js.map +1 -1
  243. package/dist/src/telemetry/sqlite-logger.d.ts +36 -0
  244. package/dist/src/telemetry/sqlite-logger.d.ts.map +1 -0
  245. package/dist/src/telemetry/sqlite-logger.js +193 -0
  246. package/dist/src/telemetry/sqlite-logger.js.map +1 -0
  247. package/dist/src/telemetry/telemetry-sql-client.d.ts +11 -0
  248. package/dist/src/telemetry/telemetry-sql-client.d.ts.map +1 -0
  249. package/dist/src/telemetry/telemetry-sql-client.js +8 -0
  250. package/dist/src/telemetry/telemetry-sql-client.js.map +1 -0
  251. package/dist/src/telemetry/tracer.d.ts +16 -1
  252. package/dist/src/telemetry/tracer.d.ts.map +1 -1
  253. package/dist/src/telemetry/tracer.js +87 -70
  254. package/dist/src/telemetry/tracer.js.map +1 -1
  255. package/dist/src/telemetry/types.d.ts +11 -0
  256. package/dist/src/telemetry/types.d.ts.map +1 -0
  257. package/dist/src/telemetry/types.js +2 -0
  258. package/dist/src/telemetry/types.js.map +1 -0
  259. package/dist/src/thumbnail/index.d.ts +2 -2
  260. package/dist/src/thumbnail/index.d.ts.map +1 -1
  261. package/dist/src/thumbnail/index.js +1 -1
  262. package/dist/src/thumbnail/index.js.map +1 -1
  263. package/dist/src/thumbnail/photon.d.ts.map +1 -1
  264. package/dist/src/thumbnail/photon.js +1 -1
  265. package/dist/src/thumbnail/photon.js.map +1 -1
  266. package/dist/src/thumbnail/types.d.ts +1 -1
  267. package/dist/src/thumbnail/types.d.ts.map +1 -1
  268. package/dist/src/thumbnail/types.js +2 -2
  269. package/dist/src/thumbnail/types.js.map +1 -1
  270. package/dist/tests/app/app.test.js +101 -26
  271. package/dist/tests/app/app.test.js.map +1 -1
  272. package/dist/tests/app/builder.test.js +77 -9
  273. package/dist/tests/app/builder.test.js.map +1 -1
  274. package/dist/tests/app/define-rule.test.d.ts +2 -0
  275. package/dist/tests/app/define-rule.test.d.ts.map +1 -0
  276. package/dist/tests/app/define-rule.test.js +147 -0
  277. package/dist/tests/app/define-rule.test.js.map +1 -0
  278. package/dist/tests/cache/cached-collection.test.js +200 -40
  279. package/dist/tests/cache/cached-collection.test.js.map +1 -1
  280. package/dist/tests/cache/collection-cache.test.js +1 -1
  281. package/dist/tests/cache/collection-cache.test.js.map +1 -1
  282. package/dist/tests/collection-service/auth-credentials.test.d.ts +2 -0
  283. package/dist/tests/collection-service/auth-credentials.test.d.ts.map +1 -0
  284. package/dist/tests/collection-service/auth-credentials.test.js +172 -0
  285. package/dist/tests/collection-service/auth-credentials.test.js.map +1 -0
  286. package/dist/tests/collection-service/collection-service.test.js +374 -174
  287. package/dist/tests/collection-service/collection-service.test.js.map +1 -1
  288. package/dist/tests/collection-service/decode.test.d.ts +2 -0
  289. package/dist/tests/collection-service/decode.test.d.ts.map +1 -0
  290. package/dist/tests/collection-service/decode.test.js +37 -0
  291. package/dist/tests/collection-service/decode.test.js.map +1 -0
  292. package/dist/tests/collection-service/input-schema.test.js +162 -95
  293. package/dist/tests/collection-service/input-schema.test.js.map +1 -1
  294. package/dist/tests/config/config.test.js +15 -4
  295. package/dist/tests/config/config.test.js.map +1 -1
  296. package/dist/tests/cron/cron-integration.test.d.ts +2 -0
  297. package/dist/tests/cron/cron-integration.test.d.ts.map +1 -0
  298. package/dist/tests/cron/cron-integration.test.js +109 -0
  299. package/dist/tests/cron/cron-integration.test.js.map +1 -0
  300. package/dist/tests/cron/cron-persistence.test.d.ts +2 -0
  301. package/dist/tests/cron/cron-persistence.test.d.ts.map +1 -0
  302. package/dist/tests/cron/cron-persistence.test.js +123 -0
  303. package/dist/tests/cron/cron-persistence.test.js.map +1 -0
  304. package/dist/tests/cron/cron-service.test.d.ts +2 -0
  305. package/dist/tests/cron/cron-service.test.d.ts.map +1 -0
  306. package/dist/tests/cron/cron-service.test.js +124 -0
  307. package/dist/tests/cron/cron-service.test.js.map +1 -0
  308. package/dist/tests/crypto/crypto.test.js +2 -2
  309. package/dist/tests/crypto/crypto.test.js.map +1 -1
  310. package/dist/tests/hooks/hook-collection.test.d.ts +2 -0
  311. package/dist/tests/hooks/hook-collection.test.d.ts.map +1 -0
  312. package/dist/tests/hooks/hook-collection.test.js +110 -0
  313. package/dist/tests/hooks/hook-collection.test.js.map +1 -0
  314. package/dist/tests/hooks/hook-service.test.d.ts +2 -0
  315. package/dist/tests/hooks/hook-service.test.d.ts.map +1 -0
  316. package/dist/tests/hooks/hook-service.test.js +220 -0
  317. package/dist/tests/hooks/hook-service.test.js.map +1 -0
  318. package/dist/tests/http/cors.test.d.ts +2 -0
  319. package/dist/tests/http/cors.test.d.ts.map +1 -0
  320. package/dist/tests/http/cors.test.js +211 -0
  321. package/dist/tests/http/cors.test.js.map +1 -0
  322. package/dist/tests/http/file-serve.test.js +63 -54
  323. package/dist/tests/http/file-serve.test.js.map +1 -1
  324. package/dist/tests/http/file-token.test.js +69 -51
  325. package/dist/tests/http/file-token.test.js.map +1 -1
  326. package/dist/tests/http/ip-middleware.test.js +13 -13
  327. package/dist/tests/http/ip-middleware.test.js.map +1 -1
  328. package/dist/tests/http/port-retry.test.d.ts +2 -0
  329. package/dist/tests/http/port-retry.test.d.ts.map +1 -0
  330. package/dist/tests/http/port-retry.test.js +52 -0
  331. package/dist/tests/http/port-retry.test.js.map +1 -0
  332. package/dist/tests/http/router.test.js +260 -85
  333. package/dist/tests/http/router.test.js.map +1 -1
  334. package/dist/tests/migrator/dialect-sqlite.test.js +1 -1
  335. package/dist/tests/migrator/dialect-sqlite.test.js.map +1 -1
  336. package/dist/tests/migrator/integration.test.js +6 -6
  337. package/dist/tests/migrator/integration.test.js.map +1 -1
  338. package/dist/tests/migrator/migrator.test.js +5 -5
  339. package/dist/tests/migrator/migrator.test.js.map +1 -1
  340. package/dist/tests/migrator/schema-diff.test.js.map +1 -1
  341. package/dist/tests/repository/repository.test.js +71 -8
  342. package/dist/tests/repository/repository.test.js.map +1 -1
  343. package/dist/tests/rule/compiler.test.js +1 -1
  344. package/dist/tests/rule/compiler.test.js.map +1 -1
  345. package/dist/tests/rule/enforcer.test.js +9 -9
  346. package/dist/tests/rule/enforcer.test.js.map +1 -1
  347. package/dist/tests/storage/storage.test.js +15 -15
  348. package/dist/tests/storage/storage.test.js.map +1 -1
  349. package/dist/tests/telemetry/collections.test.d.ts +2 -0
  350. package/dist/tests/telemetry/collections.test.d.ts.map +1 -0
  351. package/dist/tests/telemetry/collections.test.js +67 -0
  352. package/dist/tests/telemetry/collections.test.js.map +1 -0
  353. package/dist/tests/telemetry/instrumentation.test.js +27 -20
  354. package/dist/tests/telemetry/instrumentation.test.js.map +1 -1
  355. package/dist/tests/telemetry/logger.test.js +38 -6
  356. package/dist/tests/telemetry/logger.test.js.map +1 -1
  357. package/dist/tests/telemetry/sqlite-logger.test.d.ts +2 -0
  358. package/dist/tests/telemetry/sqlite-logger.test.d.ts.map +1 -0
  359. package/dist/tests/telemetry/sqlite-logger.test.js +272 -0
  360. package/dist/tests/telemetry/sqlite-logger.test.js.map +1 -0
  361. package/dist/tests/telemetry/telemetry-routes.test.d.ts +2 -0
  362. package/dist/tests/telemetry/telemetry-routes.test.d.ts.map +1 -0
  363. package/dist/tests/telemetry/telemetry-routes.test.js +154 -0
  364. package/dist/tests/telemetry/telemetry-routes.test.js.map +1 -0
  365. package/dist/tests/telemetry/tracer.test.js +41 -4
  366. package/dist/tests/telemetry/tracer.test.js.map +1 -1
  367. package/dist/tests/thumbnail/photon.test.js +35 -35
  368. package/dist/tests/thumbnail/photon.test.js.map +1 -1
  369. package/dist/tests/thumbnail/thumbnail.test.js +7 -7
  370. package/dist/tests/thumbnail/thumbnail.test.js.map +1 -1
  371. package/package.json +9 -9
@@ -1,11 +1,15 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { AuthCollection, BaseCollection, Field } from "@gettersethya/mira-client";
2
2
  import { Layer } from "effect";
3
- import { MiraBuilder } from "../../src/app/builder.js";
3
+ import { describe, expect, it } from "vitest";
4
4
  import { MiraApp } from "../../src/app/app.js";
5
- import { ConsoleTelemetryLayer } from "../../src/telemetry/index.js";
6
- import { NodePlatform } from "../../src/platforms/node.js";
5
+ import { MiraBuilder } from "../../src/app/builder.js";
6
+ import { defineRule } from "../../src/app/index.js";
7
+ import { MiraPlugin } from "../../src/app/plugin.js";
7
8
  import { SqliteDatabase } from "../../src/databases/sqlite.js";
9
+ import { defaultCorsConfig } from "../../src/http/cors.js";
10
+ import { NodePlatform } from "../../src/platforms/node.js";
8
11
  import { LocalFileStorage } from "../../src/storage/index.js";
12
+ import { ConsoleTelemetryLayer } from "../../src/telemetry/index.js";
9
13
  // Use real presets as stubs — builder stores them without invoking them in unit tests
10
14
  const stubPlatform = NodePlatform;
11
15
  const stubDatabase = SqliteDatabase({ filename: ":memory:" });
@@ -55,15 +59,79 @@ describe("MiraBuilder", () => {
55
59
  describe("MiraApp.extend()", () => {
56
60
  it("returns this (chainable)", () => {
57
61
  const app = fullBuilder().build();
58
- const result = app.extend(Layer.empty);
62
+ const result = app.extend(MiraPlugin.fromLayer(Layer.empty));
59
63
  expect(result).toBe(app);
60
64
  });
61
65
  it("accumulates extras in order", () => {
62
- const layerA = Layer.empty;
63
- const layerB = Layer.empty;
66
+ const pluginA = MiraPlugin.fromLayer(Layer.empty);
67
+ const pluginB = MiraPlugin.fromLayer(Layer.empty);
68
+ const app = fullBuilder().build();
69
+ app.extend(pluginA).extend(pluginB);
70
+ expect(app._getExtras()).toEqual([pluginA, pluginB]);
71
+ });
72
+ });
73
+ describe("MiraBuilder.rules()", () => {
74
+ const postsDef = BaseCollection.define("posts", { title: Field.text() });
75
+ it("stores rules on builder config", () => {
76
+ const rb = defineRule(postsDef, (R) => ({ list: R.public() }));
77
+ const b = fullBuilder().rules([rb]);
78
+ expect(b._getPartialConfig().rules).toEqual([rb]);
79
+ });
80
+ it("throws on duplicate collection names", () => {
81
+ const rb1 = defineRule(postsDef, (R) => ({ list: R.public() }));
82
+ const rb2 = defineRule(postsDef, (R) => ({ view: R.public() }));
83
+ expect(() => fullBuilder().rules([rb1, rb2])).toThrow('Duplicate rule binding for collection "posts"');
84
+ });
85
+ it("rules survive through build", () => {
86
+ const rb = defineRule(postsDef, (R) => ({ list: R.public() }));
87
+ const app = fullBuilder().rules([rb]).build();
88
+ expect(app._getConfig().rules).toEqual([rb]);
89
+ });
90
+ });
91
+ describe("MiraBuilder.cors()", () => {
92
+ it("stores cors on builder config", () => {
93
+ const cors = { allowedOrigins: ["http://localhost:3000"] };
94
+ const b = fullBuilder().cors(cors);
95
+ expect(b._getPartialConfig().cors).toEqual(cors);
96
+ });
97
+ it("defaults cors to defaultCorsConfig when .cors() not called", () => {
98
+ const app = fullBuilder().build();
99
+ expect(app._getConfig().cors).toBe(defaultCorsConfig);
100
+ });
101
+ it("custom cors survives through build", () => {
102
+ const cors = { allowedOrigins: ["http://localhost:3000"], credentials: true };
103
+ const app = fullBuilder().cors(cors).build();
104
+ expect(app._getConfig().cors).toEqual(cors);
105
+ });
106
+ it("throws on wildcard origin with credentials", () => {
107
+ expect(() => fullBuilder().cors({ allowedOrigins: ["*"], credentials: true })).toThrow("credentials: true requires explicit allowedOrigins");
108
+ });
109
+ it("throws on omitted origins with credentials", () => {
110
+ expect(() => fullBuilder().cors({ credentials: true })).toThrow("credentials: true requires explicit allowedOrigins");
111
+ });
112
+ it("allows explicit origin with credentials", () => {
113
+ const b = fullBuilder().cors({ allowedOrigins: ["http://localhost:3000"], credentials: true });
114
+ expect(b._getPartialConfig().cors).toBeDefined();
115
+ });
116
+ });
117
+ describe("MiraPlugin.adminCollections", () => {
118
+ const adminDef = AuthCollection.define("_admin", {});
119
+ it("auto-adds plugin admin collections to the app collection set", () => {
120
+ const plugin = MiraPlugin.define({ adminCollections: [adminDef] });
121
+ const app = fullBuilder().build().extend(plugin);
122
+ expect(app._resolveAdminCollections()).toEqual([adminDef]);
123
+ });
124
+ it("merges admin collections from multiple plugins", () => {
125
+ const otherDef = AuthCollection.define("_otheradmin", {});
126
+ const app = fullBuilder()
127
+ .build()
128
+ .extend(MiraPlugin.define({ adminCollections: [adminDef] }))
129
+ .extend(MiraPlugin.define({ adminCollections: [otherDef] }));
130
+ expect(app._resolveAdminCollections()).toEqual([adminDef, otherDef]);
131
+ });
132
+ it("there is no admin bypass when no plugin declares one", () => {
64
133
  const app = fullBuilder().build();
65
- app.extend(layerA).extend(layerB);
66
- expect(app._getExtras()).toEqual([layerA, layerB]);
134
+ expect(app._resolveAdminCollections()).toEqual([]);
67
135
  });
68
136
  });
69
137
  //# sourceMappingURL=builder.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"builder.test.js","sourceRoot":"","sources":["../../../tests/app/builder.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"builder.test.js","sourceRoot":"","sources":["../../../tests/app/builder.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=define-rule.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-rule.test.d.ts","sourceRoot":"","sources":["../../../tests/app/define-rule.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,147 @@
1
+ import { AuthCollection, BaseCollection, Field, ViewCollection } from "@gettersethya/mira-client";
2
+ import { describe, expect, it } from "vitest";
3
+ import { applyRulesToCollections, defineRule } from "../../src/app/index.js";
4
+ const Posts = BaseCollection.define("posts", {
5
+ title: Field.text({ maxLength: 200 }),
6
+ });
7
+ const Users = AuthCollection.define("users", {
8
+ name: Field.text(),
9
+ });
10
+ const ActivePosts = ViewCollection.define("active_posts", "SELECT id, seqId, title FROM posts WHERE status = 'active'", {
11
+ id: Field.text().view(),
12
+ seqId: Field.integer().view(),
13
+ title: Field.text().view(),
14
+ });
15
+ describe("defineRule", () => {
16
+ it("returns RuleBinding with correct collectionName", () => {
17
+ const rb = defineRule(Posts, (R) => ({ list: R.public() }));
18
+ expect(rb._tag).toBe("RuleBinding");
19
+ expect(rb.collectionName).toBe("posts");
20
+ expect(rb.ruleMap).toBeDefined();
21
+ });
22
+ it("ruleMap contains the rules returned by callback", () => {
23
+ const rb = defineRule(Posts, (R) => ({
24
+ list: R.public(),
25
+ view: R.field("title").eq(R.literal("hello")),
26
+ }));
27
+ expect(rb.ruleMap.list).toEqual({ op: "public" });
28
+ expect(rb.ruleMap.view).toEqual({
29
+ op: "eq",
30
+ left: { kind: "field", field: "title" },
31
+ right: { kind: "literal", value: "hello" },
32
+ });
33
+ });
34
+ it("callback receives RuleBuilder with collection fields", () => {
35
+ const rb = defineRule(Posts, (R) => {
36
+ const op = R.field("title");
37
+ expect(op).toMatchObject({ kind: "field", field: "title" });
38
+ return {};
39
+ });
40
+ expect(rb._tag).toBe("RuleBinding");
41
+ });
42
+ it("callback receives system fields on base collection", () => {
43
+ defineRule(Posts, (R) => {
44
+ expect(R.field("id")).toMatchObject({ kind: "field", field: "id" });
45
+ expect(R.field("created")).toMatchObject({ kind: "field", field: "created" });
46
+ expect(R.field("updated")).toMatchObject({ kind: "field", field: "updated" });
47
+ expect(R.field("seqId")).toMatchObject({ kind: "field", field: "seqId" });
48
+ return {};
49
+ });
50
+ });
51
+ it("callback receives auth system fields on auth collection", () => {
52
+ defineRule(Users, (R) => {
53
+ expect(R.field("email")).toMatchObject({ kind: "field", field: "email" });
54
+ expect(R.field("emailVerified")).toMatchObject({ kind: "field", field: "emailVerified" });
55
+ return {};
56
+ });
57
+ });
58
+ it("selfId() available for auth collections", () => {
59
+ defineRule(Users, (R) => {
60
+ expect(R.selfId()).toMatchObject({ kind: "authId", collection: "users" });
61
+ return {};
62
+ });
63
+ });
64
+ it("empty callback returns empty RuleMap", () => {
65
+ const rb = defineRule(Posts, () => ({}));
66
+ expect(rb.ruleMap).toEqual({});
67
+ });
68
+ it("view collection gets its declared fields", () => {
69
+ defineRule(ActivePosts, (R) => {
70
+ const idOp = R.field("id");
71
+ const seqIdOp = R.field("seqId");
72
+ const titleOp = R.field("title");
73
+ expect(idOp.kind).toBe("field");
74
+ expect(seqIdOp.kind).toBe("field");
75
+ expect(titleOp.kind).toBe("field");
76
+ return {};
77
+ });
78
+ });
79
+ it("view collection rule map keeps list and view only", () => {
80
+ const rb = defineRule(ActivePosts, (R) => ({
81
+ list: R.public(),
82
+ view: R.field("title").eq(R.literal("x")),
83
+ }));
84
+ expect(rb._tag).toBe("RuleBinding");
85
+ expect(rb.collectionName).toBe("active_posts");
86
+ expect(rb.ruleMap.list).toEqual({ op: "public" });
87
+ expect(rb.ruleMap.view).toEqual({
88
+ op: "eq",
89
+ left: { kind: "field", field: "title" },
90
+ right: { kind: "literal", value: "x" },
91
+ });
92
+ expect(rb.ruleMap.create).toBeUndefined();
93
+ expect(rb.ruleMap.update).toBeUndefined();
94
+ expect(rb.ruleMap.delete).toBeUndefined();
95
+ });
96
+ it("auth collection rule map supports all CRUD actions", () => {
97
+ const rb = defineRule(Users, (R) => ({
98
+ list: R.public(),
99
+ view: R.selfId().eq(R.literal("u1")),
100
+ create: R.public(),
101
+ update: R.selfId().eq(R.literal("u1")),
102
+ delete: R.selfId().eq(R.literal("u1")),
103
+ }));
104
+ expect(rb._tag).toBe("RuleBinding");
105
+ expect(rb.collectionName).toBe("users");
106
+ expect(rb.ruleMap.list).toEqual({ op: "public" });
107
+ expect(rb.ruleMap.view).toEqual({
108
+ op: "eq",
109
+ left: { kind: "authId", collection: "users" },
110
+ right: { kind: "literal", value: "u1" },
111
+ });
112
+ expect(rb.ruleMap.create).toEqual({ op: "public" });
113
+ expect(rb.ruleMap.update).toBeDefined();
114
+ expect(rb.ruleMap.delete).toBeDefined();
115
+ });
116
+ });
117
+ describe("applyRulesToCollections", () => {
118
+ it("merges rules into matching collection schema", () => {
119
+ const rb = defineRule(Posts, (R) => ({ list: R.public() }));
120
+ const [merged] = applyRulesToCollections([Posts], [rb]);
121
+ expect(merged.schema["x-rules"]).toBeDefined();
122
+ expect(merged.schema["x-rules"].list).toEqual({ op: "public" });
123
+ });
124
+ it("collection without RuleBinding has no x-rules", () => {
125
+ const merged = applyRulesToCollections([Posts, Users], []);
126
+ expect(merged[0].schema).not.toHaveProperty("x-rules");
127
+ expect(merged[1].schema).not.toHaveProperty("x-rules");
128
+ });
129
+ it("no rules array at all — all collections have no x-rules", () => {
130
+ const merged = applyRulesToCollections([Posts, Users], []);
131
+ for (const c of merged) {
132
+ expect(c.schema).not.toHaveProperty("x-rules");
133
+ }
134
+ });
135
+ it("RuleBinding for unknown collection name throws", () => {
136
+ const rb = defineRule(Posts, (R) => ({ list: R.public() }));
137
+ const unknown = { ...rb, collectionName: "nonexistent" };
138
+ expect(() => applyRulesToCollections([Posts], [unknown])).toThrow('RuleBinding references unknown collection "nonexistent"');
139
+ });
140
+ it("returns a new array with original collections unmodified", () => {
141
+ const rb = defineRule(Posts, (R) => ({ list: R.public() }));
142
+ const merged = applyRulesToCollections([Posts], [rb]);
143
+ expect(merged).not.toBe([Posts]);
144
+ expect(Posts.schema).not.toHaveProperty("x-rules");
145
+ });
146
+ });
147
+ //# sourceMappingURL=define-rule.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-rule.test.js","sourceRoot":"","sources":["../../../tests/app/define-rule.test.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,26 @@
1
- import { SqlClient } from "@effect/sql";
2
- import { SqliteClient } from "@effect/sql-sqlite-node";
3
- import { Effect, Either, Layer } from "effect";
4
1
  import { performance } from "node:perf_hooks";
2
+ import { SqliteClient } from "@effect/sql-sqlite-node";
5
3
  import { describe, it } from "@effect/vitest";
6
- import { expect } from "vitest";
4
+ import { AuthCollection } from "@gettersethya/mira-client";
7
5
  import { BaseCollection } from "@gettersethya/mira-client";
8
6
  import { Field } from "@gettersethya/mira-client";
9
7
  import { ViewCollection } from "@gettersethya/mira-client";
10
- import { CollectionService } from "../../src/collection-service/collection-service.js";
11
- import { NotFoundError } from "../../src/collection-service/errors.js";
8
+ import { Effect, Layer, Result } from "effect";
9
+ import { SqlClient } from "effect/sql";
10
+ import { expect } from "vitest";
11
+ import { applyRulesToCollections, defineRule } from "../../src/app/index.js";
12
12
  import { makeCachedCollectionServiceLayer } from "../../src/cache/cached-collection.js";
13
+ import { CollectionService } from "../../src/collection-service/collection-service.js";
14
+ import { NodeCryptoLayer } from "../../src/crypto/node.js";
15
+ import { Dialect } from "../../src/dialect/dialect.js";
16
+ import { sqliteDialect } from "../../src/dialect/dialect-sqlite.js";
17
+ import { NodeAuthServiceLayer } from "../../src/http/auth-node.js";
13
18
  import { RepositoryLive } from "../../src/repository/repository.js";
14
19
  import { FileStorage, FileStorageNotFound } from "../../src/storage/storage.js";
15
- import { NodeCryptoLayer } from "../../src/crypto/node.js";
16
- const Posts = BaseCollection.define("posts", {
20
+ const postsDef = BaseCollection.define("posts", {
17
21
  title: Field.text({ maxLength: 100 }),
18
- }).rules((R) => ({
22
+ });
23
+ const postsRules = defineRule(postsDef, (R) => ({
19
24
  list: R.public(),
20
25
  view: R.public(),
21
26
  create: R.public(),
@@ -25,15 +30,63 @@ const Posts = BaseCollection.define("posts", {
25
30
  // Intentionally slow view: 100 000-step recursive CTE forces SQLite to do
26
31
  // significant work on every query, making the cache speedup clearly measurable.
27
32
  // SUM(n) requires visiting all 100 000 rows — SQLite cannot short-circuit it.
28
- const SLOW_SQL = `
29
- WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
33
+ const SLOW_SQL = `
34
+ WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
30
35
  SELECT 1 AS seqId, 'perf-test-id' AS id, CAST(SUM(n) AS TEXT) AS title FROM gen`;
31
- const SlowView = ViewCollection.define("slow_posts", SLOW_SQL.trim(), {
36
+ const slowViewDef = ViewCollection.define("slow_posts", SLOW_SQL.trim(), {
32
37
  seqId: Field.integer().view(),
33
38
  id: Field.text().view(),
34
39
  title: Field.text().view(),
35
- }).rules((R) => ({ list: R.public(), view: R.public() }));
40
+ });
41
+ const slowViewRules = defineRule(slowViewDef, (R) => ({ list: R.public(), view: R.public() }));
36
42
  const noCtx = { headers: {}, query: {} };
43
+ const adminCtx = { headers: {}, query: {}, admin: true };
44
+ // Dummy collection — only its `.name` is used by Rule.authId(), never queried.
45
+ const usersDef = BaseCollection.define("users", {
46
+ name: Field.text({ maxLength: 100 }),
47
+ });
48
+ const usersRules = defineRule(usersDef, (R) => ({
49
+ list: R.public(),
50
+ view: R.public(),
51
+ create: R.public(),
52
+ update: R.public(),
53
+ delete: R.public(),
54
+ }));
55
+ const Users = applyRulesToCollections([usersDef], [usersRules])[0];
56
+ // Owner-only rule — scoped by the authenticated identity (@auth_id).
57
+ const notesDef = BaseCollection.define("notes", {
58
+ ownerId: Field.text({ maxLength: 100 }),
59
+ body: Field.text({ maxLength: 200 }),
60
+ });
61
+ const notesRules = defineRule(notesDef, (R) => ({
62
+ list: R.public(),
63
+ view: R.field("ownerId").eq(R.authId(Users)),
64
+ create: R.public(),
65
+ update: R.public(),
66
+ delete: R.public(),
67
+ }));
68
+ // Tenant-scoped rule — bound by an incoming request query param (@request_query_tenantId).
69
+ const tenantDocsDef = BaseCollection.define("tenant_docs", {
70
+ tenantId: Field.text({ maxLength: 100 }),
71
+ body: Field.text({ maxLength: 200 }),
72
+ });
73
+ const tenantDocsRules = defineRule(tenantDocsDef, (R) => ({
74
+ list: R.public(),
75
+ view: R.field("tenantId").eq(R.request("query", "tenantId")),
76
+ create: R.public(),
77
+ update: R.public(),
78
+ delete: R.public(),
79
+ }));
80
+ // Auth collection — `password` is x-hidden, stripped from non-admin responses only.
81
+ const accountsDef = AuthCollection.define("accounts", {});
82
+ const accountsRules = defineRule(accountsDef, (R) => ({
83
+ list: R.public(),
84
+ view: R.public(),
85
+ create: R.public(),
86
+ update: R.public(),
87
+ delete: R.public(),
88
+ }));
89
+ const [Posts, SlowView, Notes, TenantDocs, Accounts] = applyRulesToCollections([postsDef, slowViewDef, notesDef, tenantDocsDef, accountsDef], [postsRules, slowViewRules, notesRules, tenantDocsRules, accountsRules]);
37
90
  const FileStorageTest = Layer.succeed(FileStorage, FileStorage.of({
38
91
  upload: (key) => Effect.succeed(key),
39
92
  delete: () => Effect.void,
@@ -44,12 +97,13 @@ const FileStorageTest = Layer.succeed(FileStorage, FileStorage.of({
44
97
  }));
45
98
  const sqliteLayer = SqliteClient.layer({ filename: ":memory:" });
46
99
  const perfSqliteLayer = SqliteClient.layer({ filename: ":memory:" });
100
+ const DialectTest = Layer.succeed(Dialect, sqliteDialect);
47
101
  const cachedServiceWithDeps = makeCachedCollectionServiceLayer([Posts], {
48
102
  recordTtlMs: 60_000,
49
103
  listTtlMs: 60_000,
50
104
  maxRecords: 100,
51
105
  maxLists: 100,
52
- }).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
106
+ }).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
53
107
  // Expose CollectionService AND SqlClient so setupPostsTable can access SqlClient.SqlClient
54
108
  const testLayer = Layer.mergeAll(cachedServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
55
109
  const perfCachedServiceWithDeps = makeCachedCollectionServiceLayer([Posts, SlowView], {
@@ -57,40 +111,91 @@ const perfCachedServiceWithDeps = makeCachedCollectionServiceLayer([Posts, SlowV
57
111
  listTtlMs: 60_000,
58
112
  maxRecords: 100,
59
113
  maxLists: 100,
60
- }).pipe(Layer.provide(RepositoryLive), Layer.provide(perfSqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
114
+ }).pipe(Layer.provide(RepositoryLive), Layer.provide(perfSqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
61
115
  const perfTestLayer = Layer.mergeAll(perfCachedServiceWithDeps, perfSqliteLayer, FileStorageTest, NodeCryptoLayer);
116
+ const securitySqliteLayer = SqliteClient.layer({ filename: ":memory:" });
117
+ const securityCachedServiceWithDeps = makeCachedCollectionServiceLayer([Notes, TenantDocs, Accounts], {
118
+ recordTtlMs: 60_000,
119
+ listTtlMs: 60_000,
120
+ maxRecords: 100,
121
+ maxLists: 100,
122
+ }).pipe(Layer.provide(RepositoryLive), Layer.provide(securitySqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
123
+ const securityTestLayer = Layer.mergeAll(securityCachedServiceWithDeps, securitySqliteLayer, FileStorageTest, NodeCryptoLayer);
124
+ const setupNotesTable = Effect.gen(function* () {
125
+ const sql = yield* SqlClient.SqlClient;
126
+ yield* sql.unsafe(`
127
+ CREATE TABLE IF NOT EXISTS "notes" (
128
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
129
+ "id" TEXT NOT NULL UNIQUE,
130
+ "ownerId" TEXT NOT NULL,
131
+ "body" TEXT NOT NULL,
132
+ "created" TEXT NOT NULL,
133
+ "updated" TEXT NOT NULL
134
+ )
135
+ `);
136
+ yield* sql.unsafe(`DELETE FROM "notes"`);
137
+ });
138
+ const setupTenantDocsTable = Effect.gen(function* () {
139
+ const sql = yield* SqlClient.SqlClient;
140
+ yield* sql.unsafe(`
141
+ CREATE TABLE IF NOT EXISTS "tenant_docs" (
142
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
143
+ "id" TEXT NOT NULL UNIQUE,
144
+ "tenantId" TEXT NOT NULL,
145
+ "body" TEXT NOT NULL,
146
+ "created" TEXT NOT NULL,
147
+ "updated" TEXT NOT NULL
148
+ )
149
+ `);
150
+ yield* sql.unsafe(`DELETE FROM "tenant_docs"`);
151
+ });
152
+ const setupAccountsTable = Effect.gen(function* () {
153
+ const sql = yield* SqlClient.SqlClient;
154
+ yield* sql.unsafe(`
155
+ CREATE TABLE IF NOT EXISTS "accounts" (
156
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
157
+ "id" TEXT NOT NULL UNIQUE,
158
+ "email" TEXT NOT NULL UNIQUE,
159
+ "password" TEXT NOT NULL,
160
+ "emailVerified" INTEGER NOT NULL DEFAULT 0,
161
+ "created" TEXT NOT NULL,
162
+ "updated" TEXT NOT NULL
163
+ )
164
+ `);
165
+ yield* sql.unsafe(`DELETE FROM "accounts"`);
166
+ });
62
167
  const setupPostsTable = Effect.gen(function* () {
63
168
  const sql = yield* SqlClient.SqlClient;
64
- yield* sql.unsafe(`
65
- CREATE TABLE IF NOT EXISTS "posts" (
66
- "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
67
- "id" TEXT NOT NULL UNIQUE,
68
- "title" TEXT NOT NULL,
69
- "created" TEXT NOT NULL,
70
- "updated" TEXT NOT NULL
71
- )
169
+ yield* sql.unsafe(`
170
+ CREATE TABLE IF NOT EXISTS "posts" (
171
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
172
+ "id" TEXT NOT NULL UNIQUE,
173
+ "title" TEXT NOT NULL,
174
+ "created" TEXT NOT NULL,
175
+ "updated" TEXT NOT NULL
176
+ )
72
177
  `);
73
178
  yield* sql.unsafe(`DELETE FROM "posts"`);
74
179
  });
75
180
  const setupPerfView = Effect.gen(function* () {
76
181
  const sql = yield* SqlClient.SqlClient;
77
182
  // posts table required by the layer even though SlowView doesn't query it
78
- yield* sql.unsafe(`
79
- CREATE TABLE IF NOT EXISTS "posts" (
80
- "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
81
- "id" TEXT NOT NULL UNIQUE,
82
- "title" TEXT NOT NULL,
83
- "created" TEXT NOT NULL,
84
- "updated" TEXT NOT NULL
85
- )
183
+ yield* sql.unsafe(`
184
+ CREATE TABLE IF NOT EXISTS "posts" (
185
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
186
+ "id" TEXT NOT NULL UNIQUE,
187
+ "title" TEXT NOT NULL,
188
+ "created" TEXT NOT NULL,
189
+ "updated" TEXT NOT NULL
190
+ )
86
191
  `);
87
192
  yield* sql.unsafe(`DROP VIEW IF EXISTS "slow_posts"`);
88
193
  // This view is intentionally bad: the 100 000-step CTE forces SQLite to do
89
194
  // real work on every query execution. SUM(n) requires all rows — no early exit.
90
- yield* sql.unsafe(`
91
- CREATE VIEW "slow_posts" AS
92
- WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
93
- SELECT 1 AS seqId, 'perf-test-id' AS id, CAST(SUM(n) AS TEXT) AS title FROM gen
195
+ yield* sql.unsafe(`
196
+ CREATE VIEW "slow_posts" AS
197
+ WITH RECURSIVE gen(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM gen WHERE n < 100000)
198
+ SELECT 1 AS seqId, 'perf-test-id' AS id, CAST(SUM(n) AS TEXT) AS title FROM gen
94
199
  `);
95
200
  });
96
201
  describe("CachedCollectionService", () => {
@@ -132,10 +237,10 @@ describe("CachedCollectionService", () => {
132
237
  // Delete via service — invalidates cache
133
238
  yield* svc.delete(Posts, id, noCtx);
134
239
  // View should fail with NotFoundError
135
- const result = yield* Effect.either(svc.view(Posts, id, noCtx));
136
- expect(Either.isLeft(result)).toBe(true);
137
- if (Either.isLeft(result)) {
138
- expect(result.left instanceof NotFoundError).toBe(true);
240
+ const result = yield* Effect.result(svc.view(Posts, id, noCtx));
241
+ expect(Result.isFailure(result)).toBe(true);
242
+ if (Result.isFailure(result)) {
243
+ expect(result.failure._tag).toBe("NotFoundError");
139
244
  }
140
245
  }).pipe(Effect.provide(testLayer)));
141
246
  it.effect("list cache hit — second list returns cached count after direct DB insert", () => Effect.gen(function* () {
@@ -184,7 +289,7 @@ describe("CachedCollectionService", () => {
184
289
  it.effect("list cache invalidated by delete — record no longer in list", () => Effect.gen(function* () {
185
290
  yield* setupPostsTable;
186
291
  const svc = yield* CollectionService;
187
- const r1 = yield* svc.create(Posts, { title: "Keep" }, noCtx);
292
+ yield* svc.create(Posts, { title: "Keep" }, noCtx);
188
293
  const r2 = yield* svc.create(Posts, { title: "Remove" }, noCtx);
189
294
  const id2 = r2["id"];
190
295
  // Prime the list cache
@@ -200,6 +305,61 @@ describe("CachedCollectionService", () => {
200
305
  expect(titles).not.toContain("Remove");
201
306
  }).pipe(Effect.provide(testLayer)));
202
307
  });
308
+ describe("Cache key authorization scoping (security)", () => {
309
+ it.effect("auth-identity-bound rule — cache must not leak a record across different owners", () => Effect.gen(function* () {
310
+ yield* setupNotesTable;
311
+ const svc = yield* CollectionService;
312
+ const created = yield* svc.create(Notes, { ownerId: "userA", body: "secret" }, noCtx);
313
+ const id = created["id"];
314
+ const ctxOwner = { headers: {}, query: {}, auth: { collection: "users", record: { id: "userA" } } };
315
+ const ctxOther = { headers: {}, query: {}, auth: { collection: "users", record: { id: "userB" } } };
316
+ // Owner views their own note — populates the cache
317
+ const ownerView = yield* svc.view(Notes, id, ctxOwner);
318
+ expect(ownerView["body"]).toBe("secret");
319
+ // A different identity requests the same id/select/expand within the TTL window.
320
+ // The rule (ownerId == @auth_id) denies userB at the DB layer — a cache hit must
321
+ // not bypass that check and hand back userA's row.
322
+ const otherView = yield* Effect.result(svc.view(Notes, id, ctxOther));
323
+ expect(Result.isFailure(otherView)).toBe(true);
324
+ if (Result.isFailure(otherView)) {
325
+ expect(otherView.failure._tag).toBe("NotFoundError");
326
+ }
327
+ }).pipe(Effect.provide(securityTestLayer)));
328
+ it.effect("request-query-bound rule — cache must not leak a record across different query values", () => Effect.gen(function* () {
329
+ yield* setupTenantDocsTable;
330
+ const svc = yield* CollectionService;
331
+ const created = yield* svc.create(TenantDocs, { tenantId: "tenant-1", body: "secret" }, noCtx);
332
+ const id = created["id"];
333
+ const ctxTenant1 = { headers: {}, query: { tenantId: "tenant-1" } };
334
+ const ctxTenant2 = { headers: {}, query: { tenantId: "tenant-2" } };
335
+ // tenant-1 views the doc — populates the cache
336
+ const tenant1View = yield* svc.view(TenantDocs, id, ctxTenant1);
337
+ expect(tenant1View["body"]).toBe("secret");
338
+ // tenant-2 requests the same id/select/expand within the TTL window.
339
+ // The rule (tenantId == @request_query_tenantId) denies tenant-2 at the DB layer —
340
+ // a cache hit must not bypass that check.
341
+ const tenant2View = yield* Effect.result(svc.view(TenantDocs, id, ctxTenant2));
342
+ expect(Result.isFailure(tenant2View)).toBe(true);
343
+ if (Result.isFailure(tenant2View)) {
344
+ expect(tenant2View.failure._tag).toBe("NotFoundError");
345
+ }
346
+ }).pipe(Effect.provide(securityTestLayer)));
347
+ it.effect("ctx.admin-bound field visibility — cache must not leak hidden fields to non-admin requests", () => Effect.gen(function* () {
348
+ yield* setupAccountsTable;
349
+ const svc = yield* CollectionService;
350
+ const created = yield* svc.create(Accounts, { email: "a@b.com", password: "hashed-secret" }, adminCtx);
351
+ const id = created["id"];
352
+ // Admin views the record — password is x-hidden but admin bypasses field hiding —
353
+ // populates the cache with the password included. The password is hashed on
354
+ // write, so assert presence rather than the literal seeded value.
355
+ const adminView = yield* svc.view(Accounts, id, adminCtx);
356
+ expect(typeof adminView["password"]).toBe("string");
357
+ // Non-admin requests the same id/select/expand within the TTL window.
358
+ // A cache hit must not hand back the admin's unfiltered response.
359
+ const nonAdminView = yield* svc.view(Accounts, id, noCtx);
360
+ expect(nonAdminView["password"]).toBeUndefined();
361
+ }).pipe(Effect.provide(securityTestLayer)));
362
+ });
203
363
  describe("Performance — cache vs. DB", () => {
204
364
  // Each test uses a fresh :memory: DB and empty cache via perfTestLayer.
205
365
  // slow_posts VIEW forces 100 000 recursive CTE steps + full SUM aggregation