@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,15 +1,19 @@
1
- import { SqlClient } from "@effect/sql";
2
1
  import { SqliteClient } from "@effect/sql-sqlite-node";
3
- import { Effect, Either, Layer, Match } from "effect";
4
2
  import { describe, it } from "@effect/vitest";
5
- import { expect } from "vitest";
6
- import { BaseCollection } from "@gettersethya/mira-client";
3
+ import { AuthCollection, BaseCollection } from "@gettersethya/mira-client";
7
4
  import { Field } from "@gettersethya/mira-client";
8
5
  import { ViewCollection } from "@gettersethya/mira-client";
9
- import { RepositoryLive } from "../../src/repository/repository.js";
6
+ import { Effect, Layer, Match, Result } from "effect";
7
+ import { SqlClient } from "effect/sql";
8
+ import { expect } from "vitest";
9
+ import { applyRulesToCollections, defineRule } from "../../src/app/index.js";
10
10
  import { CollectionService, makeCollectionServiceLayer } from "../../src/collection-service/collection-service.js";
11
- import { FileStorage, FileStorageNotFound } from "../../src/storage/storage.js";
12
11
  import { NodeCryptoLayer } from "../../src/crypto/node.js";
12
+ import { Dialect } from "../../src/dialect/dialect.js";
13
+ import { sqliteDialect } from "../../src/dialect/dialect-sqlite.js";
14
+ import { NodeAuthServiceLayer } from "../../src/http/auth-node.js";
15
+ import { RepositoryLive } from "../../src/repository/repository.js";
16
+ import { FileStorage, FileStorageNotFound } from "../../src/storage/storage.js";
13
17
  // ---------------------------------------------------------------------------
14
18
  // Test layer
15
19
  // ---------------------------------------------------------------------------
@@ -22,8 +26,7 @@ const FileStorageTest = Layer.succeed(FileStorage, FileStorage.of({
22
26
  list: () => Effect.succeed([])
23
27
  }));
24
28
  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);
29
+ const DialectTest = Layer.succeed(Dialect, sqliteDialect);
27
30
  const noCtx = { headers: {}, query: {} };
28
31
  // ---------------------------------------------------------------------------
29
32
  // Table setup helpers
@@ -31,50 +34,50 @@ const noCtx = { headers: {}, query: {} };
31
34
  // ---------------------------------------------------------------------------
32
35
  const setupPostsTable = Effect.gen(function* () {
33
36
  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
- )
37
+ yield* sql.unsafe(`
38
+ CREATE TABLE IF NOT EXISTS "posts" (
39
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ "id" TEXT NOT NULL UNIQUE,
41
+ "title" TEXT NOT NULL,
42
+ "published" INTEGER NOT NULL DEFAULT 0,
43
+ "authorId" TEXT,
44
+ "created" TEXT NOT NULL,
45
+ "updated" TEXT NOT NULL
46
+ )
44
47
  `);
45
48
  yield* sql.unsafe(`DELETE FROM "posts"`);
46
49
  });
47
50
  // View collection tests: a base table + a SQLite VIEW over it
48
51
  const setupViewTables = Effect.gen(function* () {
49
52
  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
- )
53
+ yield* sql.unsafe(`
54
+ CREATE TABLE IF NOT EXISTS "posts_base" (
55
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ "id" TEXT NOT NULL UNIQUE,
57
+ "title" TEXT NOT NULL,
58
+ "status" TEXT NOT NULL DEFAULT 'draft',
59
+ "created" TEXT NOT NULL,
60
+ "updated" TEXT NOT NULL
61
+ )
59
62
  `);
60
63
  yield* sql.unsafe(`DELETE FROM "posts_base"`);
61
64
  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
+ yield* sql.unsafe(`
66
+ CREATE VIEW "active_posts" AS
67
+ SELECT seqId, id, title, status, created, updated FROM posts_base WHERE status = 'active'
65
68
  `);
66
69
  });
67
70
  const setupScoredTable = Effect.gen(function* () {
68
71
  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
- )
72
+ yield* sql.unsafe(`
73
+ CREATE TABLE IF NOT EXISTS "scored" (
74
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
75
+ "id" TEXT NOT NULL UNIQUE,
76
+ "name" TEXT NOT NULL,
77
+ "score" REAL NOT NULL,
78
+ "created" TEXT NOT NULL,
79
+ "updated" TEXT NOT NULL
80
+ )
78
81
  `);
79
82
  yield* sql.unsafe(`DELETE FROM "scored"`);
80
83
  });
