@gettersethya/mira 0.1.0-alpha.10

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 (399) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/dist/src/app/app.d.ts +100 -0
  4. package/dist/src/app/app.d.ts.map +1 -0
  5. package/dist/src/app/app.js +176 -0
  6. package/dist/src/app/app.js.map +1 -0
  7. package/dist/src/app/builder.d.ts +106 -0
  8. package/dist/src/app/builder.d.ts.map +1 -0
  9. package/dist/src/app/builder.js +122 -0
  10. package/dist/src/app/builder.js.map +1 -0
  11. package/dist/src/app/index.d.ts +33 -0
  12. package/dist/src/app/index.d.ts.map +1 -0
  13. package/dist/src/app/index.js +30 -0
  14. package/dist/src/app/index.js.map +1 -0
  15. package/dist/src/app/plugin.d.ts +60 -0
  16. package/dist/src/app/plugin.d.ts.map +1 -0
  17. package/dist/src/app/plugin.js +20 -0
  18. package/dist/src/app/plugin.js.map +1 -0
  19. package/dist/src/app/types.d.ts +55 -0
  20. package/dist/src/app/types.d.ts.map +1 -0
  21. package/dist/src/app/types.js +2 -0
  22. package/dist/src/app/types.js.map +1 -0
  23. package/dist/src/cache/cached-collection.d.ts +30 -0
  24. package/dist/src/cache/cached-collection.d.ts.map +1 -0
  25. package/dist/src/cache/cached-collection.js +107 -0
  26. package/dist/src/cache/cached-collection.js.map +1 -0
  27. package/dist/src/cache/collection-cache.d.ts +17 -0
  28. package/dist/src/cache/collection-cache.d.ts.map +1 -0
  29. package/dist/src/cache/collection-cache.js +37 -0
  30. package/dist/src/cache/collection-cache.js.map +1 -0
  31. package/dist/src/cache/index.d.ts +4 -0
  32. package/dist/src/cache/index.d.ts.map +1 -0
  33. package/dist/src/cache/index.js +3 -0
  34. package/dist/src/cache/index.js.map +1 -0
  35. package/dist/src/cache/types.d.ts +12 -0
  36. package/dist/src/cache/types.d.ts.map +1 -0
  37. package/dist/src/cache/types.js +2 -0
  38. package/dist/src/cache/types.js.map +1 -0
  39. package/dist/src/collection-service/collection-service.d.ts +62 -0
  40. package/dist/src/collection-service/collection-service.d.ts.map +1 -0
  41. package/dist/src/collection-service/collection-service.js +289 -0
  42. package/dist/src/collection-service/collection-service.js.map +1 -0
  43. package/dist/src/collection-service/context.d.ts +19 -0
  44. package/dist/src/collection-service/context.d.ts.map +1 -0
  45. package/dist/src/collection-service/context.js +2 -0
  46. package/dist/src/collection-service/context.js.map +1 -0
  47. package/dist/src/collection-service/decode.d.ts +6 -0
  48. package/dist/src/collection-service/decode.d.ts.map +1 -0
  49. package/dist/src/collection-service/decode.js +26 -0
  50. package/dist/src/collection-service/decode.js.map +1 -0
  51. package/dist/src/collection-service/errors.d.ts +67 -0
  52. package/dist/src/collection-service/errors.d.ts.map +1 -0
  53. package/dist/src/collection-service/errors.js +43 -0
  54. package/dist/src/collection-service/errors.js.map +1 -0
  55. package/dist/src/collection-service/index.d.ts +5 -0
  56. package/dist/src/collection-service/index.d.ts.map +1 -0
  57. package/dist/src/collection-service/index.js +3 -0
  58. package/dist/src/collection-service/index.js.map +1 -0
  59. package/dist/src/collection-service/input-schema.d.ts +10 -0
  60. package/dist/src/collection-service/input-schema.d.ts.map +1 -0
  61. package/dist/src/collection-service/input-schema.js +123 -0
  62. package/dist/src/collection-service/input-schema.js.map +1 -0
  63. package/dist/src/collection-service/where.d.ts +35 -0
  64. package/dist/src/collection-service/where.d.ts.map +1 -0
  65. package/dist/src/collection-service/where.js +91 -0
  66. package/dist/src/collection-service/where.js.map +1 -0
  67. package/dist/src/config/config.d.ts +44 -0
  68. package/dist/src/config/config.d.ts.map +1 -0
  69. package/dist/src/config/config.js +89 -0
  70. package/dist/src/config/config.js.map +1 -0
  71. package/dist/src/config/index.d.ts +3 -0
  72. package/dist/src/config/index.d.ts.map +1 -0
  73. package/dist/src/config/index.js +2 -0
  74. package/dist/src/config/index.js.map +1 -0
  75. package/dist/src/config/types.d.ts +29 -0
  76. package/dist/src/config/types.d.ts.map +1 -0
  77. package/dist/src/config/types.js +2 -0
  78. package/dist/src/config/types.js.map +1 -0
  79. package/dist/src/crypto/crypto.d.ts +11 -0
  80. package/dist/src/crypto/crypto.d.ts.map +1 -0
  81. package/dist/src/crypto/crypto.js +4 -0
  82. package/dist/src/crypto/crypto.js.map +1 -0
  83. package/dist/src/crypto/index.d.ts +3 -0
  84. package/dist/src/crypto/index.d.ts.map +1 -0
  85. package/dist/src/crypto/index.js +3 -0
  86. package/dist/src/crypto/index.js.map +1 -0
  87. package/dist/src/crypto/node.d.ts +4 -0
  88. package/dist/src/crypto/node.d.ts.map +1 -0
  89. package/dist/src/crypto/node.js +9 -0
  90. package/dist/src/crypto/node.js.map +1 -0
  91. package/dist/src/databases/index.d.ts +2 -0
  92. package/dist/src/databases/index.d.ts.map +1 -0
  93. package/dist/src/databases/index.js +2 -0
  94. package/dist/src/databases/index.js.map +1 -0
  95. package/dist/src/databases/sqlite.d.ts +24 -0
  96. package/dist/src/databases/sqlite.d.ts.map +1 -0
  97. package/dist/src/databases/sqlite.js +27 -0
  98. package/dist/src/databases/sqlite.js.map +1 -0
  99. package/dist/src/hooks/hook-collection.d.ts +5 -0
  100. package/dist/src/hooks/hook-collection.d.ts.map +1 -0
  101. package/dist/src/hooks/hook-collection.js +77 -0
  102. package/dist/src/hooks/hook-collection.js.map +1 -0
  103. package/dist/src/hooks/hook-service.d.ts +33 -0
  104. package/dist/src/hooks/hook-service.d.ts.map +1 -0
  105. package/dist/src/hooks/hook-service.js +118 -0
  106. package/dist/src/hooks/hook-service.js.map +1 -0
  107. package/dist/src/hooks/index.d.ts +4 -0
  108. package/dist/src/hooks/index.d.ts.map +1 -0
  109. package/dist/src/hooks/index.js +3 -0
  110. package/dist/src/hooks/index.js.map +1 -0
  111. package/dist/src/hooks/types.d.ts +52 -0
  112. package/dist/src/hooks/types.d.ts.map +1 -0
  113. package/dist/src/hooks/types.js +2 -0
  114. package/dist/src/hooks/types.js.map +1 -0
  115. package/dist/src/http/auth-node.d.ts +4 -0
  116. package/dist/src/http/auth-node.d.ts.map +1 -0
  117. package/dist/src/http/auth-node.js +34 -0
  118. package/dist/src/http/auth-node.js.map +1 -0
  119. package/dist/src/http/auth.d.ts +40 -0
  120. package/dist/src/http/auth.d.ts.map +1 -0
  121. package/dist/src/http/auth.js +76 -0
  122. package/dist/src/http/auth.js.map +1 -0
  123. package/dist/src/http/errors.d.ts +31 -0
  124. package/dist/src/http/errors.d.ts.map +1 -0
  125. package/dist/src/http/errors.js +36 -0
  126. package/dist/src/http/errors.js.map +1 -0
  127. package/dist/src/http/file-serve.d.ts +10 -0
  128. package/dist/src/http/file-serve.d.ts.map +1 -0
  129. package/dist/src/http/file-serve.js +159 -0
  130. package/dist/src/http/file-serve.js.map +1 -0
  131. package/dist/src/http/file-token.d.ts +7 -0
  132. package/dist/src/http/file-token.d.ts.map +1 -0
  133. package/dist/src/http/file-token.js +57 -0
  134. package/dist/src/http/file-token.js.map +1 -0
  135. package/dist/src/http/files.d.ts +11 -0
  136. package/dist/src/http/files.d.ts.map +1 -0
  137. package/dist/src/http/files.js +85 -0
  138. package/dist/src/http/files.js.map +1 -0
  139. package/dist/src/http/index.d.ts +11 -0
  140. package/dist/src/http/index.d.ts.map +1 -0
  141. package/dist/src/http/index.js +9 -0
  142. package/dist/src/http/index.js.map +1 -0
  143. package/dist/src/http/ip-middleware.d.ts +4 -0
  144. package/dist/src/http/ip-middleware.d.ts.map +1 -0
  145. package/dist/src/http/ip-middleware.js +24 -0
  146. package/dist/src/http/ip-middleware.js.map +1 -0
  147. package/dist/src/http/params.d.ts +15 -0
  148. package/dist/src/http/params.d.ts.map +1 -0
  149. package/dist/src/http/params.js +48 -0
  150. package/dist/src/http/params.js.map +1 -0
  151. package/dist/src/http/router.d.ts +11 -0
  152. package/dist/src/http/router.d.ts.map +1 -0
  153. package/dist/src/http/router.js +300 -0
  154. package/dist/src/http/router.js.map +1 -0
  155. package/dist/src/http/schema.d.ts +5 -0
  156. package/dist/src/http/schema.d.ts.map +1 -0
  157. package/dist/src/http/schema.js +17 -0
  158. package/dist/src/http/schema.js.map +1 -0
  159. package/dist/src/http/server-factory-node.d.ts +4 -0
  160. package/dist/src/http/server-factory-node.d.ts.map +1 -0
  161. package/dist/src/http/server-factory-node.js +8 -0
  162. package/dist/src/http/server-factory-node.js.map +1 -0
  163. package/dist/src/http/server-factory.d.ts +11 -0
  164. package/dist/src/http/server-factory.d.ts.map +1 -0
  165. package/dist/src/http/server-factory.js +4 -0
  166. package/dist/src/http/server-factory.js.map +1 -0
  167. package/dist/src/http/telemetry-routes.d.ts +5 -0
  168. package/dist/src/http/telemetry-routes.d.ts.map +1 -0
  169. package/dist/src/http/telemetry-routes.js +156 -0
  170. package/dist/src/http/telemetry-routes.js.map +1 -0
  171. package/dist/src/index.d.ts +19 -0
  172. package/dist/src/index.d.ts.map +1 -0
  173. package/dist/src/index.js +22 -0
  174. package/dist/src/index.js.map +1 -0
  175. package/dist/src/migrator/dialect-sqlite.d.ts +9 -0
  176. package/dist/src/migrator/dialect-sqlite.d.ts.map +1 -0
  177. package/dist/src/migrator/dialect-sqlite.js +102 -0
  178. package/dist/src/migrator/dialect-sqlite.js.map +1 -0
  179. package/dist/src/migrator/dialect.d.ts +24 -0
  180. package/dist/src/migrator/dialect.d.ts.map +1 -0
  181. package/dist/src/migrator/dialect.js +5 -0
  182. package/dist/src/migrator/dialect.js.map +1 -0
  183. package/dist/src/migrator/index.d.ts +8 -0
  184. package/dist/src/migrator/index.d.ts.map +1 -0
  185. package/dist/src/migrator/index.js +6 -0
  186. package/dist/src/migrator/index.js.map +1 -0
  187. package/dist/src/migrator/migrator.d.ts +36 -0
  188. package/dist/src/migrator/migrator.d.ts.map +1 -0
  189. package/dist/src/migrator/migrator.js +200 -0
  190. package/dist/src/migrator/migrator.js.map +1 -0
  191. package/dist/src/migrator/schema-diff.d.ts +25 -0
  192. package/dist/src/migrator/schema-diff.d.ts.map +1 -0
  193. package/dist/src/migrator/schema-diff.js +157 -0
  194. package/dist/src/migrator/schema-diff.js.map +1 -0
  195. package/dist/src/migrator/types.d.ts +91 -0
  196. package/dist/src/migrator/types.d.ts.map +1 -0
  197. package/dist/src/migrator/types.js +13 -0
  198. package/dist/src/migrator/types.js.map +1 -0
  199. package/dist/src/platforms/node.d.ts +40 -0
  200. package/dist/src/platforms/node.d.ts.map +1 -0
  201. package/dist/src/platforms/node.js +45 -0
  202. package/dist/src/platforms/node.js.map +1 -0
  203. package/dist/src/repository/id.d.ts +5 -0
  204. package/dist/src/repository/id.d.ts.map +1 -0
  205. package/dist/src/repository/id.js +9 -0
  206. package/dist/src/repository/id.js.map +1 -0
  207. package/dist/src/repository/index.d.ts +3 -0
  208. package/dist/src/repository/index.d.ts.map +1 -0
  209. package/dist/src/repository/index.js +2 -0
  210. package/dist/src/repository/index.js.map +1 -0
  211. package/dist/src/repository/repository.d.ts +53 -0
  212. package/dist/src/repository/repository.d.ts.map +1 -0
  213. package/dist/src/repository/repository.js +150 -0
  214. package/dist/src/repository/repository.js.map +1 -0
  215. package/dist/src/repository/types.d.ts +42 -0
  216. package/dist/src/repository/types.d.ts.map +1 -0
  217. package/dist/src/repository/types.js +2 -0
  218. package/dist/src/repository/types.js.map +1 -0
  219. package/dist/src/rule/compiler.d.ts +52 -0
  220. package/dist/src/rule/compiler.d.ts.map +1 -0
  221. package/dist/src/rule/compiler.js +115 -0
  222. package/dist/src/rule/compiler.js.map +1 -0
  223. package/dist/src/rule/enforcer.d.ts +47 -0
  224. package/dist/src/rule/enforcer.d.ts.map +1 -0
  225. package/dist/src/rule/enforcer.js +60 -0
  226. package/dist/src/rule/enforcer.js.map +1 -0
  227. package/dist/src/rule/index.d.ts +3 -0
  228. package/dist/src/rule/index.d.ts.map +1 -0
  229. package/dist/src/rule/index.js +3 -0
  230. package/dist/src/rule/index.js.map +1 -0
  231. package/dist/src/storage/index.d.ts +3 -0
  232. package/dist/src/storage/index.d.ts.map +1 -0
  233. package/dist/src/storage/index.js +3 -0
  234. package/dist/src/storage/index.js.map +1 -0
  235. package/dist/src/storage/local-preset.d.ts +21 -0
  236. package/dist/src/storage/local-preset.d.ts.map +1 -0
  237. package/dist/src/storage/local-preset.js +21 -0
  238. package/dist/src/storage/local-preset.js.map +1 -0
  239. package/dist/src/storage/storage.d.ts +83 -0
  240. package/dist/src/storage/storage.d.ts.map +1 -0
  241. package/dist/src/storage/storage.js +120 -0
  242. package/dist/src/storage/storage.js.map +1 -0
  243. package/dist/src/telemetry/collections.d.ts +4 -0
  244. package/dist/src/telemetry/collections.d.ts.map +1 -0
  245. package/dist/src/telemetry/collections.js +19 -0
  246. package/dist/src/telemetry/collections.js.map +1 -0
  247. package/dist/src/telemetry/index.d.ts +9 -0
  248. package/dist/src/telemetry/index.d.ts.map +1 -0
  249. package/dist/src/telemetry/index.js +9 -0
  250. package/dist/src/telemetry/index.js.map +1 -0
  251. package/dist/src/telemetry/logger.d.ts +4 -0
  252. package/dist/src/telemetry/logger.d.ts.map +1 -0
  253. package/dist/src/telemetry/logger.js +27 -0
  254. package/dist/src/telemetry/logger.js.map +1 -0
  255. package/dist/src/telemetry/sqlite-logger.d.ts +24 -0
  256. package/dist/src/telemetry/sqlite-logger.d.ts.map +1 -0
  257. package/dist/src/telemetry/sqlite-logger.js +152 -0
  258. package/dist/src/telemetry/sqlite-logger.js.map +1 -0
  259. package/dist/src/telemetry/telemetry-sql-client.d.ts +11 -0
  260. package/dist/src/telemetry/telemetry-sql-client.d.ts.map +1 -0
  261. package/dist/src/telemetry/telemetry-sql-client.js +8 -0
  262. package/dist/src/telemetry/telemetry-sql-client.js.map +1 -0
  263. package/dist/src/telemetry/tracer.d.ts +16 -0
  264. package/dist/src/telemetry/tracer.d.ts.map +1 -0
  265. package/dist/src/telemetry/tracer.js +106 -0
  266. package/dist/src/telemetry/tracer.js.map +1 -0
  267. package/dist/src/thumbnail/index.d.ts +4 -0
  268. package/dist/src/thumbnail/index.d.ts.map +1 -0
  269. package/dist/src/thumbnail/index.js +3 -0
  270. package/dist/src/thumbnail/index.js.map +1 -0
  271. package/dist/src/thumbnail/photon.d.ts +9 -0
  272. package/dist/src/thumbnail/photon.d.ts.map +1 -0
  273. package/dist/src/thumbnail/photon.js +73 -0
  274. package/dist/src/thumbnail/photon.js.map +1 -0
  275. package/dist/src/thumbnail/types.d.ts +23 -0
  276. package/dist/src/thumbnail/types.d.ts.map +1 -0
  277. package/dist/src/thumbnail/types.js +22 -0
  278. package/dist/src/thumbnail/types.js.map +1 -0
  279. package/dist/tests/app/app.test.d.ts +2 -0
  280. package/dist/tests/app/app.test.d.ts.map +1 -0
  281. package/dist/tests/app/app.test.js +125 -0
  282. package/dist/tests/app/app.test.js.map +1 -0
  283. package/dist/tests/app/builder.test.d.ts +2 -0
  284. package/dist/tests/app/builder.test.d.ts.map +1 -0
  285. package/dist/tests/app/builder.test.js +70 -0
  286. package/dist/tests/app/builder.test.js.map +1 -0
  287. package/dist/tests/cache/cached-collection.test.d.ts +2 -0
  288. package/dist/tests/cache/cached-collection.test.d.ts.map +1 -0
  289. package/dist/tests/cache/cached-collection.test.js +244 -0
  290. package/dist/tests/cache/cached-collection.test.js.map +1 -0
  291. package/dist/tests/cache/collection-cache.test.d.ts +2 -0
  292. package/dist/tests/cache/collection-cache.test.d.ts.map +1 -0
  293. package/dist/tests/cache/collection-cache.test.js +73 -0
  294. package/dist/tests/cache/collection-cache.test.js.map +1 -0
  295. package/dist/tests/collection-service/collection-service.test.d.ts +2 -0
  296. package/dist/tests/collection-service/collection-service.test.d.ts.map +1 -0
  297. package/dist/tests/collection-service/collection-service.test.js +938 -0
  298. package/dist/tests/collection-service/collection-service.test.js.map +1 -0
  299. package/dist/tests/collection-service/input-schema.test.d.ts +2 -0
  300. package/dist/tests/collection-service/input-schema.test.d.ts.map +1 -0
  301. package/dist/tests/collection-service/input-schema.test.js +327 -0
  302. package/dist/tests/collection-service/input-schema.test.js.map +1 -0
  303. package/dist/tests/config/config.test.d.ts +2 -0
  304. package/dist/tests/config/config.test.d.ts.map +1 -0
  305. package/dist/tests/config/config.test.js +84 -0
  306. package/dist/tests/config/config.test.js.map +1 -0
  307. package/dist/tests/crypto/crypto.test.d.ts +2 -0
  308. package/dist/tests/crypto/crypto.test.d.ts.map +1 -0
  309. package/dist/tests/crypto/crypto.test.js +56 -0
  310. package/dist/tests/crypto/crypto.test.js.map +1 -0
  311. package/dist/tests/hooks/hook-collection.test.d.ts +2 -0
  312. package/dist/tests/hooks/hook-collection.test.d.ts.map +1 -0
  313. package/dist/tests/hooks/hook-collection.test.js +103 -0
  314. package/dist/tests/hooks/hook-collection.test.js.map +1 -0
  315. package/dist/tests/hooks/hook-service.test.d.ts +2 -0
  316. package/dist/tests/hooks/hook-service.test.d.ts.map +1 -0
  317. package/dist/tests/hooks/hook-service.test.js +216 -0
  318. package/dist/tests/hooks/hook-service.test.js.map +1 -0
  319. package/dist/tests/http/file-serve.test.d.ts +2 -0
  320. package/dist/tests/http/file-serve.test.d.ts.map +1 -0
  321. package/dist/tests/http/file-serve.test.js +210 -0
  322. package/dist/tests/http/file-serve.test.js.map +1 -0
  323. package/dist/tests/http/file-token.test.d.ts +2 -0
  324. package/dist/tests/http/file-token.test.d.ts.map +1 -0
  325. package/dist/tests/http/file-token.test.js +165 -0
  326. package/dist/tests/http/file-token.test.js.map +1 -0
  327. package/dist/tests/http/ip-middleware.test.d.ts +2 -0
  328. package/dist/tests/http/ip-middleware.test.d.ts.map +1 -0
  329. package/dist/tests/http/ip-middleware.test.js +58 -0
  330. package/dist/tests/http/ip-middleware.test.js.map +1 -0
  331. package/dist/tests/http/router.test.d.ts +2 -0
  332. package/dist/tests/http/router.test.d.ts.map +1 -0
  333. package/dist/tests/http/router.test.js +312 -0
  334. package/dist/tests/http/router.test.js.map +1 -0
  335. package/dist/tests/migrator/dialect-sqlite.test.d.ts +2 -0
  336. package/dist/tests/migrator/dialect-sqlite.test.d.ts.map +1 -0
  337. package/dist/tests/migrator/dialect-sqlite.test.js +90 -0
  338. package/dist/tests/migrator/dialect-sqlite.test.js.map +1 -0
  339. package/dist/tests/migrator/integration.test.d.ts +2 -0
  340. package/dist/tests/migrator/integration.test.d.ts.map +1 -0
  341. package/dist/tests/migrator/integration.test.js +180 -0
  342. package/dist/tests/migrator/integration.test.js.map +1 -0
  343. package/dist/tests/migrator/migrator.test.d.ts +2 -0
  344. package/dist/tests/migrator/migrator.test.d.ts.map +1 -0
  345. package/dist/tests/migrator/migrator.test.js +225 -0
  346. package/dist/tests/migrator/migrator.test.js.map +1 -0
  347. package/dist/tests/migrator/schema-diff.test.d.ts +2 -0
  348. package/dist/tests/migrator/schema-diff.test.d.ts.map +1 -0
  349. package/dist/tests/migrator/schema-diff.test.js +323 -0
  350. package/dist/tests/migrator/schema-diff.test.js.map +1 -0
  351. package/dist/tests/repository/repository.test.d.ts +2 -0
  352. package/dist/tests/repository/repository.test.d.ts.map +1 -0
  353. package/dist/tests/repository/repository.test.js +255 -0
  354. package/dist/tests/repository/repository.test.js.map +1 -0
  355. package/dist/tests/rule/compiler.test.d.ts +2 -0
  356. package/dist/tests/rule/compiler.test.d.ts.map +1 -0
  357. package/dist/tests/rule/compiler.test.js +226 -0
  358. package/dist/tests/rule/compiler.test.js.map +1 -0
  359. package/dist/tests/rule/enforcer.test.d.ts +2 -0
  360. package/dist/tests/rule/enforcer.test.d.ts.map +1 -0
  361. package/dist/tests/rule/enforcer.test.js +190 -0
  362. package/dist/tests/rule/enforcer.test.js.map +1 -0
  363. package/dist/tests/storage/storage.test.d.ts +2 -0
  364. package/dist/tests/storage/storage.test.d.ts.map +1 -0
  365. package/dist/tests/storage/storage.test.js +76 -0
  366. package/dist/tests/storage/storage.test.js.map +1 -0
  367. package/dist/tests/telemetry/collections.test.d.ts +2 -0
  368. package/dist/tests/telemetry/collections.test.d.ts.map +1 -0
  369. package/dist/tests/telemetry/collections.test.js +67 -0
  370. package/dist/tests/telemetry/collections.test.js.map +1 -0
  371. package/dist/tests/telemetry/instrumentation.test.d.ts +2 -0
  372. package/dist/tests/telemetry/instrumentation.test.d.ts.map +1 -0
  373. package/dist/tests/telemetry/instrumentation.test.js +226 -0
  374. package/dist/tests/telemetry/instrumentation.test.js.map +1 -0
  375. package/dist/tests/telemetry/logger.test.d.ts +2 -0
  376. package/dist/tests/telemetry/logger.test.d.ts.map +1 -0
  377. package/dist/tests/telemetry/logger.test.js +56 -0
  378. package/dist/tests/telemetry/logger.test.js.map +1 -0
  379. package/dist/tests/telemetry/sqlite-logger.test.d.ts +2 -0
  380. package/dist/tests/telemetry/sqlite-logger.test.d.ts.map +1 -0
  381. package/dist/tests/telemetry/sqlite-logger.test.js +218 -0
  382. package/dist/tests/telemetry/sqlite-logger.test.js.map +1 -0
  383. package/dist/tests/telemetry/telemetry-routes.test.d.ts +2 -0
  384. package/dist/tests/telemetry/telemetry-routes.test.d.ts.map +1 -0
  385. package/dist/tests/telemetry/telemetry-routes.test.js +133 -0
  386. package/dist/tests/telemetry/telemetry-routes.test.js.map +1 -0
  387. package/dist/tests/telemetry/tracer.test.d.ts +2 -0
  388. package/dist/tests/telemetry/tracer.test.d.ts.map +1 -0
  389. package/dist/tests/telemetry/tracer.test.js +63 -0
  390. package/dist/tests/telemetry/tracer.test.js.map +1 -0
  391. package/dist/tests/thumbnail/photon.test.d.ts +2 -0
  392. package/dist/tests/thumbnail/photon.test.d.ts.map +1 -0
  393. package/dist/tests/thumbnail/photon.test.js +478 -0
  394. package/dist/tests/thumbnail/photon.test.js.map +1 -0
  395. package/dist/tests/thumbnail/thumbnail.test.d.ts +2 -0
  396. package/dist/tests/thumbnail/thumbnail.test.d.ts.map +1 -0
  397. package/dist/tests/thumbnail/thumbnail.test.js +35 -0
  398. package/dist/tests/thumbnail/thumbnail.test.js.map +1 -0
  399. package/package.json +46 -0
@@ -0,0 +1,938 @@
1
+ import { SqlClient } from "@effect/sql";
2
+ import { SqliteClient } from "@effect/sql-sqlite-node";
3
+ import { Effect, Either, Layer, Match } from "effect";
4
+ import { describe, it } from "@effect/vitest";
5
+ import { expect } from "vitest";
6
+ import { AuthCollection, BaseCollection } from "@gettersethya/mira-client";
7
+ import { Field } from "@gettersethya/mira-client";
8
+ import { ViewCollection } from "@gettersethya/mira-client";
9
+ import { RepositoryLive } from "../../src/repository/repository.js";
10
+ import { CollectionService, makeCollectionServiceLayer } from "../../src/collection-service/collection-service.js";
11
+ import { FileStorage, FileStorageNotFound } from "../../src/storage/storage.js";
12
+ import { NodeCryptoLayer } from "../../src/crypto/node.js";
13
+ // ---------------------------------------------------------------------------
14
+ // Test layer
15
+ // ---------------------------------------------------------------------------
16
+ const FileStorageTest = Layer.succeed(FileStorage, FileStorage.of({
17
+ upload: (key) => Effect.succeed(key),
18
+ delete: () => Effect.void,
19
+ url: (key) => `/files/${key}`,
20
+ read: (key) => Effect.fail(new FileStorageNotFound({ key })),
21
+ exists: () => Effect.succeed(false),
22
+ list: () => Effect.succeed([])
23
+ }));
24
+ const sqliteLayer = SqliteClient.layer({ filename: ":memory:" });
25
+ const collectionServiceWithDeps = makeCollectionServiceLayer([]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
26
+ const testLayer = Layer.mergeAll(collectionServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
27
+ const noCtx = { headers: {}, query: {} };
28
+ // ---------------------------------------------------------------------------
29
+ // Table setup helpers
30
+ // seqId as INTEGER PRIMARY KEY AUTOINCREMENT so SQLite fills it on INSERT.
31
+ // ---------------------------------------------------------------------------
32
+ const setupPostsTable = Effect.gen(function* () {
33
+ const sql = yield* SqlClient.SqlClient;
34
+ yield* sql.unsafe(`
35
+ CREATE TABLE IF NOT EXISTS "posts" (
36
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
37
+ "id" TEXT NOT NULL UNIQUE,
38
+ "title" TEXT NOT NULL,
39
+ "published" INTEGER NOT NULL DEFAULT 0,
40
+ "authorId" TEXT,
41
+ "created" TEXT NOT NULL,
42
+ "updated" TEXT NOT NULL
43
+ )
44
+ `);
45
+ yield* sql.unsafe(`DELETE FROM "posts"`);
46
+ });
47
+ // View collection tests: a base table + a SQLite VIEW over it
48
+ const setupViewTables = Effect.gen(function* () {
49
+ const sql = yield* SqlClient.SqlClient;
50
+ yield* sql.unsafe(`
51
+ CREATE TABLE IF NOT EXISTS "posts_base" (
52
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
53
+ "id" TEXT NOT NULL UNIQUE,
54
+ "title" TEXT NOT NULL,
55
+ "status" TEXT NOT NULL DEFAULT 'draft',
56
+ "created" TEXT NOT NULL,
57
+ "updated" TEXT NOT NULL
58
+ )
59
+ `);
60
+ yield* sql.unsafe(`DELETE FROM "posts_base"`);
61
+ yield* sql.unsafe(`DROP VIEW IF EXISTS "active_posts"`);
62
+ yield* sql.unsafe(`
63
+ CREATE VIEW "active_posts" AS
64
+ SELECT seqId, id, title, status, created, updated FROM posts_base WHERE status = 'active'
65
+ `);
66
+ });
67
+ const setupScoredTable = Effect.gen(function* () {
68
+ const sql = yield* SqlClient.SqlClient;
69
+ yield* sql.unsafe(`
70
+ CREATE TABLE IF NOT EXISTS "scored" (
71
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
72
+ "id" TEXT NOT NULL UNIQUE,
73
+ "name" TEXT NOT NULL,
74
+ "score" REAL NOT NULL,
75
+ "created" TEXT NOT NULL,
76
+ "updated" TEXT NOT NULL
77
+ )
78
+ `);
79
+ yield* sql.unsafe(`DELETE FROM "scored"`);
80
+ });
81
+ // ---------------------------------------------------------------------------
82
+ // Collection definitions
83
+ // ---------------------------------------------------------------------------
84
+ // Base collection — all operations public
85
+ const Posts = BaseCollection.define("posts", {
86
+ title: Field.text({ maxLength: 100 }),
87
+ published: Field.boolean({ default: false }),
88
+ authorId: Field.text({ required: false })
89
+ }).rules((R) => ({
90
+ list: R.public(),
91
+ view: R.public(),
92
+ create: R.public(),
93
+ update: R.public(),
94
+ delete: R.public()
95
+ }));
96
+ // Base collection — rule-enforced: only authorId "user1" can access
97
+ const ProtectedPosts = BaseCollection.define("posts", {
98
+ title: Field.text(),
99
+ published: Field.boolean({ default: false }),
100
+ authorId: Field.text({ required: false })
101
+ }).rules((R) => ({
102
+ list: R.field("authorId").eq(R.literal("user1")),
103
+ view: R.field("authorId").eq(R.literal("user1")),
104
+ create: R.field("authorId").eq(R.literal("user1")),
105
+ update: R.field("authorId").eq(R.literal("user1")),
106
+ delete: R.field("authorId").eq(R.literal("user1"))
107
+ }));
108
+ // Base collection — no rules (deny all by default)
109
+ const NoRulePosts = BaseCollection.define("posts", {
110
+ title: Field.text()
111
+ });
112
+ // View collection — public list/view, no create/update/delete
113
+ const ActivePosts = ViewCollection.define("active_posts", "SELECT seqId, id, title, status, created, updated FROM posts_base WHERE status = 'active'", {
114
+ seqId: Field.integer().view(),
115
+ id: Field.text().view(),
116
+ title: Field.text().view(),
117
+ status: Field.text().view()
118
+ }).rules((R) => ({
119
+ list: R.public(),
120
+ view: R.public()
121
+ }));
122
+ // Collection with error callbacks for integration testing
123
+ const Scored = BaseCollection.define("scored", {
124
+ name: Field.text({
125
+ minLength: 2,
126
+ error: (kind) => Match.value(kind).pipe(Match.when("minLength", () => "Name too short"), Match.when("required", () => "Name is required"), Match.orElse(() => "Invalid name"))
127
+ }),
128
+ score: Field.number({
129
+ min: 0,
130
+ max: 100,
131
+ error: (kind) => Match.value(kind).pipe(Match.when("minimum", () => "Score below zero"), Match.when("maximum", () => "Score above 100"), Match.orElse(() => "Invalid score"))
132
+ })
133
+ }).rules((R) => ({
134
+ create: R.public(),
135
+ update: R.public(),
136
+ list: R.public(),
137
+ view: R.public(),
138
+ delete: R.public()
139
+ }));
140
+ // Collection with a literalText field
141
+ const Roles = BaseCollection.define("roles", {
142
+ role: Field.literalText({ literal: ["admin", "agent", "readonly"] })
143
+ }).rules((R) => ({
144
+ create: R.public(),
145
+ update: R.public(),
146
+ list: R.public(),
147
+ view: R.public(),
148
+ delete: R.public()
149
+ }));
150
+ // ScoredServiceLayer wires Scored into the collection service
151
+ const scoredServiceWithDeps = makeCollectionServiceLayer([Scored]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
152
+ const scoredTestLayer = Layer.mergeAll(scoredServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
153
+ // RolesServiceLayer wires Roles into the collection service
154
+ const rolesServiceWithDeps = makeCollectionServiceLayer([Roles]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
155
+ const rolesTestLayer = Layer.mergeAll(rolesServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
156
+ // ---------------------------------------------------------------------------
157
+ // Helpers
158
+ // ---------------------------------------------------------------------------
159
+ function isCursorPage(r) {
160
+ return typeof r === "object" && r !== null && "nextCursor" in r;
161
+ }
162
+ // ---------------------------------------------------------------------------
163
+ // list
164
+ // ---------------------------------------------------------------------------
165
+ describe("list", () => {
166
+ it.effect("public base collection returns CursorPage shape", () => Effect.gen(function* () {
167
+ yield* setupPostsTable;
168
+ const svc = yield* CollectionService;
169
+ const result = yield* svc.list(Posts, null, 10, noCtx);
170
+ expect(isCursorPage(result)).toBe(true);
171
+ }).pipe(Effect.provide(testLayer)));
172
+ it.effect("returns all inserted rows", () => Effect.gen(function* () {
173
+ yield* setupPostsTable;
174
+ const svc = yield* CollectionService;
175
+ yield* svc.create(Posts, { title: "A" }, noCtx);
176
+ yield* svc.create(Posts, { title: "B" }, noCtx);
177
+ const result = yield* svc.list(Posts, null, 10, noCtx);
178
+ expect(isCursorPage(result) && result.items.length).toBe(2);
179
+ }).pipe(Effect.provide(testLayer)));
180
+ it.effect("seqId is NOT present in list items (x-hidden stripped)", () => Effect.gen(function* () {
181
+ yield* setupPostsTable;
182
+ const svc = yield* CollectionService;
183
+ yield* svc.create(Posts, { title: "X" }, noCtx);
184
+ const result = yield* svc.list(Posts, null, 10, noCtx);
185
+ if (!isCursorPage(result))
186
+ throw new Error("expected CursorPage");
187
+ expect("seqId" in result.items[0]).toBe(false);
188
+ }).pipe(Effect.provide(testLayer)));
189
+ it.effect("nextCursor is null when page is not full", () => Effect.gen(function* () {
190
+ yield* setupPostsTable;
191
+ const svc = yield* CollectionService;
192
+ yield* svc.create(Posts, { title: "A" }, noCtx);
193
+ const result = yield* svc.list(Posts, null, 10, noCtx);
194
+ if (!isCursorPage(result))
195
+ throw new Error("expected CursorPage");
196
+ expect(result.nextCursor).toBeNull();
197
+ }).pipe(Effect.provide(testLayer)));
198
+ it.effect("nextCursor is a number when page is full", () => Effect.gen(function* () {
199
+ yield* setupPostsTable;
200
+ const svc = yield* CollectionService;
201
+ yield* svc.create(Posts, { title: "A" }, noCtx);
202
+ yield* svc.create(Posts, { title: "B" }, noCtx);
203
+ yield* svc.create(Posts, { title: "C" }, noCtx);
204
+ const result = yield* svc.list(Posts, null, 2, noCtx);
205
+ if (!isCursorPage(result))
206
+ throw new Error("expected CursorPage");
207
+ expect(typeof result.nextCursor).toBe("number");
208
+ }).pipe(Effect.provide(testLayer)));
209
+ it.effect("cursor pagination: page 2 fetches rows not in page 1", () => Effect.gen(function* () {
210
+ yield* setupPostsTable;
211
+ const svc = yield* CollectionService;
212
+ yield* svc.create(Posts, { title: "A" }, noCtx);
213
+ yield* svc.create(Posts, { title: "B" }, noCtx);
214
+ yield* svc.create(Posts, { title: "C" }, noCtx);
215
+ yield* svc.create(Posts, { title: "D" }, noCtx);
216
+ yield* svc.create(Posts, { title: "E" }, noCtx);
217
+ const page1 = yield* svc.list(Posts, null, 2, noCtx);
218
+ if (!isCursorPage(page1))
219
+ throw new Error("expected CursorPage");
220
+ expect(page1.items.length).toBe(2);
221
+ expect(page1.nextCursor).not.toBeNull();
222
+ if (page1.nextCursor === null)
223
+ throw new Error("expected nextCursor");
224
+ const page2 = yield* svc.list(Posts, page1.nextCursor, 2, noCtx);
225
+ if (!isCursorPage(page2))
226
+ throw new Error("expected CursorPage");
227
+ expect(page2.items.length).toBe(2);
228
+ if (page2.nextCursor === null)
229
+ throw new Error("expected nextCursor");
230
+ const page3 = yield* svc.list(Posts, page2.nextCursor, 2, noCtx);
231
+ if (!isCursorPage(page3))
232
+ throw new Error("expected CursorPage");
233
+ expect(page3.items.length).toBe(1);
234
+ expect(page3.nextCursor).toBeNull();
235
+ const ids1 = page1.items.map((r) => r["id"]);
236
+ const ids2 = page2.items.map((r) => r["id"]);
237
+ expect(ids1.some((id) => ids2.includes(id))).toBe(false);
238
+ }).pipe(Effect.provide(testLayer)));
239
+ it.effect("rule-enforced list returns only matching rows", () => Effect.gen(function* () {
240
+ yield* setupPostsTable;
241
+ const svc = yield* CollectionService;
242
+ yield* svc.create(Posts, { title: "Mine", authorId: "user1" }, noCtx);
243
+ yield* svc.create(Posts, { title: "Theirs", authorId: "user2" }, noCtx);
244
+ const result = yield* svc.list(ProtectedPosts, null, 10, noCtx);
245
+ if (!isCursorPage(result))
246
+ throw new Error("expected CursorPage");
247
+ expect(result.items.length).toBe(1);
248
+ expect(result.items[0]["title"]).toBe("Mine");
249
+ }).pipe(Effect.provide(testLayer)));
250
+ it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
251
+ yield* setupPostsTable;
252
+ const svc = yield* CollectionService;
253
+ const result = yield* svc.list(NoRulePosts, null, 10, noCtx).pipe(Effect.either);
254
+ expect(Either.isLeft(result)).toBe(true);
255
+ if (Either.isLeft(result)) {
256
+ expect(result.left._tag).toBe("ForbiddenError");
257
+ if (result.left._tag === "ForbiddenError") {
258
+ expect(result.left.action).toBe("list");
259
+ }
260
+ }
261
+ }).pipe(Effect.provide(testLayer)));
262
+ it.effect("view collection returns CursorPage shape", () => Effect.gen(function* () {
263
+ yield* setupViewTables;
264
+ const svc = yield* CollectionService;
265
+ const result = yield* svc.list(ActivePosts, null, 10, noCtx);
266
+ expect(isCursorPage(result)).toBe(true);
267
+ }).pipe(Effect.provide(testLayer)));
268
+ it.effect("view collection list returns only rows from the view", () => Effect.gen(function* () {
269
+ yield* setupViewTables;
270
+ const sql = yield* SqlClient.SqlClient;
271
+ // Insert directly into posts_base since repository uses different table name
272
+ yield* sql.unsafe(`INSERT INTO posts_base (id, title, status, created, updated) VALUES (?, ?, ?, datetime('now'), datetime('now'))`, ["id1", "Active Post", "active"]);
273
+ yield* sql.unsafe(`INSERT INTO posts_base (id, title, status, created, updated) VALUES (?, ?, ?, datetime('now'), datetime('now'))`, ["id2", "Draft Post", "draft"]);
274
+ const svc = yield* CollectionService;
275
+ const result = yield* svc.list(ActivePosts, null, 10, noCtx);
276
+ if (!isCursorPage(result))
277
+ throw new Error("expected CursorPage");
278
+ expect(result.items.length).toBe(1);
279
+ expect(result.items[0]["title"]).toBe("Active Post");
280
+ }).pipe(Effect.provide(testLayer)));
281
+ });
282
+ // ---------------------------------------------------------------------------
283
+ // list with filter
284
+ // ---------------------------------------------------------------------------
285
+ describe("list with filter", () => {
286
+ it.effect("filters by eq on string field", () => Effect.gen(function* () {
287
+ yield* setupPostsTable;
288
+ const svc = yield* CollectionService;
289
+ yield* svc.create(Posts, { title: "Apple" }, noCtx);
290
+ yield* svc.create(Posts, { title: "Banana" }, noCtx);
291
+ yield* svc.create(Posts, { title: "Apple Pie" }, noCtx);
292
+ const result = yield* svc.list(Posts, null, 10, noCtx, {
293
+ op: "like",
294
+ field: "title",
295
+ value: "Apple%"
296
+ });
297
+ if (!isCursorPage(result))
298
+ throw new Error("expected CursorPage");
299
+ expect(result.items.length).toBe(2);
300
+ }).pipe(Effect.provide(testLayer)));
301
+ it.effect("filters by gt on integer field", () => Effect.gen(function* () {
302
+ yield* setupPostsTable;
303
+ const svc = yield* CollectionService;
304
+ yield* svc.create(Posts, { title: "A", authorId: "10" }, noCtx);
305
+ yield* svc.create(Posts, { title: "B", authorId: "20" }, noCtx);
306
+ yield* svc.create(Posts, { title: "C", authorId: "30" }, noCtx);
307
+ // authorId is text but we can still compare with gt textually
308
+ const result = yield* svc.list(Posts, null, 10, noCtx, {
309
+ op: "gt",
310
+ field: "authorId",
311
+ value: "20"
312
+ });
313
+ if (!isCursorPage(result))
314
+ throw new Error("expected CursorPage");
315
+ expect(result.items.length).toBe(1);
316
+ expect(result.items[0]["title"]).toBe("C");
317
+ }).pipe(Effect.provide(testLayer)));
318
+ it.effect("and filter combines conditions", () => Effect.gen(function* () {
319
+ yield* setupPostsTable;
320
+ const svc = yield* CollectionService;
321
+ yield* svc.create(Posts, { title: "Alpha", authorId: "user1" }, noCtx);
322
+ yield* svc.create(Posts, { title: "Beta", authorId: "user2" }, noCtx);
323
+ yield* svc.create(Posts, { title: "Gamma", authorId: "user1" }, noCtx);
324
+ // Filter: title starts with "A" AND authorId = "user1"
325
+ // We can approximate startsWith using like
326
+ const result = yield* svc.list(Posts, null, 10, noCtx, {
327
+ op: "and",
328
+ left: { op: "like", field: "title", value: "A%" },
329
+ right: { op: "eq", field: "authorId", value: "user1" }
330
+ });
331
+ if (!isCursorPage(result))
332
+ throw new Error("expected CursorPage");
333
+ expect(result.items.length).toBe(1);
334
+ expect(result.items[0]["title"]).toBe("Alpha");
335
+ }).pipe(Effect.provide(testLayer)));
336
+ it.effect("filter is ANDed with rule enforcement", () => Effect.gen(function* () {
337
+ yield* setupPostsTable;
338
+ const svc = yield* CollectionService;
339
+ yield* svc.create(Posts, { title: "X", authorId: "user1" }, noCtx);
340
+ yield* svc.create(Posts, { title: "Y", authorId: "user1" }, noCtx);
341
+ yield* svc.create(Posts, { title: "Z", authorId: "user2" }, noCtx);
342
+ // ProtectedPosts rule: authorId = "user1"
343
+ // Filter: title like "X" → should return only "X" (which is user1's)
344
+ const result = yield* svc.list(ProtectedPosts, null, 10, noCtx, {
345
+ op: "like",
346
+ field: "title",
347
+ value: "X"
348
+ });
349
+ if (!isCursorPage(result))
350
+ throw new Error("expected CursorPage");
351
+ expect(result.items.length).toBe(1);
352
+ expect(result.items[0]["title"]).toBe("X");
353
+ }).pipe(Effect.provide(testLayer)));
354
+ it.effect("in filter works", () => Effect.gen(function* () {
355
+ yield* setupPostsTable;
356
+ const svc = yield* CollectionService;
357
+ yield* svc.create(Posts, { title: "Red" }, noCtx);
358
+ yield* svc.create(Posts, { title: "Green" }, noCtx);
359
+ yield* svc.create(Posts, { title: "Blue" }, noCtx);
360
+ const result = yield* svc.list(Posts, null, 10, noCtx, {
361
+ op: "in",
362
+ field: "title",
363
+ values: ["Red", "Blue"]
364
+ });
365
+ if (!isCursorPage(result))
366
+ throw new Error("expected CursorPage");
367
+ expect(result.items.length).toBe(2);
368
+ const titles = result.items.map((r) => r["title"]).sort();
369
+ expect(titles).toEqual(["Blue", "Red"]);
370
+ }).pipe(Effect.provide(testLayer)));
371
+ it.effect("or filter works", () => Effect.gen(function* () {
372
+ yield* setupPostsTable;
373
+ const svc = yield* CollectionService;
374
+ yield* svc.create(Posts, { title: "Cat", authorId: "a" }, noCtx);
375
+ yield* svc.create(Posts, { title: "Dog", authorId: "b" }, noCtx);
376
+ yield* svc.create(Posts, { title: "Fish", authorId: "c" }, noCtx);
377
+ // title = "Cat" OR authorId = "b"
378
+ const result = yield* svc.list(Posts, null, 10, noCtx, {
379
+ op: "or",
380
+ left: { op: "eq", field: "title", value: "Cat" },
381
+ right: { op: "eq", field: "authorId", value: "b" }
382
+ });
383
+ if (!isCursorPage(result))
384
+ throw new Error("expected CursorPage");
385
+ expect(result.items.length).toBe(2);
386
+ const titles = result.items.map((r) => r["title"]).sort();
387
+ expect(titles).toEqual(["Cat", "Dog"]);
388
+ }).pipe(Effect.provide(testLayer)));
389
+ it.effect("not filter works", () => Effect.gen(function* () {
390
+ yield* setupPostsTable;
391
+ const svc = yield* CollectionService;
392
+ yield* svc.create(Posts, { title: "Keep" }, noCtx);
393
+ yield* svc.create(Posts, { title: "Skip" }, noCtx);
394
+ yield* svc.create(Posts, { title: "Keep2" }, noCtx);
395
+ // NOT (title = "Skip")
396
+ const result = yield* svc.list(Posts, null, 10, noCtx, {
397
+ op: "not",
398
+ node: { op: "eq", field: "title", value: "Skip" }
399
+ });
400
+ if (!isCursorPage(result))
401
+ throw new Error("expected CursorPage");
402
+ expect(result.items.length).toBe(2);
403
+ const titles = result.items.map((r) => r["title"]);
404
+ expect(titles).not.toContain("Skip");
405
+ }).pipe(Effect.provide(testLayer)));
406
+ });
407
+ // ---------------------------------------------------------------------------
408
+ // view
409
+ // ---------------------------------------------------------------------------
410
+ describe("view", () => {
411
+ it.effect("returns the row for an existing id", () => Effect.gen(function* () {
412
+ yield* setupPostsTable;
413
+ const svc = yield* CollectionService;
414
+ const created = yield* svc.create(Posts, { title: "Hello" }, noCtx);
415
+ const id = created["id"];
416
+ if (typeof id !== "string")
417
+ throw new Error("expected string id");
418
+ const result = yield* svc.view(Posts, id, noCtx);
419
+ expect(result["title"]).toBe("Hello");
420
+ }).pipe(Effect.provide(testLayer)));
421
+ it.effect("returns NotFoundError for an unknown id", () => Effect.gen(function* () {
422
+ yield* setupPostsTable;
423
+ const svc = yield* CollectionService;
424
+ const result = yield* svc.view(Posts, "nonexistent123", noCtx).pipe(Effect.either);
425
+ expect(Either.isLeft(result)).toBe(true);
426
+ if (Either.isLeft(result)) {
427
+ expect(result.left._tag).toBe("NotFoundError");
428
+ }
429
+ }).pipe(Effect.provide(testLayer)));
430
+ it.effect("seqId is NOT in the returned row", () => Effect.gen(function* () {
431
+ yield* setupPostsTable;
432
+ const svc = yield* CollectionService;
433
+ const created = yield* svc.create(Posts, { title: "Hidden" }, noCtx);
434
+ const id = created["id"];
435
+ if (typeof id !== "string")
436
+ throw new Error("expected string id");
437
+ const result = yield* svc.view(Posts, id, noCtx);
438
+ expect("seqId" in result).toBe(false);
439
+ }).pipe(Effect.provide(testLayer)));
440
+ it.effect("row not matching rule → NotFoundError (indistinguishable from missing)", () => Effect.gen(function* () {
441
+ yield* setupPostsTable;
442
+ const svc = yield* CollectionService;
443
+ const created = yield* svc.create(Posts, { title: "Other", authorId: "user2" }, noCtx);
444
+ const id = created["id"];
445
+ if (typeof id !== "string")
446
+ throw new Error("expected string id");
447
+ const result = yield* svc.view(ProtectedPosts, id, noCtx).pipe(Effect.either);
448
+ expect(Either.isLeft(result)).toBe(true);
449
+ if (Either.isLeft(result)) {
450
+ expect(result.left._tag).toBe("NotFoundError");
451
+ }
452
+ }).pipe(Effect.provide(testLayer)));
453
+ it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
454
+ yield* setupPostsTable;
455
+ const svc = yield* CollectionService;
456
+ const result = yield* svc.view(NoRulePosts, "any", noCtx).pipe(Effect.either);
457
+ expect(Either.isLeft(result)).toBe(true);
458
+ if (Either.isLeft(result)) {
459
+ expect(result.left._tag).toBe("ForbiddenError");
460
+ }
461
+ }).pipe(Effect.provide(testLayer)));
462
+ });
463
+ // ---------------------------------------------------------------------------
464
+ // create
465
+ // ---------------------------------------------------------------------------
466
+ describe("create", () => {
467
+ it.effect("inserts a valid row and returns it with id, created, updated", () => Effect.gen(function* () {
468
+ yield* setupPostsTable;
469
+ const svc = yield* CollectionService;
470
+ const result = yield* svc.create(Posts, { title: "New Post" }, noCtx);
471
+ expect(typeof result["id"]).toBe("string");
472
+ expect(result["title"]).toBe("New Post");
473
+ expect(typeof result["created"]).toBe("string");
474
+ expect(typeof result["updated"]).toBe("string");
475
+ }).pipe(Effect.provide(testLayer)));
476
+ it.effect("seqId is NOT in the returned row", () => Effect.gen(function* () {
477
+ yield* setupPostsTable;
478
+ const svc = yield* CollectionService;
479
+ const result = yield* svc.create(Posts, { title: "X" }, noCtx);
480
+ expect("seqId" in result).toBe(false);
481
+ }).pipe(Effect.provide(testLayer)));
482
+ it.effect("ValidationError when required field is missing", () => Effect.gen(function* () {
483
+ yield* setupPostsTable;
484
+ const svc = yield* CollectionService;
485
+ const result = yield* svc.create(Posts, { published: true }, noCtx).pipe(Effect.either);
486
+ expect(Either.isLeft(result)).toBe(true);
487
+ if (Either.isLeft(result)) {
488
+ expect(result.left._tag).toBe("ValidationError");
489
+ if (result.left._tag === "ValidationError") {
490
+ expect(result.left.issues.some((i) => i.includes("title"))).toBe(true);
491
+ }
492
+ }
493
+ }).pipe(Effect.provide(testLayer)));
494
+ it.effect("ValidationError when x-system field is in input", () => Effect.gen(function* () {
495
+ yield* setupPostsTable;
496
+ const svc = yield* CollectionService;
497
+ const result = yield* svc.create(Posts, { title: "X", id: "hacked" }, noCtx).pipe(Effect.either);
498
+ expect(Either.isLeft(result)).toBe(true);
499
+ if (Either.isLeft(result)) {
500
+ expect(result.left._tag).toBe("ValidationError");
501
+ if (result.left._tag === "ValidationError") {
502
+ expect(result.left.issues.some((i) => i.includes("id"))).toBe(true);
503
+ }
504
+ }
505
+ }).pipe(Effect.provide(testLayer)));
506
+ it.effect("ValidationError when seqId is in input", () => Effect.gen(function* () {
507
+ yield* setupPostsTable;
508
+ const svc = yield* CollectionService;
509
+ const result = yield* svc.create(Posts, { title: "X", seqId: 99 }, noCtx).pipe(Effect.either);
510
+ expect(Either.isLeft(result)).toBe(true);
511
+ if (Either.isLeft(result)) {
512
+ expect(result.left._tag).toBe("ValidationError");
513
+ }
514
+ }).pipe(Effect.provide(testLayer)));
515
+ it.effect("ValidationError when type is wrong", () => Effect.gen(function* () {
516
+ yield* setupPostsTable;
517
+ const svc = yield* CollectionService;
518
+ const result = yield* svc.create(Posts, { title: 42 }, noCtx).pipe(Effect.either);
519
+ expect(Either.isLeft(result)).toBe(true);
520
+ if (Either.isLeft(result)) {
521
+ expect(result.left._tag).toBe("ValidationError");
522
+ }
523
+ }).pipe(Effect.provide(testLayer)));
524
+ it.effect("ValidationError when string exceeds maxLength", () => Effect.gen(function* () {
525
+ yield* setupPostsTable;
526
+ const svc = yield* CollectionService;
527
+ const result = yield* svc
528
+ .create(Posts, { title: "x".repeat(101) }, noCtx)
529
+ .pipe(Effect.either);
530
+ expect(Either.isLeft(result)).toBe(true);
531
+ if (Either.isLeft(result)) {
532
+ expect(result.left._tag).toBe("ValidationError");
533
+ if (result.left._tag === "ValidationError") {
534
+ expect(result.left.issues.some((i) => i.includes("100"))).toBe(true);
535
+ }
536
+ }
537
+ }).pipe(Effect.provide(testLayer)));
538
+ it.effect("ForbiddenError when create rule pre-check fails", () => Effect.gen(function* () {
539
+ yield* setupPostsTable;
540
+ const svc = yield* CollectionService;
541
+ // ProtectedPosts requires authorId = "user1"; supply "user2" → rule fails
542
+ const result = yield* svc
543
+ .create(ProtectedPosts, { title: "X", authorId: "user2" }, noCtx)
544
+ .pipe(Effect.either);
545
+ expect(Either.isLeft(result)).toBe(true);
546
+ if (Either.isLeft(result)) {
547
+ expect(result.left._tag).toBe("ForbiddenError");
548
+ }
549
+ }).pipe(Effect.provide(testLayer)));
550
+ it.effect("create succeeds when rule pre-check passes", () => Effect.gen(function* () {
551
+ yield* setupPostsTable;
552
+ const svc = yield* CollectionService;
553
+ const result = yield* svc.create(ProtectedPosts, { title: "X", authorId: "user1" }, noCtx);
554
+ expect(result["title"]).toBe("X");
555
+ }).pipe(Effect.provide(testLayer)));
556
+ it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
557
+ yield* setupPostsTable;
558
+ const svc = yield* CollectionService;
559
+ const result = yield* svc.create(NoRulePosts, { title: "X" }, noCtx).pipe(Effect.either);
560
+ expect(Either.isLeft(result)).toBe(true);
561
+ if (Either.isLeft(result)) {
562
+ expect(result.left._tag).toBe("ForbiddenError");
563
+ }
564
+ }).pipe(Effect.provide(testLayer)));
565
+ it.effect("view collection → ReadOnlyError", () => Effect.gen(function* () {
566
+ yield* setupViewTables;
567
+ const svc = yield* CollectionService;
568
+ const result = yield* svc
569
+ .create(ActivePosts, { id: "x", title: "X", status: "active" }, noCtx)
570
+ .pipe(Effect.either);
571
+ expect(Either.isLeft(result)).toBe(true);
572
+ if (Either.isLeft(result)) {
573
+ expect(result.left._tag).toBe("ReadOnlyError");
574
+ }
575
+ }).pipe(Effect.provide(testLayer)));
576
+ });
577
+ // ---------------------------------------------------------------------------
578
+ // update
579
+ // ---------------------------------------------------------------------------
580
+ describe("update", () => {
581
+ it.effect("updates supplied fields, leaves others unchanged", () => Effect.gen(function* () {
582
+ yield* setupPostsTable;
583
+ const svc = yield* CollectionService;
584
+ const created = yield* svc.create(Posts, { title: "Old", authorId: "user1" }, noCtx);
585
+ const id = created["id"];
586
+ if (typeof id !== "string")
587
+ throw new Error("expected string id");
588
+ const updated = yield* svc.update(Posts, id, { title: "New" }, noCtx);
589
+ expect(updated["title"]).toBe("New");
590
+ expect(updated["authorId"]).toBe("user1");
591
+ }).pipe(Effect.provide(testLayer)));
592
+ it.live("updated timestamp is refreshed; created is unchanged", () => Effect.gen(function* () {
593
+ yield* setupPostsTable;
594
+ const svc = yield* CollectionService;
595
+ const created = yield* svc.create(Posts, { title: "Time" }, noCtx);
596
+ yield* Effect.sleep(5);
597
+ const id = created["id"];
598
+ if (typeof id !== "string")
599
+ throw new Error("expected string id");
600
+ const updated = yield* svc.update(Posts, id, { title: "Time2" }, noCtx);
601
+ expect(updated["created"]).toBe(created["created"]);
602
+ expect(updated["updated"]).not.toBe(created["updated"]);
603
+ }).pipe(Effect.provide(testLayer)));
604
+ it.effect("ValidationError when x-system field is in patch", () => Effect.gen(function* () {
605
+ yield* setupPostsTable;
606
+ const svc = yield* CollectionService;
607
+ const created = yield* svc.create(Posts, { title: "X" }, noCtx);
608
+ const id = created["id"];
609
+ if (typeof id !== "string")
610
+ throw new Error("expected string id");
611
+ const result = yield* svc
612
+ .update(Posts, id, { title: "Y", id: "hacked", created: "1970" }, noCtx)
613
+ .pipe(Effect.either);
614
+ expect(Either.isLeft(result)).toBe(true);
615
+ if (Either.isLeft(result)) {
616
+ expect(result.left._tag).toBe("ValidationError");
617
+ }
618
+ }).pipe(Effect.provide(testLayer)));
619
+ it.effect("NotFoundError for unknown id", () => Effect.gen(function* () {
620
+ yield* setupPostsTable;
621
+ const svc = yield* CollectionService;
622
+ const result = yield* svc
623
+ .update(Posts, "nosuchid1234567", { title: "X" }, noCtx)
624
+ .pipe(Effect.either);
625
+ expect(Either.isLeft(result)).toBe(true);
626
+ if (Either.isLeft(result)) {
627
+ expect(result.left._tag).toBe("NotFoundError");
628
+ }
629
+ }).pipe(Effect.provide(testLayer)));
630
+ it.effect("ForbiddenError when rule blocks update", () => Effect.gen(function* () {
631
+ yield* setupPostsTable;
632
+ const svc = yield* CollectionService;
633
+ // Insert row with authorId user2 using public Posts, then try to update via ProtectedPosts
634
+ const created = yield* svc.create(Posts, { title: "X", authorId: "user2" }, noCtx);
635
+ const id = created["id"];
636
+ if (typeof id !== "string")
637
+ throw new Error("expected string id");
638
+ const result = yield* svc
639
+ .update(ProtectedPosts, id, { title: "Y" }, noCtx)
640
+ .pipe(Effect.either);
641
+ expect(Either.isLeft(result)).toBe(true);
642
+ if (Either.isLeft(result)) {
643
+ expect(result.left._tag).toBe("ForbiddenError");
644
+ if (result.left._tag === "ForbiddenError") {
645
+ expect(result.left.action).toBe("update");
646
+ }
647
+ }
648
+ }).pipe(Effect.provide(testLayer)));
649
+ it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
650
+ yield* setupPostsTable;
651
+ const svc = yield* CollectionService;
652
+ const result = yield* svc
653
+ .update(NoRulePosts, "anyid", { title: "X" }, noCtx)
654
+ .pipe(Effect.either);
655
+ expect(Either.isLeft(result)).toBe(true);
656
+ if (Either.isLeft(result)) {
657
+ expect(result.left._tag).toBe("ForbiddenError");
658
+ }
659
+ }).pipe(Effect.provide(testLayer)));
660
+ it.effect("view collection → ReadOnlyError", () => Effect.gen(function* () {
661
+ yield* setupViewTables;
662
+ const svc = yield* CollectionService;
663
+ const result = yield* svc
664
+ .update(ActivePosts, "anyid", { title: "X" }, noCtx)
665
+ .pipe(Effect.either);
666
+ expect(Either.isLeft(result)).toBe(true);
667
+ if (Either.isLeft(result)) {
668
+ expect(result.left._tag).toBe("ReadOnlyError");
669
+ }
670
+ }).pipe(Effect.provide(testLayer)));
671
+ });
672
+ // ---------------------------------------------------------------------------
673
+ // delete
674
+ // ---------------------------------------------------------------------------
675
+ describe("delete", () => {
676
+ it.effect("deletes an existing row and returns void", () => Effect.gen(function* () {
677
+ yield* setupPostsTable;
678
+ const svc = yield* CollectionService;
679
+ const created = yield* svc.create(Posts, { title: "Gone" }, noCtx);
680
+ const id = created["id"];
681
+ if (typeof id !== "string")
682
+ throw new Error("expected string id");
683
+ yield* svc.delete(Posts, id, noCtx);
684
+ }).pipe(Effect.provide(testLayer)));
685
+ it.effect("row is gone after delete (confirmed via view)", () => Effect.gen(function* () {
686
+ yield* setupPostsTable;
687
+ const svc = yield* CollectionService;
688
+ const created = yield* svc.create(Posts, { title: "Gone" }, noCtx);
689
+ const id = created["id"];
690
+ if (typeof id !== "string")
691
+ throw new Error("expected string id");
692
+ yield* svc.delete(Posts, id, noCtx);
693
+ const result = yield* svc.view(Posts, id, noCtx).pipe(Effect.either);
694
+ expect(Either.isLeft(result)).toBe(true);
695
+ if (Either.isLeft(result)) {
696
+ expect(result.left._tag).toBe("NotFoundError");
697
+ }
698
+ }).pipe(Effect.provide(testLayer)));
699
+ it.effect("NotFoundError for unknown id", () => Effect.gen(function* () {
700
+ yield* setupPostsTable;
701
+ const svc = yield* CollectionService;
702
+ const result = yield* svc.delete(Posts, "nosuchid1234567", noCtx).pipe(Effect.either);
703
+ expect(Either.isLeft(result)).toBe(true);
704
+ if (Either.isLeft(result)) {
705
+ expect(result.left._tag).toBe("NotFoundError");
706
+ }
707
+ }).pipe(Effect.provide(testLayer)));
708
+ it.effect("ForbiddenError when rule blocks delete", () => Effect.gen(function* () {
709
+ yield* setupPostsTable;
710
+ const svc = yield* CollectionService;
711
+ const created = yield* svc.create(Posts, { title: "X", authorId: "user2" }, noCtx);
712
+ const id = created["id"];
713
+ if (typeof id !== "string")
714
+ throw new Error("expected string id");
715
+ const result = yield* svc
716
+ .delete(ProtectedPosts, id, noCtx)
717
+ .pipe(Effect.either);
718
+ expect(Either.isLeft(result)).toBe(true);
719
+ if (Either.isLeft(result)) {
720
+ expect(result.left._tag).toBe("ForbiddenError");
721
+ if (result.left._tag === "ForbiddenError") {
722
+ expect(result.left.action).toBe("delete");
723
+ }
724
+ }
725
+ }).pipe(Effect.provide(testLayer)));
726
+ it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
727
+ yield* setupPostsTable;
728
+ const svc = yield* CollectionService;
729
+ const result = yield* svc.delete(NoRulePosts, "anyid", noCtx).pipe(Effect.either);
730
+ expect(Either.isLeft(result)).toBe(true);
731
+ if (Either.isLeft(result)) {
732
+ expect(result.left._tag).toBe("ForbiddenError");
733
+ }
734
+ }).pipe(Effect.provide(testLayer)));
735
+ it.effect("view collection → ReadOnlyError", () => Effect.gen(function* () {
736
+ yield* setupViewTables;
737
+ const svc = yield* CollectionService;
738
+ const result = yield* svc.delete(ActivePosts, "anyid", noCtx).pipe(Effect.either);
739
+ expect(Either.isLeft(result)).toBe(true);
740
+ if (Either.isLeft(result)) {
741
+ expect(result.left._tag).toBe("ReadOnlyError");
742
+ }
743
+ }).pipe(Effect.provide(testLayer)));
744
+ });
745
+ // ---------------------------------------------------------------------------
746
+ // error callback — integration tests
747
+ // ---------------------------------------------------------------------------
748
+ describe("error callback — integration", () => {
749
+ it.effect("minLength message propagates from create", () => Effect.gen(function* () {
750
+ yield* setupScoredTable;
751
+ const svc = yield* CollectionService;
752
+ const result = yield* svc.create(Scored, { name: "x", score: 50 }, noCtx).pipe(Effect.either);
753
+ expect(Either.isLeft(result)).toBe(true);
754
+ if (Either.isLeft(result) && result.left._tag === "ValidationError") {
755
+ expect(result.left.issues.some((i) => i.includes("Name too short"))).toBe(true);
756
+ }
757
+ }).pipe(Effect.provide(scoredTestLayer)));
758
+ it.effect("minimum message propagates from create", () => Effect.gen(function* () {
759
+ yield* setupScoredTable;
760
+ const svc = yield* CollectionService;
761
+ const result = yield* svc.create(Scored, { name: "ok", score: -1 }, noCtx).pipe(Effect.either);
762
+ expect(Either.isLeft(result)).toBe(true);
763
+ if (Either.isLeft(result) && result.left._tag === "ValidationError") {
764
+ expect(result.left.issues.some((i) => i.includes("Score below zero"))).toBe(true);
765
+ }
766
+ }).pipe(Effect.provide(scoredTestLayer)));
767
+ it.effect("maximum message propagates from create", () => Effect.gen(function* () {
768
+ yield* setupScoredTable;
769
+ const svc = yield* CollectionService;
770
+ const result = yield* svc.create(Scored, { name: "ok", score: 200 }, noCtx).pipe(Effect.either);
771
+ expect(Either.isLeft(result)).toBe(true);
772
+ if (Either.isLeft(result) && result.left._tag === "ValidationError") {
773
+ expect(result.left.issues.some((i) => i.includes("Score above 100"))).toBe(true);
774
+ }
775
+ }).pipe(Effect.provide(scoredTestLayer)));
776
+ it.effect("required message propagates from create", () => Effect.gen(function* () {
777
+ yield* setupScoredTable;
778
+ const svc = yield* CollectionService;
779
+ const result = yield* svc.create(Scored, { score: 50 }, noCtx).pipe(Effect.either);
780
+ expect(Either.isLeft(result)).toBe(true);
781
+ if (Either.isLeft(result) && result.left._tag === "ValidationError") {
782
+ expect(result.left.issues.some((i) => i.includes("Name is required"))).toBe(true);
783
+ }
784
+ }).pipe(Effect.provide(scoredTestLayer)));
785
+ it.effect("Match.orElse fallback fires for type mismatch", () => Effect.gen(function* () {
786
+ yield* setupScoredTable;
787
+ const svc = yield* CollectionService;
788
+ const result = yield* svc.create(Scored, { name: 999, score: 50 }, noCtx).pipe(Effect.either);
789
+ expect(Either.isLeft(result)).toBe(true);
790
+ if (Either.isLeft(result) && result.left._tag === "ValidationError") {
791
+ expect(result.left.issues.some((i) => i.includes("Invalid name"))).toBe(true);
792
+ }
793
+ }).pipe(Effect.provide(scoredTestLayer)));
794
+ it.effect("update with too-short name produces default message (no custom error on update path)", () => Effect.gen(function* () {
795
+ yield* setupScoredTable;
796
+ const svc = yield* CollectionService;
797
+ const created = yield* svc.create(Scored, { name: "ok", score: 50 }, noCtx);
798
+ const id = created["id"];
799
+ if (typeof id !== "string")
800
+ throw new Error("expected string id");
801
+ const result = yield* svc.update(Scored, id, { name: "x" }, noCtx).pipe(Effect.either);
802
+ expect(Either.isLeft(result)).toBe(true);
803
+ if (Either.isLeft(result)) {
804
+ expect(result.left._tag).toBe("ValidationError");
805
+ if (result.left._tag === "ValidationError") {
806
+ expect(result.left.issues.length).toBeGreaterThan(0);
807
+ }
808
+ }
809
+ }).pipe(Effect.provide(scoredTestLayer)));
810
+ it.effect("valid data succeeds", () => Effect.gen(function* () {
811
+ yield* setupScoredTable;
812
+ const svc = yield* CollectionService;
813
+ const result = yield* svc.create(Scored, { name: "ok", score: 50 }, noCtx);
814
+ expect(result["name"]).toBe("ok");
815
+ expect(result["score"]).toBe(50);
816
+ }).pipe(Effect.provide(scoredTestLayer)));
817
+ });
818
+ // ---------------------------------------------------------------------------
819
+ // literalText integration tests
820
+ // ---------------------------------------------------------------------------
821
+ const setupRolesTable = Effect.gen(function* () {
822
+ const sql = yield* SqlClient.SqlClient;
823
+ yield* sql.unsafe(`
824
+ CREATE TABLE IF NOT EXISTS "roles" (
825
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
826
+ "id" TEXT NOT NULL UNIQUE,
827
+ "role" TEXT NOT NULL,
828
+ "created" TEXT NOT NULL,
829
+ "updated" TEXT NOT NULL
830
+ )
831
+ `);
832
+ yield* sql.unsafe(`DELETE FROM "roles"`);
833
+ });
834
+ describe("literalText integration", () => {
835
+ it.effect("create with valid literal value succeeds", () => Effect.gen(function* () {
836
+ yield* setupRolesTable;
837
+ const svc = yield* CollectionService;
838
+ const result = yield* svc.create(Roles, { role: "admin" }, noCtx);
839
+ expect(result["role"]).toBe("admin");
840
+ }).pipe(Effect.provide(rolesTestLayer)));
841
+ it.effect("create with invalid literal value fails with ValidationError", () => Effect.gen(function* () {
842
+ yield* setupRolesTable;
843
+ const svc = yield* CollectionService;
844
+ const result = yield* svc.create(Roles, { role: "superadmin" }, noCtx).pipe(Effect.either);
845
+ expect(Either.isLeft(result)).toBe(true);
846
+ if (Either.isLeft(result)) {
847
+ expect(result.left._tag).toBe("ValidationError");
848
+ }
849
+ }).pipe(Effect.provide(rolesTestLayer)));
850
+ it.effect("update with invalid literal value fails with ValidationError", () => Effect.gen(function* () {
851
+ yield* setupRolesTable;
852
+ const svc = yield* CollectionService;
853
+ const created = yield* svc.create(Roles, { role: "admin" }, noCtx);
854
+ const id = created["id"];
855
+ if (typeof id !== "string")
856
+ throw new Error("expected string id");
857
+ const result = yield* svc.update(Roles, id, { role: "superadmin" }, noCtx).pipe(Effect.either);
858
+ expect(Either.isLeft(result)).toBe(true);
859
+ if (Either.isLeft(result)) {
860
+ expect(result.left._tag).toBe("ValidationError");
861
+ }
862
+ }).pipe(Effect.provide(rolesTestLayer)));
863
+ it.effect("update with valid literal value succeeds", () => Effect.gen(function* () {
864
+ yield* setupRolesTable;
865
+ const svc = yield* CollectionService;
866
+ const created = yield* svc.create(Roles, { role: "admin" }, noCtx);
867
+ const id = created["id"];
868
+ if (typeof id !== "string")
869
+ throw new Error("expected string id");
870
+ const updated = yield* svc.update(Roles, id, { role: "agent" }, noCtx);
871
+ expect(updated["role"]).toBe("agent");
872
+ }).pipe(Effect.provide(rolesTestLayer)));
873
+ });
874
+ // ---------------------------------------------------------------------------
875
+ // AuthCollection create — regression: email/password were blocked by x-system
876
+ // ---------------------------------------------------------------------------
877
+ const SuperAdmin = AuthCollection.define("_superadmin", {}).rules((R) => ({
878
+ list: R.field("email").eq(R.literal("")),
879
+ view: R.field("email").eq(R.literal("")),
880
+ create: R.public(),
881
+ update: R.field("email").eq(R.literal("")),
882
+ delete: R.field("email").eq(R.literal(""))
883
+ }));
884
+ const setupSuperAdminTable = Effect.gen(function* () {
885
+ const sql = yield* SqlClient.SqlClient;
886
+ yield* sql.unsafe(`
887
+ CREATE TABLE IF NOT EXISTS "_superadmin" (
888
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
889
+ "id" TEXT NOT NULL UNIQUE,
890
+ "email" TEXT NOT NULL UNIQUE,
891
+ "password" TEXT NOT NULL,
892
+ "emailVerified" INTEGER NOT NULL DEFAULT 0,
893
+ "created" TEXT NOT NULL,
894
+ "updated" TEXT NOT NULL
895
+ )
896
+ `);
897
+ yield* sql.unsafe(`DELETE FROM "_superadmin"`);
898
+ });
899
+ const superAdminTestLayer = Layer.mergeAll(makeCollectionServiceLayer([SuperAdmin]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer)), sqliteLayer, FileStorageTest, NodeCryptoLayer);
900
+ const adminCtx = { headers: {}, query: {}, admin: true };
901
+ describe("AuthCollection create", () => {
902
+ it.effect("succeeds with email and password (regression: was blocked by x-system check)", () => Effect.gen(function* () {
903
+ yield* setupSuperAdminTable;
904
+ const svc = yield* CollectionService;
905
+ const record = yield* svc.create(SuperAdmin, { email: "admin@example.com", password: "hashed-pw" }, adminCtx);
906
+ expect(record["email"]).toBe("admin@example.com");
907
+ expect(typeof record["id"]).toBe("string");
908
+ expect(record["id"]).not.toBe("");
909
+ }).pipe(Effect.provide(superAdminTestLayer)));
910
+ it.effect("rejects attempt to set generated field id", () => Effect.gen(function* () {
911
+ yield* setupSuperAdminTable;
912
+ const svc = yield* CollectionService;
913
+ const result = yield* svc.create(SuperAdmin, { email: "a@b.com", password: "pw", id: "custom-id" }, adminCtx).pipe(Effect.either);
914
+ expect(Either.isLeft(result)).toBe(true);
915
+ if (Either.isLeft(result)) {
916
+ expect(result.left._tag).toBe("ValidationError");
917
+ }
918
+ }).pipe(Effect.provide(superAdminTestLayer)));
919
+ it.effect("fails validation when email is missing", () => Effect.gen(function* () {
920
+ yield* setupSuperAdminTable;
921
+ const svc = yield* CollectionService;
922
+ const result = yield* svc.create(SuperAdmin, { password: "pw" }, adminCtx).pipe(Effect.either);
923
+ expect(Either.isLeft(result)).toBe(true);
924
+ if (Either.isLeft(result)) {
925
+ expect(result.left._tag).toBe("ValidationError");
926
+ }
927
+ }).pipe(Effect.provide(superAdminTestLayer)));
928
+ it.effect("fails validation when email format is invalid", () => Effect.gen(function* () {
929
+ yield* setupSuperAdminTable;
930
+ const svc = yield* CollectionService;
931
+ const result = yield* svc.create(SuperAdmin, { email: "notanemail", password: "pw" }, adminCtx).pipe(Effect.either);
932
+ expect(Either.isLeft(result)).toBe(true);
933
+ if (Either.isLeft(result)) {
934
+ expect(result.left._tag).toBe("ValidationError");
935
+ }
936
+ }).pipe(Effect.provide(superAdminTestLayer)));
937
+ });
938
+ //# sourceMappingURL=collection-service.test.js.map