@@ -82,45 +85,51 @@ const setupScoredTable = Effect.gen(function* () {
82
85
  // Collection definitions
83
86
  // ---------------------------------------------------------------------------
84
87
  // Base collection — all operations public
85
- const Posts = BaseCollection.define("posts", {
88
+ const postsDef = BaseCollection.define("posts", {
86
89
  title: Field.text({ maxLength: 100 }),
87
90
  published: Field.boolean({ default: false }),
88
91
  authorId: Field.text({ required: false })
89
- }).rules((R) => ({
92
+ });
93
+ const postsRules = defineRule(postsDef, (R) => ({
90
94
  list: R.public(),
91
95
  view: R.public(),
92
96
  create: R.public(),
93
97
  update: R.public(),
94
98
  delete: R.public()
95
99
  }));
100
+ const Posts = applyRulesToCollections([postsDef], [postsRules])[0];
96
101
  // Base collection — rule-enforced: only authorId "user1" can access
97
- const ProtectedPosts = BaseCollection.define("posts", {
102
+ const protectedPostsDef = BaseCollection.define("posts", {
98
103
  title: Field.text(),
99
104
  published: Field.boolean({ default: false }),
100
105
  authorId: Field.text({ required: false })
101
- }).rules((R) => ({
106
+ });
107
+ const protectedPostsRules = defineRule(protectedPostsDef, (R) => ({
102
108
  list: R.field("authorId").eq(R.literal("user1")),
103
109
  view: R.field("authorId").eq(R.literal("user1")),
104
110
  create: R.field("authorId").eq(R.literal("user1")),
105
111
  update: R.field("authorId").eq(R.literal("user1")),
106
112
  delete: R.field("authorId").eq(R.literal("user1"))
107
113
  }));
114
+ const ProtectedPosts = applyRulesToCollections([protectedPostsDef], [protectedPostsRules])[0];
108
115
  // Base collection — no rules (deny all by default)
109
116
  const NoRulePosts = BaseCollection.define("posts", {
110
117
  title: Field.text()
111
118
  });
112
119
  // 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'", {
120
+ const activePostsDef = ViewCollection.define("active_posts", "SELECT seqId, id, title, status, created, updated FROM posts_base WHERE status = 'active'", {
114
121
  seqId: Field.integer().view(),
115
122
  id: Field.text().view(),
116
123
  title: Field.text().view(),
117
124
  status: Field.text().view()
118
- }).rules((R) => ({
125
+ });
126
+ const activePostsRules = defineRule(activePostsDef, (R) => ({
119
127
  list: R.public(),
120
128
  view: R.public()
121
129
  }));
130
+ const ActivePosts = applyRulesToCollections([activePostsDef], [activePostsRules])[0];
122
131
  // Collection with error callbacks for integration testing
123
- const Scored = BaseCollection.define("scored", {
132
+ const scoredDef = BaseCollection.define("scored", {
124
133
  name: Field.text({
125
134
  minLength: 2,
126
135
  error: (kind) => Match.value(kind).pipe(Match.when("minLength", () => "Name too short"), Match.when("required", () => "Name is required"), Match.orElse(() => "Invalid name"))
@@ -130,16 +139,36 @@ const Scored = BaseCollection.define("scored", {
130
139
  max: 100,
131
140
  error: (kind) => Match.value(kind).pipe(Match.when("minimum", () => "Score below zero"), Match.when("maximum", () => "Score above 100"), Match.orElse(() => "Invalid score"))
132
141
  })
133
- }).rules((R) => ({
142
+ });
143
+ const scoredRules = defineRule(scoredDef, (R) => ({
144
+ create: R.public(),
145
+ update: R.public(),
146
+ list: R.public(),
147
+ view: R.public(),
148
+ delete: R.public()
149
+ }));
150
+ const Scored = applyRulesToCollections([scoredDef], [scoredRules])[0];
151
+ // Collection with a literalText field
152
+ const rolesDef = BaseCollection.define("roles", {
153
+ role: Field.literalText({ literal: ["admin", "agent", "readonly"] })
154
+ });
155
+ const rolesRules = defineRule(rolesDef, (R) => ({
134
156
  create: R.public(),
135
157
  update: R.public(),
136
158
  list: R.public(),
137
159
  view: R.public(),
138
160
  delete: R.public()
139
161
  }));
162
+ const Roles = applyRulesToCollections([rolesDef], [rolesRules])[0];
163
+ // PostsServiceLayer wires Posts into the collection service (registers `published` for boolean encode/decode)
164
+ const collectionServiceWithDeps = makeCollectionServiceLayer([Posts]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
165
+ const testLayer = Layer.mergeAll(collectionServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
140
166
  // ScoredServiceLayer wires Scored into the collection service
141
- const scoredServiceWithDeps = makeCollectionServiceLayer([Scored]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer));
167
+ const scoredServiceWithDeps = makeCollectionServiceLayer([Scored]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
142
168
  const scoredTestLayer = Layer.mergeAll(scoredServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
169
+ // RolesServiceLayer wires Roles into the collection service
170
+ const rolesServiceWithDeps = makeCollectionServiceLayer([Roles]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer));
171
+ const rolesTestLayer = Layer.mergeAll(rolesServiceWithDeps, sqliteLayer, FileStorageTest, NodeCryptoLayer);
143
172
  // ---------------------------------------------------------------------------
144
173
  // Helpers
145
174
  // ---------------------------------------------------------------------------
@@ -237,12 +266,12 @@ describe("list", () => {
237
266
  it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
238
267
  yield* setupPostsTable;
239
268
  const svc = yield* CollectionService;
240
- const result = yield* svc.list(NoRulePosts, null, 10, noCtx).pipe(Effect.either);
241
- expect(Either.isLeft(result)).toBe(true);
242
- if (Either.isLeft(result)) {
243
- expect(result.left._tag).toBe("ForbiddenError");
244
- if (result.left._tag === "ForbiddenError") {
245
- expect(result.left.action).toBe("list");
269
+ const result = yield* svc.list(NoRulePosts, null, 10, noCtx).pipe(Effect.result);
270
+ expect(Result.isFailure(result)).toBe(true);
271
+ if (Result.isFailure(result)) {
272
+ expect(result.failure._tag).toBe("ForbiddenError");
273
+ if (result.failure._tag === "ForbiddenError") {
274
+ expect(result.failure.action).toBe("list");
246
275
  }
247
276
  }
248
277
  }).pipe(Effect.provide(testLayer)));
@@ -408,10 +437,10 @@ describe("view", () => {
408
437
  it.effect("returns NotFoundError for an unknown id", () => Effect.gen(function* () {
409
438
  yield* setupPostsTable;
410
439
  const svc = yield* CollectionService;
411
- const result = yield* svc.view(Posts, "nonexistent123", noCtx).pipe(Effect.either);
412
- expect(Either.isLeft(result)).toBe(true);
413
- if (Either.isLeft(result)) {
414
- expect(result.left._tag).toBe("NotFoundError");
440
+ const result = yield* svc.view(Posts, "nonexistent123", noCtx).pipe(Effect.result);
441
+ expect(Result.isFailure(result)).toBe(true);
442
+ if (Result.isFailure(result)) {
443
+ expect(result.failure._tag).toBe("NotFoundError");
415
444
  }
416
445
  }).pipe(Effect.provide(testLayer)));
417
446
  it.effect("seqId is NOT in the returned row", () => Effect.gen(function* () {
@@ -431,19 +460,19 @@ describe("view", () => {
431
460
  const id = created["id"];
432
461
  if (typeof id !== "string")
433
462
  throw new Error("expected string id");
434
- const result = yield* svc.view(ProtectedPosts, id, noCtx).pipe(Effect.either);
435
- expect(Either.isLeft(result)).toBe(true);
436
- if (Either.isLeft(result)) {
437
- expect(result.left._tag).toBe("NotFoundError");
463
+ const result = yield* svc.view(ProtectedPosts, id, noCtx).pipe(Effect.result);
464
+ expect(Result.isFailure(result)).toBe(true);
465
+ if (Result.isFailure(result)) {
466
+ expect(result.failure._tag).toBe("NotFoundError");
438
467
  }
439
468
  }).pipe(Effect.provide(testLayer)));
440
469
  it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
441
470
  yield* setupPostsTable;
442
471
  const svc = yield* CollectionService;
443
- const result = yield* svc.view(NoRulePosts, "any", noCtx).pipe(Effect.either);
444
- expect(Either.isLeft(result)).toBe(true);
445
- if (Either.isLeft(result)) {
446
- expect(result.left._tag).toBe("ForbiddenError");
472
+ const result = yield* svc.view(NoRulePosts, "any", noCtx).pipe(Effect.result);
473
+ expect(Result.isFailure(result)).toBe(true);
474
+ if (Result.isFailure(result)) {
475
+ expect(result.failure._tag).toBe("ForbiddenError");
447
476
  }
448
477
  }).pipe(Effect.provide(testLayer)));
449
478
  });
@@ -466,46 +495,58 @@ describe("create", () => {
466
495
  const result = yield* svc.create(Posts, { title: "X" }, noCtx);
467
496
  expect("seqId" in result).toBe(false);
468
497
  }).pipe(Effect.provide(testLayer)));
498
+ it.effect("boolean field round-trips through SQLite encode/decode (true)", () => Effect.gen(function* () {
499
+ yield* setupPostsTable;
500
+ const svc = yield* CollectionService;
501
+ const result = yield* svc.create(Posts, { title: "Bool true", published: true }, noCtx);
502
+ expect(result["published"]).toBe(true);
503
+ }).pipe(Effect.provide(testLayer)));
504
+ it.effect("boolean field round-trips through SQLite encode/decode (false)", () => Effect.gen(function* () {
505
+ yield* setupPostsTable;
506
+ const svc = yield* CollectionService;
507
+ const result = yield* svc.create(Posts, { title: "Bool false", published: false }, noCtx);
508
+ expect(result["published"]).toBe(false);
509
+ }).pipe(Effect.provide(testLayer)));
469
510
  it.effect("ValidationError when required field is missing", () => Effect.gen(function* () {
470
511
  yield* setupPostsTable;
471
512
  const svc = yield* CollectionService;
472
- const result = yield* svc.create(Posts, { published: true }, noCtx).pipe(Effect.either);
473
- expect(Either.isLeft(result)).toBe(true);
474
- if (Either.isLeft(result)) {
475
- expect(result.left._tag).toBe("ValidationError");
476
- if (result.left._tag === "ValidationError") {
477
- expect(result.left.issues.some((i) => i.includes("title"))).toBe(true);
513
+ const result = yield* svc.create(Posts, { published: true }, noCtx).pipe(Effect.result);
514
+ expect(Result.isFailure(result)).toBe(true);
515
+ if (Result.isFailure(result)) {
516
+ expect(result.failure._tag).toBe("ValidationError");
517
+ if (result.failure._tag === "ValidationError") {
518
+ expect(result.failure.issues.some((i) => i.includes("title"))).toBe(true);
478
519
  }
479
520
  }
480
521
  }).pipe(Effect.provide(testLayer)));
481
522
  it.effect("ValidationError when x-system field is in input", () => Effect.gen(function* () {
482
523
  yield* setupPostsTable;
483
524
  const svc = yield* CollectionService;
484
- const result = yield* svc.create(Posts, { title: "X", id: "hacked" }, noCtx).pipe(Effect.either);
485
- expect(Either.isLeft(result)).toBe(true);
486
- if (Either.isLeft(result)) {
487
- expect(result.left._tag).toBe("ValidationError");
488
- if (result.left._tag === "ValidationError") {
489
- expect(result.left.issues.some((i) => i.includes("id"))).toBe(true);
525
+ const result = yield* svc.create(Posts, { title: "X", id: "hacked" }, noCtx).pipe(Effect.result);
526
+ expect(Result.isFailure(result)).toBe(true);
527
+ if (Result.isFailure(result)) {
528
+ expect(result.failure._tag).toBe("ValidationError");
529
+ if (result.failure._tag === "ValidationError") {
530
+ expect(result.failure.issues.some((i) => i.includes("id"))).toBe(true);
490
531
  }
491
532
  }
492
533
  }).pipe(Effect.provide(testLayer)));
493
534
  it.effect("ValidationError when seqId is in input", () => Effect.gen(function* () {
494
535
  yield* setupPostsTable;
495
536
  const svc = yield* CollectionService;
496
- const result = yield* svc.create(Posts, { title: "X", seqId: 99 }, noCtx).pipe(Effect.either);
497
- expect(Either.isLeft(result)).toBe(true);
498
- if (Either.isLeft(result)) {
499
- expect(result.left._tag).toBe("ValidationError");
537
+ const result = yield* svc.create(Posts, { title: "X", seqId: 99 }, noCtx).pipe(Effect.result);
538
+ expect(Result.isFailure(result)).toBe(true);
539
+ if (Result.isFailure(result)) {
540
+ expect(result.failure._tag).toBe("ValidationError");
500
541
  }
501
542
  }).pipe(Effect.provide(testLayer)));
502
543
  it.effect("ValidationError when type is wrong", () => Effect.gen(function* () {
503
544
  yield* setupPostsTable;
504
545
  const svc = yield* CollectionService;
505
- const result = yield* svc.create(Posts, { title: 42 }, noCtx).pipe(Effect.either);
506
- expect(Either.isLeft(result)).toBe(true);
507
- if (Either.isLeft(result)) {
508
- expect(result.left._tag).toBe("ValidationError");
546
+ const result = yield* svc.create(Posts, { title: 42 }, noCtx).pipe(Effect.result);
547
+ expect(Result.isFailure(result)).toBe(true);
548
+ if (Result.isFailure(result)) {
549
+ expect(result.failure._tag).toBe("ValidationError");
509
550
  }
510
551
  }).pipe(Effect.provide(testLayer)));
511
552
  it.effect("ValidationError when string exceeds maxLength", () => Effect.gen(function* () {
@@ -513,12 +554,12 @@ describe("create", () => {
513
554
  const svc = yield* CollectionService;
514
555
  const result = yield* svc
515
556
  .create(Posts, { title: "x".repeat(101) }, noCtx)
516
- .pipe(Effect.either);
517
- expect(Either.isLeft(result)).toBe(true);
518
- if (Either.isLeft(result)) {
519
- expect(result.left._tag).toBe("ValidationError");
520
- if (result.left._tag === "ValidationError") {
521
- expect(result.left.issues.some((i) => i.includes("100"))).toBe(true);
557
+ .pipe(Effect.result);
558
+ expect(Result.isFailure(result)).toBe(true);
559
+ if (Result.isFailure(result)) {
560
+ expect(result.failure._tag).toBe("ValidationError");
561
+ if (result.failure._tag === "ValidationError") {
562
+ expect(result.failure.issues.some((i) => i.includes("100"))).toBe(true);
522
563
  }
523
564
  }
524
565
  }).pipe(Effect.provide(testLayer)));
@@ -528,10 +569,10 @@ describe("create", () => {
528
569
  // ProtectedPosts requires authorId = "user1"; supply "user2" → rule fails
529
570
  const result = yield* svc
530
571
  .create(ProtectedPosts, { title: "X", authorId: "user2" }, noCtx)
531
- .pipe(Effect.either);
532
- expect(Either.isLeft(result)).toBe(true);
533
- if (Either.isLeft(result)) {
534
- expect(result.left._tag).toBe("ForbiddenError");
572
+ .pipe(Effect.result);
573
+ expect(Result.isFailure(result)).toBe(true);
574
+ if (Result.isFailure(result)) {
575
+ expect(result.failure._tag).toBe("ForbiddenError");
535
576
  }
536
577
  }).pipe(Effect.provide(testLayer)));
537
578
  it.effect("create succeeds when rule pre-check passes", () => Effect.gen(function* () {
@@ -543,10 +584,10 @@ describe("create", () => {
543
584
  it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
544
585
  yield* setupPostsTable;
545
586
  const svc = yield* CollectionService;
546
- const result = yield* svc.create(NoRulePosts, { title: "X" }, noCtx).pipe(Effect.either);
547
- expect(Either.isLeft(result)).toBe(true);
548
- if (Either.isLeft(result)) {
549
- expect(result.left._tag).toBe("ForbiddenError");
587
+ const result = yield* svc.create(NoRulePosts, { title: "X" }, noCtx).pipe(Effect.result);
588
+ expect(Result.isFailure(result)).toBe(true);
589
+ if (Result.isFailure(result)) {
590
+ expect(result.failure._tag).toBe("ForbiddenError");
550
591
  }
551
592
  }).pipe(Effect.provide(testLayer)));
552
593
  it.effect("view collection → ReadOnlyError", () => Effect.gen(function* () {
@@ -554,10 +595,10 @@ describe("create", () => {
554
595
  const svc = yield* CollectionService;
555
596
  const result = yield* svc
556
597
  .create(ActivePosts, { id: "x", title: "X", status: "active" }, noCtx)
557
- .pipe(Effect.either);
558
- expect(Either.isLeft(result)).toBe(true);
559
- if (Either.isLeft(result)) {
560
- expect(result.left._tag).toBe("ReadOnlyError");
598
+ .pipe(Effect.result);
599
+ expect(Result.isFailure(result)).toBe(true);
600
+ if (Result.isFailure(result)) {
601
+ expect(result.failure._tag).toBe("ReadOnlyError");
561
602
  }
562
603
  }).pipe(Effect.provide(testLayer)));
563
604
  });
@@ -576,6 +617,16 @@ describe("update", () => {
576
617
  expect(updated["title"]).toBe("New");
577
618
  expect(updated["authorId"]).toBe("user1");
578
619
  }).pipe(Effect.provide(testLayer)));
620
+ it.effect("toggling a boolean field updates and round-trips correctly", () => Effect.gen(function* () {
621
+ yield* setupPostsTable;
622
+ const svc = yield* CollectionService;
623
+ const created = yield* svc.create(Posts, { title: "Toggle", published: false }, noCtx);
624
+ const id = created["id"];
625
+ if (typeof id !== "string")
626
+ throw new Error("expected string id");
627
+ const updated = yield* svc.update(Posts, id, { published: true }, noCtx);
628
+ expect(updated["published"]).toBe(true);
629
+ }).pipe(Effect.provide(testLayer)));
579
630
  it.live("updated timestamp is refreshed; created is unchanged", () => Effect.gen(function* () {
580
631
  yield* setupPostsTable;
581
632
  const svc = yield* CollectionService;
@@ -597,10 +648,10 @@ describe("update", () => {
597
648
  throw new Error("expected string id");
598
649
  const result = yield* svc
599
650
  .update(Posts, id, { title: "Y", id: "hacked", created: "1970" }, noCtx)
600
- .pipe(Effect.either);
601
- expect(Either.isLeft(result)).toBe(true);
602
- if (Either.isLeft(result)) {
603
- expect(result.left._tag).toBe("ValidationError");
651
+ .pipe(Effect.result);
652
+ expect(Result.isFailure(result)).toBe(true);
653
+ if (Result.isFailure(result)) {
654
+ expect(result.failure._tag).toBe("ValidationError");
604
655
  }
605
656
  }).pipe(Effect.provide(testLayer)));
606
657
  it.effect("NotFoundError for unknown id", () => Effect.gen(function* () {
@@ -608,10 +659,10 @@ describe("update", () => {
608
659
  const svc = yield* CollectionService;
609
660
  const result = yield* svc
610
661
  .update(Posts, "nosuchid1234567", { title: "X" }, noCtx)
611
- .pipe(Effect.either);
612
- expect(Either.isLeft(result)).toBe(true);
613
- if (Either.isLeft(result)) {
614
- expect(result.left._tag).toBe("NotFoundError");
662
+ .pipe(Effect.result);
663
+ expect(Result.isFailure(result)).toBe(true);
664
+ if (Result.isFailure(result)) {
665
+ expect(result.failure._tag).toBe("NotFoundError");
615
666
  }
616
667
  }).pipe(Effect.provide(testLayer)));
617
668
  it.effect("ForbiddenError when rule blocks update", () => Effect.gen(function* () {
@@ -624,12 +675,12 @@ describe("update", () => {
624
675
  throw new Error("expected string id");
625
676
  const result = yield* svc
626
677
  .update(ProtectedPosts, id, { title: "Y" }, noCtx)
627
- .pipe(Effect.either);
628
- expect(Either.isLeft(result)).toBe(true);
629
- if (Either.isLeft(result)) {
630
- expect(result.left._tag).toBe("ForbiddenError");
631
- if (result.left._tag === "ForbiddenError") {
632
- expect(result.left.action).toBe("update");
678
+ .pipe(Effect.result);
679
+ expect(Result.isFailure(result)).toBe(true);
680
+ if (Result.isFailure(result)) {
681
+ expect(result.failure._tag).toBe("ForbiddenError");
682
+ if (result.failure._tag === "ForbiddenError") {
683
+ expect(result.failure.action).toBe("update");
633
684
  }
634
685
  }
635
686
  }).pipe(Effect.provide(testLayer)));
@@ -638,10 +689,10 @@ describe("update", () => {
638
689
  const svc = yield* CollectionService;
639
690
  const result = yield* svc
640
691
  .update(NoRulePosts, "anyid", { title: "X" }, noCtx)
641
- .pipe(Effect.either);
642
- expect(Either.isLeft(result)).toBe(true);
643
- if (Either.isLeft(result)) {
644
- expect(result.left._tag).toBe("ForbiddenError");
692
+ .pipe(Effect.result);
693
+ expect(Result.isFailure(result)).toBe(true);
694
+ if (Result.isFailure(result)) {
695
+ expect(result.failure._tag).toBe("ForbiddenError");
645
696
  }
646
697
  }).pipe(Effect.provide(testLayer)));
647
698
  it.effect("view collection → ReadOnlyError", () => Effect.gen(function* () {
@@ -649,10 +700,10 @@ describe("update", () => {
649
700
  const svc = yield* CollectionService;
650
701
  const result = yield* svc
651
702
  .update(ActivePosts, "anyid", { title: "X" }, noCtx)
652
- .pipe(Effect.either);
653
- expect(Either.isLeft(result)).toBe(true);
654
- if (Either.isLeft(result)) {
655
- expect(result.left._tag).toBe("ReadOnlyError");
703
+ .pipe(Effect.result);
704
+ expect(Result.isFailure(result)).toBe(true);
705
+ if (Result.isFailure(result)) {
706
+ expect(result.failure._tag).toBe("ReadOnlyError");
656
707
  }
657
708
  }).pipe(Effect.provide(testLayer)));
658
709
  });
@@ -677,19 +728,19 @@ describe("delete", () => {
677
728
  if (typeof id !== "string")
678
729
  throw new Error("expected string id");
679
730
  yield* svc.delete(Posts, id, noCtx);
680
- const result = yield* svc.view(Posts, id, noCtx).pipe(Effect.either);
681
- expect(Either.isLeft(result)).toBe(true);
682
- if (Either.isLeft(result)) {
683
- expect(result.left._tag).toBe("NotFoundError");
731
+ const result = yield* svc.view(Posts, id, noCtx).pipe(Effect.result);
732
+ expect(Result.isFailure(result)).toBe(true);
733
+ if (Result.isFailure(result)) {
734
+ expect(result.failure._tag).toBe("NotFoundError");
684
735
  }
685
736
  }).pipe(Effect.provide(testLayer)));
686
737
  it.effect("NotFoundError for unknown id", () => Effect.gen(function* () {
687
738
  yield* setupPostsTable;
688
739
  const svc = yield* CollectionService;
689
- const result = yield* svc.delete(Posts, "nosuchid1234567", noCtx).pipe(Effect.either);
690
- expect(Either.isLeft(result)).toBe(true);
691
- if (Either.isLeft(result)) {
692
- expect(result.left._tag).toBe("NotFoundError");
740
+ const result = yield* svc.delete(Posts, "nosuchid1234567", noCtx).pipe(Effect.result);
741
+ expect(Result.isFailure(result)).toBe(true);
742
+ if (Result.isFailure(result)) {
743
+ expect(result.failure._tag).toBe("NotFoundError");
693
744
  }
694
745
  }).pipe(Effect.provide(testLayer)));
695
746
  it.effect("ForbiddenError when rule blocks delete", () => Effect.gen(function* () {
@@ -701,31 +752,31 @@ describe("delete", () => {
701
752
  throw new Error("expected string id");
702
753
  const result = yield* svc
703
754
  .delete(ProtectedPosts, id, noCtx)
704
- .pipe(Effect.either);
705
- expect(Either.isLeft(result)).toBe(true);
706
- if (Either.isLeft(result)) {
707
- expect(result.left._tag).toBe("ForbiddenError");
708
- if (result.left._tag === "ForbiddenError") {
709
- expect(result.left.action).toBe("delete");
755
+ .pipe(Effect.result);
756
+ expect(Result.isFailure(result)).toBe(true);
757
+ if (Result.isFailure(result)) {
758
+ expect(result.failure._tag).toBe("ForbiddenError");
759
+ if (result.failure._tag === "ForbiddenError") {
760
+ expect(result.failure.action).toBe("delete");
710
761
  }
711
762
  }
712
763
  }).pipe(Effect.provide(testLayer)));
713
764
  it.effect("no rules → ForbiddenError", () => Effect.gen(function* () {
714
765
  yield* setupPostsTable;
715
766
  const svc = yield* CollectionService;
716
- const result = yield* svc.delete(NoRulePosts, "anyid", noCtx).pipe(Effect.either);
717
- expect(Either.isLeft(result)).toBe(true);
718
- if (Either.isLeft(result)) {
719
- expect(result.left._tag).toBe("ForbiddenError");
767
+ const result = yield* svc.delete(NoRulePosts, "anyid", noCtx).pipe(Effect.result);
768
+ expect(Result.isFailure(result)).toBe(true);
769
+ if (Result.isFailure(result)) {
770
+ expect(result.failure._tag).toBe("ForbiddenError");
720
771
  }
721
772
  }).pipe(Effect.provide(testLayer)));
722
773
  it.effect("view collection → ReadOnlyError", () => Effect.gen(function* () {
723
774
  yield* setupViewTables;
724
775
  const svc = yield* CollectionService;
725
- const result = yield* svc.delete(ActivePosts, "anyid", noCtx).pipe(Effect.either);
726
- expect(Either.isLeft(result)).toBe(true);
727
- if (Either.isLeft(result)) {
728
- expect(result.left._tag).toBe("ReadOnlyError");
776
+ const result = yield* svc.delete(ActivePosts, "anyid", noCtx).pipe(Effect.result);
777
+ expect(Result.isFailure(result)).toBe(true);
778
+ if (Result.isFailure(result)) {
779
+ expect(result.failure._tag).toBe("ReadOnlyError");
729
780
  }
730
781
  }).pipe(Effect.provide(testLayer)));
731
782
  });
@@ -736,46 +787,46 @@ describe("error callback — integration", () => {
736
787
  it.effect("minLength message propagates from create", () => Effect.gen(function* () {
737
788
  yield* setupScoredTable;
738
789
  const svc = yield* CollectionService;
739
- const result = yield* svc.create(Scored, { name: "x", score: 50 }, noCtx).pipe(Effect.either);
740
- expect(Either.isLeft(result)).toBe(true);
741
- if (Either.isLeft(result) && result.left._tag === "ValidationError") {
742
- expect(result.left.issues.some((i) => i.includes("Name too short"))).toBe(true);
790
+ const result = yield* svc.create(Scored, { name: "x", score: 50 }, noCtx).pipe(Effect.result);
791
+ expect(Result.isFailure(result)).toBe(true);
792
+ if (Result.isFailure(result) && result.failure._tag === "ValidationError") {
793
+ expect(result.failure.issues.some((i) => i.includes("Name too short"))).toBe(true);
743
794
  }
744
795
  }).pipe(Effect.provide(scoredTestLayer)));
745
796
  it.effect("minimum message propagates from create", () => Effect.gen(function* () {
746
797
  yield* setupScoredTable;
747
798
  const svc = yield* CollectionService;
748
- const result = yield* svc.create(Scored, { name: "ok", score: -1 }, noCtx).pipe(Effect.either);
749
- expect(Either.isLeft(result)).toBe(true);
750
- if (Either.isLeft(result) && result.left._tag === "ValidationError") {
751
- expect(result.left.issues.some((i) => i.includes("Score below zero"))).toBe(true);
799
+ const result = yield* svc.create(Scored, { name: "ok", score: -1 }, noCtx).pipe(Effect.result);
800
+ expect(Result.isFailure(result)).toBe(true);
801
+ if (Result.isFailure(result) && result.failure._tag === "ValidationError") {
802
+ expect(result.failure.issues.some((i) => i.includes("Score below zero"))).toBe(true);
752
803
  }
753
804
  }).pipe(Effect.provide(scoredTestLayer)));
754
805
  it.effect("maximum message propagates from create", () => Effect.gen(function* () {
755
806
  yield* setupScoredTable;
756
807
  const svc = yield* CollectionService;
757
- const result = yield* svc.create(Scored, { name: "ok", score: 200 }, noCtx).pipe(Effect.either);
758
- expect(Either.isLeft(result)).toBe(true);
759
- if (Either.isLeft(result) && result.left._tag === "ValidationError") {
760
- expect(result.left.issues.some((i) => i.includes("Score above 100"))).toBe(true);
808
+ const result = yield* svc.create(Scored, { name: "ok", score: 200 }, noCtx).pipe(Effect.result);
809
+ expect(Result.isFailure(result)).toBe(true);
810
+ if (Result.isFailure(result) && result.failure._tag === "ValidationError") {
811
+ expect(result.failure.issues.some((i) => i.includes("Score above 100"))).toBe(true);
761
812
  }
762
813
  }).pipe(Effect.provide(scoredTestLayer)));
763
814
  it.effect("required message propagates from create", () => Effect.gen(function* () {
764
815
  yield* setupScoredTable;
765
816
  const svc = yield* CollectionService;
766
- const result = yield* svc.create(Scored, { score: 50 }, noCtx).pipe(Effect.either);
767
- expect(Either.isLeft(result)).toBe(true);
768
- if (Either.isLeft(result) && result.left._tag === "ValidationError") {
769
- expect(result.left.issues.some((i) => i.includes("Name is required"))).toBe(true);
817
+ const result = yield* svc.create(Scored, { score: 50 }, noCtx).pipe(Effect.result);
818
+ expect(Result.isFailure(result)).toBe(true);
819
+ if (Result.isFailure(result) && result.failure._tag === "ValidationError") {
820
+ expect(result.failure.issues.some((i) => i.includes("Name is required"))).toBe(true);
770
821
  }
771
822
  }).pipe(Effect.provide(scoredTestLayer)));
772
823
  it.effect("Match.orElse fallback fires for type mismatch", () => Effect.gen(function* () {
773
824
  yield* setupScoredTable;
774
825
  const svc = yield* CollectionService;
775
- const result = yield* svc.create(Scored, { name: 999, score: 50 }, noCtx).pipe(Effect.either);
776
- expect(Either.isLeft(result)).toBe(true);
777
- if (Either.isLeft(result) && result.left._tag === "ValidationError") {
778
- expect(result.left.issues.some((i) => i.includes("Invalid name"))).toBe(true);
826
+ const result = yield* svc.create(Scored, { name: 999, score: 50 }, noCtx).pipe(Effect.result);
827
+ expect(Result.isFailure(result)).toBe(true);
828
+ if (Result.isFailure(result) && result.failure._tag === "ValidationError") {
829
+ expect(result.failure.issues.some((i) => i.includes("Invalid name"))).toBe(true);
779
830
  }
780
831
  }).pipe(Effect.provide(scoredTestLayer)));
781
832
  it.effect("update with too-short name produces default message (no custom error on update path)", () => Effect.gen(function* () {
@@ -785,12 +836,12 @@ describe("error callback — integration", () => {
785
836
  const id = created["id"];
786
837
  if (typeof id !== "string")
787
838
  throw new Error("expected string id");
788
- const result = yield* svc.update(Scored, id, { name: "x" }, noCtx).pipe(Effect.either);
789
- expect(Either.isLeft(result)).toBe(true);
790
- if (Either.isLeft(result)) {
791
- expect(result.left._tag).toBe("ValidationError");
792
- if (result.left._tag === "ValidationError") {
793
- expect(result.left.issues.length).toBeGreaterThan(0);
839
+ const result = yield* svc.update(Scored, id, { name: "x" }, noCtx).pipe(Effect.result);
840
+ expect(Result.isFailure(result)).toBe(true);
841
+ if (Result.isFailure(result)) {
842
+ expect(result.failure._tag).toBe("ValidationError");
843
+ if (result.failure._tag === "ValidationError") {
844
+ expect(result.failure.issues.length).toBeGreaterThan(0);
794
845
  }
795
846
  }
796
847
  }).pipe(Effect.provide(scoredTestLayer)));
@@ -802,4 +853,153 @@ describe("error callback — integration", () => {
802
853
  expect(result["score"]).toBe(50);
803
854
  }).pipe(Effect.provide(scoredTestLayer)));
804
855
  });
856
+ // ---------------------------------------------------------------------------
857
+ // literalText integration tests
858
+ // ---------------------------------------------------------------------------
859
+ const setupRolesTable = Effect.gen(function* () {
860
+ const sql = yield* SqlClient.SqlClient;
861
+ yield* sql.unsafe(`
862
+ CREATE TABLE IF NOT EXISTS "roles" (
863
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
864
+ "id" TEXT NOT NULL UNIQUE,
865
+ "role" TEXT NOT NULL,
866
+ "created" TEXT NOT NULL,
867
+ "updated" TEXT NOT NULL
868
+ )
869
+ `);
870
+ yield* sql.unsafe(`DELETE FROM "roles"`);
871
+ });
872
+ describe("literalText integration", () => {
873
+ it.effect("create with valid literal value succeeds", () => Effect.gen(function* () {
874
+ yield* setupRolesTable;
875
+ const svc = yield* CollectionService;
876
+ const result = yield* svc.create(Roles, { role: "admin" }, noCtx);
877
+ expect(result["role"]).toBe("admin");
878
+ }).pipe(Effect.provide(rolesTestLayer)));
879
+ it.effect("create with invalid literal value fails with ValidationError", () => Effect.gen(function* () {
880
+ yield* setupRolesTable;
881
+ const svc = yield* CollectionService;
882
+ const result = yield* svc.create(Roles, { role: "superadmin" }, noCtx).pipe(Effect.result);
883
+ expect(Result.isFailure(result)).toBe(true);
884
+ if (Result.isFailure(result)) {
885
+ expect(result.failure._tag).toBe("ValidationError");
886
+ }
887
+ }).pipe(Effect.provide(rolesTestLayer)));
888
+ it.effect("update with invalid literal value fails with ValidationError", () => Effect.gen(function* () {
889
+ yield* setupRolesTable;
890
+ const svc = yield* CollectionService;
891
+ const created = yield* svc.create(Roles, { role: "admin" }, noCtx);
892
+ const id = created["id"];
893
+ if (typeof id !== "string")
894
+ throw new Error("expected string id");
895
+ const result = yield* svc.update(Roles, id, { role: "superadmin" }, noCtx).pipe(Effect.result);
896
+ expect(Result.isFailure(result)).toBe(true);
897
+ if (Result.isFailure(result)) {
898
+ expect(result.failure._tag).toBe("ValidationError");
899
+ }
900
+ }).pipe(Effect.provide(rolesTestLayer)));
901
+ it.effect("update with valid literal value succeeds", () => Effect.gen(function* () {
902
+ yield* setupRolesTable;
903
+ const svc = yield* CollectionService;
904
+ const created = yield* svc.create(Roles, { role: "admin" }, noCtx);
905
+ const id = created["id"];
906
+ if (typeof id !== "string")
907
+ throw new Error("expected string id");
908
+ const updated = yield* svc.update(Roles, id, { role: "agent" }, noCtx);
909
+ expect(updated["role"]).toBe("agent");
910
+ }).pipe(Effect.provide(rolesTestLayer)));
911
+ });
912
+ // ---------------------------------------------------------------------------
913
+ // AuthCollection create — regression: email/password were blocked by x-system
914
+ // ---------------------------------------------------------------------------
915
+ const superAdminDef = AuthCollection.define("_superadmin", {});
916
+ const superAdminRules = defineRule(superAdminDef, (R) => ({
917
+ list: R.field("email").eq(R.literal("")),
918
+ view: R.field("email").eq(R.literal("")),
919
+ create: R.public(),
920
+ update: R.field("email").eq(R.literal("")),
921
+ delete: R.field("email").eq(R.literal(""))
922
+ }));
923
+ const SuperAdmin = applyRulesToCollections([superAdminDef], [superAdminRules])[0];
924
+ const setupSuperAdminTable = Effect.gen(function* () {
925
+ const sql = yield* SqlClient.SqlClient;
926
+ yield* sql.unsafe(`
927
+ CREATE TABLE IF NOT EXISTS "_superadmin" (
928
+ "seqId" INTEGER PRIMARY KEY AUTOINCREMENT,
929
+ "id" TEXT NOT NULL UNIQUE,
930
+ "email" TEXT NOT NULL UNIQUE,
931
+ "password" TEXT NOT NULL,
932
+ "emailVerified" INTEGER NOT NULL DEFAULT 0,
933
+ "created" TEXT NOT NULL,
934
+ "updated" TEXT NOT NULL
935
+ )
936
+ `);
937
+ yield* sql.unsafe(`DELETE FROM "_superadmin"`);
938
+ });
939
+ const superAdminTestLayer = Layer.mergeAll(makeCollectionServiceLayer([SuperAdmin]).pipe(Layer.provide(RepositoryLive), Layer.provide(sqliteLayer), Layer.provide(FileStorageTest), Layer.provide(NodeCryptoLayer), Layer.provide(DialectTest), Layer.provide(NodeAuthServiceLayer)), sqliteLayer, FileStorageTest, NodeCryptoLayer, NodeAuthServiceLayer);
940
+ const adminCtx = { headers: {}, query: {}, admin: true };
941
+ describe("AuthCollection create", () => {
942
+ it.effect("succeeds with email and password (regression: was blocked by x-system check)", () => Effect.gen(function* () {
943
+ yield* setupSuperAdminTable;
944
+ const svc = yield* CollectionService;
945
+ const record = yield* svc.create(SuperAdmin, { email: "admin@example.com", password: "hashed-pw" }, adminCtx);
946
+ expect(record["email"]).toBe("admin@example.com");
947
+ expect(typeof record["id"]).toBe("string");
948
+ expect(record["id"]).not.toBe("");
949
+ }).pipe(Effect.provide(superAdminTestLayer)));
950
+ it.effect("rejects attempt to set generated field id", () => Effect.gen(function* () {
951
+ yield* setupSuperAdminTable;
952
+ const svc = yield* CollectionService;
953
+ const result = yield* svc.create(SuperAdmin, { email: "a@b.com", password: "pw", id: "custom-id" }, adminCtx).pipe(Effect.result);
954
+ expect(Result.isFailure(result)).toBe(true);
955
+ if (Result.isFailure(result)) {
956
+ expect(result.failure._tag).toBe("ValidationError");
957
+ }
958
+ }).pipe(Effect.provide(superAdminTestLayer)));
959
+ it.effect("fails validation when email is missing", () => Effect.gen(function* () {
960
+ yield* setupSuperAdminTable;
961
+ const svc = yield* CollectionService;
962
+ const result = yield* svc.create(SuperAdmin, { password: "pw" }, adminCtx).pipe(Effect.result);
963
+ expect(Result.isFailure(result)).toBe(true);
964
+ if (Result.isFailure(result)) {
965
+ expect(result.failure._tag).toBe("ValidationError");
966
+ }
967
+ }).pipe(Effect.provide(superAdminTestLayer)));
968
+ it.effect("fails validation when email format is invalid", () => Effect.gen(function* () {
969
+ yield* setupSuperAdminTable;
970
+ const svc = yield* CollectionService;
971
+ const result = yield* svc.create(SuperAdmin, { email: "notanemail", password: "pw" }, adminCtx).pipe(Effect.result);
972
+ expect(Result.isFailure(result)).toBe(true);
973
+ if (Result.isFailure(result)) {
974
+ expect(result.failure._tag).toBe("ValidationError");
975
+ }
976
+ }).pipe(Effect.provide(superAdminTestLayer)));
977
+ it.effect("hashes the password on create — the stored value is not the plaintext", () => Effect.gen(function* () {
978
+ yield* setupSuperAdminTable;
979
+ const svc = yield* CollectionService;
980
+ const record = yield* svc.create(SuperAdmin, { email: "hash@example.com", password: "plaintext-pw" }, adminCtx);
981
+ const stored = record["password"];
982
+ expect(typeof stored).toBe("string");
983
+ expect(stored).not.toBe("plaintext-pw");
984
+ expect(String(stored).startsWith("scrypt$")).toBe(true);
985
+ }).pipe(Effect.provide(superAdminTestLayer)));
986
+ it.effect("hashes a new password on update", () => Effect.gen(function* () {
987
+ yield* setupSuperAdminTable;
988
+ const svc = yield* CollectionService;
989
+ const created = yield* svc.create(SuperAdmin, { email: "upd@example.com", password: "first-pw" }, adminCtx);
990
+ const id = created["id"];
991
+ const updated = yield* svc.update(SuperAdmin, id, { password: "second-pw" }, adminCtx);
992
+ expect(updated["password"]).not.toBe("second-pw");
993
+ expect(String(updated["password"]).startsWith("scrypt$")).toBe(true);
994
+ }).pipe(Effect.provide(superAdminTestLayer)));
995
+ it.effect("empty password on update is dropped and keeps the existing hash", () => Effect.gen(function* () {
996
+ yield* setupSuperAdminTable;
997
+ const svc = yield* CollectionService;
998
+ const created = yield* svc.create(SuperAdmin, { email: "keep@example.com", password: "keep-pw" }, adminCtx);
999
+ const id = created["id"];
1000
+ const before = created["password"];
1001
+ const updated = yield* svc.update(SuperAdmin, id, { password: "" }, adminCtx);
1002
+ expect(updated["password"]).toBe(before);
1003
+ }).pipe(Effect.provide(superAdminTestLayer)));
1004
+ });
805
1005
  //# sourceMappingURL=collection-service.test.js.map