effect-app 4.0.0-beta.27 → 4.0.0-beta.272

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 (342) hide show
  1. package/CHANGELOG.md +1275 -0
  2. package/dist/Array.d.ts +2 -1
  3. package/dist/Array.d.ts.map +1 -1
  4. package/dist/Array.js +4 -4
  5. package/dist/Chunk.d.ts.map +1 -1
  6. package/dist/Config/SecretURL.d.ts +3 -1
  7. package/dist/Config/SecretURL.d.ts.map +1 -1
  8. package/dist/Config/SecretURL.js +3 -6
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config/internal/configSecretURL.js +2 -2
  11. package/dist/Config.d.ts +2 -0
  12. package/dist/Config.d.ts.map +1 -0
  13. package/dist/Config.js +8 -0
  14. package/dist/ConfigProvider.d.ts +2 -0
  15. package/dist/ConfigProvider.d.ts.map +1 -0
  16. package/dist/ConfigProvider.js +6 -0
  17. package/dist/{ServiceMap.d.ts → Context.d.ts} +18 -20
  18. package/dist/Context.d.ts.map +1 -0
  19. package/dist/Context.js +67 -0
  20. package/dist/Effect.d.ts +10 -9
  21. package/dist/Effect.d.ts.map +1 -1
  22. package/dist/Effect.js +5 -8
  23. package/dist/Emailer.d.ts +51 -0
  24. package/dist/Emailer.d.ts.map +1 -0
  25. package/dist/Emailer.js +7 -0
  26. package/dist/Function.d.ts.map +1 -1
  27. package/dist/Layer.d.ts +10 -6
  28. package/dist/Layer.d.ts.map +1 -1
  29. package/dist/Layer.js +3 -2
  30. package/dist/Model/Repository/Registry.d.ts +22 -0
  31. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  32. package/dist/Model/Repository/Registry.js +18 -0
  33. package/dist/Model/Repository/ext.d.ts +60 -0
  34. package/dist/Model/Repository/ext.d.ts.map +1 -0
  35. package/dist/Model/Repository/ext.js +122 -0
  36. package/dist/Model/Repository/internal/internal.d.ts +63 -0
  37. package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
  38. package/dist/Model/Repository/internal/internal.js +430 -0
  39. package/dist/Model/Repository/legacy.d.ts +21 -0
  40. package/dist/Model/Repository/legacy.d.ts.map +1 -0
  41. package/dist/Model/Repository/legacy.js +2 -0
  42. package/dist/Model/Repository/makeRepo.d.ts +54 -0
  43. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  44. package/dist/Model/Repository/makeRepo.js +27 -0
  45. package/dist/Model/Repository/service.d.ts +121 -0
  46. package/dist/Model/Repository/service.d.ts.map +1 -0
  47. package/dist/Model/Repository/service.js +2 -0
  48. package/dist/Model/Repository/validation.d.ts +58 -0
  49. package/dist/Model/Repository/validation.d.ts.map +1 -0
  50. package/dist/Model/Repository/validation.js +32 -0
  51. package/dist/Model/Repository.d.ts +7 -0
  52. package/dist/Model/Repository.d.ts.map +1 -0
  53. package/dist/Model/Repository.js +7 -0
  54. package/dist/Model/dsl.d.ts +33 -0
  55. package/dist/Model/dsl.d.ts.map +1 -0
  56. package/dist/Model/dsl.js +43 -0
  57. package/dist/Model/filter/filterApi.d.ts +30 -0
  58. package/dist/Model/filter/filterApi.d.ts.map +1 -0
  59. package/dist/Model/filter/filterApi.js +2 -0
  60. package/dist/Model/filter/types/errors.d.ts +29 -0
  61. package/dist/Model/filter/types/errors.d.ts.map +1 -0
  62. package/dist/Model/filter/types/errors.js +2 -0
  63. package/dist/Model/filter/types/fields.d.ts +15 -0
  64. package/dist/Model/filter/types/fields.d.ts.map +1 -0
  65. package/dist/Model/filter/types/fields.js +2 -0
  66. package/dist/Model/filter/types/path/common.d.ts +316 -0
  67. package/dist/Model/filter/types/path/common.d.ts.map +1 -0
  68. package/dist/Model/filter/types/path/common.js +2 -0
  69. package/dist/Model/filter/types/path/eager.d.ts +94 -0
  70. package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
  71. package/dist/Model/filter/types/path/eager.js +36 -0
  72. package/dist/Model/filter/types/path/index.d.ts +4 -0
  73. package/dist/Model/filter/types/path/index.d.ts.map +1 -0
  74. package/dist/Model/filter/types/path/index.js +3 -0
  75. package/dist/Model/filter/types/utils.d.ts +79 -0
  76. package/dist/Model/filter/types/utils.d.ts.map +1 -0
  77. package/dist/Model/filter/types/utils.js +2 -0
  78. package/dist/Model/filter/types/validator.d.ts +30 -0
  79. package/dist/Model/filter/types/validator.d.ts.map +1 -0
  80. package/dist/Model/filter/types/validator.js +2 -0
  81. package/dist/Model/filter/types.d.ts +5 -0
  82. package/dist/Model/filter/types.d.ts.map +1 -0
  83. package/dist/Model/filter/types.js +7 -0
  84. package/dist/Model/query/dsl.d.ts +493 -0
  85. package/dist/Model/query/dsl.d.ts.map +1 -0
  86. package/dist/Model/query/dsl.js +376 -0
  87. package/dist/Model/query/new-kid-interpreter.d.ts +136 -0
  88. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
  89. package/dist/Model/query/new-kid-interpreter.js +336 -0
  90. package/dist/Model/query.d.ts +15 -0
  91. package/dist/Model/query.d.ts.map +1 -0
  92. package/dist/Model/query.js +3 -0
  93. package/dist/Model.d.ts +5 -0
  94. package/dist/Model.d.ts.map +1 -0
  95. package/dist/Model.js +5 -0
  96. package/dist/NonEmptySet.d.ts +3 -1
  97. package/dist/NonEmptySet.d.ts.map +1 -1
  98. package/dist/NonEmptySet.js +2 -2
  99. package/dist/Option.d.ts +1 -0
  100. package/dist/Option.d.ts.map +1 -1
  101. package/dist/Option.js +3 -1
  102. package/dist/Pure.d.ts +7 -5
  103. package/dist/Pure.d.ts.map +1 -1
  104. package/dist/Pure.js +17 -14
  105. package/dist/QueueMaker.d.ts +13 -0
  106. package/dist/QueueMaker.d.ts.map +1 -0
  107. package/dist/QueueMaker.js +4 -0
  108. package/dist/RequestContext.d.ts +154 -0
  109. package/dist/RequestContext.d.ts.map +1 -0
  110. package/dist/RequestContext.js +54 -0
  111. package/dist/Schema/Class.d.ts +160 -19
  112. package/dist/Schema/Class.d.ts.map +1 -1
  113. package/dist/Schema/Class.js +260 -17
  114. package/dist/Schema/FastCheck.d.ts.map +1 -1
  115. package/dist/Schema/SchemaParser.d.ts +5 -0
  116. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  117. package/dist/Schema/SchemaParser.js +6 -0
  118. package/dist/Schema/SpecialJsonSchema.d.ts +34 -0
  119. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  120. package/dist/Schema/SpecialJsonSchema.js +118 -0
  121. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  122. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  123. package/dist/Schema/SpecialOpenApi.js +123 -0
  124. package/dist/Schema/brand.d.ts +4 -2
  125. package/dist/Schema/brand.d.ts.map +1 -1
  126. package/dist/Schema/brand.js +3 -1
  127. package/dist/Schema/email.d.ts.map +1 -1
  128. package/dist/Schema/email.js +7 -4
  129. package/dist/Schema/ext.d.ts +338 -55
  130. package/dist/Schema/ext.d.ts.map +1 -1
  131. package/dist/Schema/ext.js +358 -53
  132. package/dist/Schema/moreStrings.d.ts +82 -36
  133. package/dist/Schema/moreStrings.d.ts.map +1 -1
  134. package/dist/Schema/moreStrings.js +49 -42
  135. package/dist/Schema/numbers.d.ts +34 -21
  136. package/dist/Schema/numbers.d.ts.map +1 -1
  137. package/dist/Schema/numbers.js +55 -12
  138. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  139. package/dist/Schema/phoneNumber.js +6 -3
  140. package/dist/Schema/strings.d.ts +18 -4
  141. package/dist/Schema/strings.d.ts.map +1 -1
  142. package/dist/Schema/strings.js +1 -5
  143. package/dist/Schema.d.ts +213 -7
  144. package/dist/Schema.d.ts.map +1 -1
  145. package/dist/Schema.js +190 -11
  146. package/dist/Set.d.ts +4 -1
  147. package/dist/Set.d.ts.map +1 -1
  148. package/dist/Set.js +3 -2
  149. package/dist/Store.d.ts +170 -0
  150. package/dist/Store.d.ts.map +1 -0
  151. package/dist/Store.js +121 -0
  152. package/dist/_ext/Array.d.ts +1 -1
  153. package/dist/_ext/Array.d.ts.map +1 -1
  154. package/dist/_ext/Array.js +4 -2
  155. package/dist/_ext/misc.d.ts +4 -1
  156. package/dist/_ext/misc.d.ts.map +1 -1
  157. package/dist/_ext/misc.js +4 -2
  158. package/dist/_ext/ord.ext.d.ts +2 -1
  159. package/dist/_ext/ord.ext.d.ts.map +1 -1
  160. package/dist/_ext/ord.ext.js +2 -2
  161. package/dist/client/InvalidationKeys.d.ts +29 -0
  162. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  163. package/dist/client/InvalidationKeys.js +33 -0
  164. package/dist/client/apiClientFactory.d.ts +19 -31
  165. package/dist/client/apiClientFactory.d.ts.map +1 -1
  166. package/dist/client/apiClientFactory.js +104 -34
  167. package/dist/client/clientFor.d.ts +52 -18
  168. package/dist/client/clientFor.d.ts.map +1 -1
  169. package/dist/client/clientFor.js +9 -1
  170. package/dist/client/errors.d.ts +82 -27
  171. package/dist/client/errors.d.ts.map +1 -1
  172. package/dist/client/errors.js +75 -19
  173. package/dist/client/makeClient.d.ts +494 -32
  174. package/dist/client/makeClient.d.ts.map +1 -1
  175. package/dist/client/makeClient.js +66 -24
  176. package/dist/client.d.ts +1 -0
  177. package/dist/client.d.ts.map +1 -1
  178. package/dist/client.js +2 -1
  179. package/dist/faker.d.ts.map +1 -1
  180. package/dist/http/Request.d.ts +1 -1
  181. package/dist/http/Request.d.ts.map +1 -1
  182. package/dist/http/Request.js +2 -2
  183. package/dist/ids.d.ts +42 -14
  184. package/dist/ids.d.ts.map +1 -1
  185. package/dist/ids.js +30 -5
  186. package/dist/index.d.ts +6 -7
  187. package/dist/index.d.ts.map +1 -1
  188. package/dist/index.js +8 -8
  189. package/dist/middleware.d.ts +13 -7
  190. package/dist/middleware.d.ts.map +1 -1
  191. package/dist/middleware.js +14 -8
  192. package/dist/rpc/Invalidation.d.ts +420 -0
  193. package/dist/rpc/Invalidation.d.ts.map +1 -0
  194. package/dist/rpc/Invalidation.js +168 -0
  195. package/dist/rpc/MiddlewareMaker.d.ts +11 -7
  196. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  197. package/dist/rpc/MiddlewareMaker.js +59 -38
  198. package/dist/rpc/RpcContextMap.d.ts +3 -3
  199. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  200. package/dist/rpc/RpcContextMap.js +4 -4
  201. package/dist/rpc/RpcMiddleware.d.ts +14 -10
  202. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  203. package/dist/rpc/RpcMiddleware.js +1 -1
  204. package/dist/rpc.d.ts +1 -1
  205. package/dist/rpc.d.ts.map +1 -1
  206. package/dist/rpc.js +2 -2
  207. package/dist/runtime.d.ts +19 -0
  208. package/dist/runtime.d.ts.map +1 -0
  209. package/dist/runtime.js +40 -0
  210. package/dist/setupRequest.d.ts +19 -0
  211. package/dist/setupRequest.d.ts.map +1 -0
  212. package/dist/setupRequest.js +69 -0
  213. package/dist/toast.d.ts +51 -0
  214. package/dist/toast.d.ts.map +1 -0
  215. package/dist/toast.js +34 -0
  216. package/dist/transform.d.ts +1 -1
  217. package/dist/transform.d.ts.map +1 -1
  218. package/dist/transform.js +4 -5
  219. package/dist/utils/effectify.d.ts +1 -1
  220. package/dist/utils/effectify.d.ts.map +1 -1
  221. package/dist/utils/effectify.js +2 -2
  222. package/dist/utils/extend.d.ts.map +1 -1
  223. package/dist/utils/gen.d.ts +4 -4
  224. package/dist/utils/gen.d.ts.map +1 -1
  225. package/dist/utils/logLevel.d.ts +2 -2
  226. package/dist/utils/logLevel.d.ts.map +1 -1
  227. package/dist/utils/logger.d.ts +4 -3
  228. package/dist/utils/logger.d.ts.map +1 -1
  229. package/dist/utils/logger.js +4 -4
  230. package/dist/utils.d.ts +34 -39
  231. package/dist/utils.d.ts.map +1 -1
  232. package/dist/utils.js +33 -37
  233. package/dist/validation/validators.d.ts.map +1 -1
  234. package/dist/validation.d.ts.map +1 -1
  235. package/dist/withToast.d.ts +30 -0
  236. package/dist/withToast.d.ts.map +1 -0
  237. package/dist/withToast.js +64 -0
  238. package/package.json +22 -247
  239. package/src/Array.ts +5 -5
  240. package/src/Chunk.ts +3 -3
  241. package/src/Config/SecretURL.ts +6 -3
  242. package/src/Config/internal/configSecretURL.ts +2 -2
  243. package/src/Config.ts +7 -0
  244. package/src/ConfigProvider.ts +5 -0
  245. package/src/{ServiceMap.ts → Context.ts} +56 -63
  246. package/src/Effect.ts +14 -16
  247. package/src/Emailer.ts +51 -0
  248. package/src/Inputify.type.ts +1 -1
  249. package/src/Layer.ts +11 -7
  250. package/src/Model/Repository/Registry.ts +35 -0
  251. package/src/Model/Repository/ext.ts +375 -0
  252. package/src/Model/Repository/internal/internal.ts +741 -0
  253. package/src/Model/Repository/legacy.ts +29 -0
  254. package/src/Model/Repository/makeRepo.ts +145 -0
  255. package/src/Model/Repository/service.ts +676 -0
  256. package/src/Model/Repository/validation.ts +31 -0
  257. package/src/Model/Repository.ts +6 -0
  258. package/src/Model/dsl.ts +129 -0
  259. package/src/Model/filter/filterApi.ts +60 -0
  260. package/src/Model/filter/types/errors.ts +47 -0
  261. package/src/Model/filter/types/fields.ts +50 -0
  262. package/src/Model/filter/types/path/common.ts +404 -0
  263. package/src/Model/filter/types/path/eager.ts +329 -0
  264. package/src/Model/filter/types/path/index.ts +4 -0
  265. package/src/Model/filter/types/utils.ts +128 -0
  266. package/src/Model/filter/types/validator.ts +46 -0
  267. package/src/Model/filter/types.ts +6 -0
  268. package/src/Model/query/dsl.ts +2694 -0
  269. package/src/Model/query/new-kid-interpreter.ts +484 -0
  270. package/src/Model/query.ts +13 -0
  271. package/src/Model.ts +4 -0
  272. package/src/NonEmptySet.ts +6 -4
  273. package/src/Option.ts +2 -0
  274. package/src/Pure.ts +22 -20
  275. package/src/QueueMaker.ts +19 -0
  276. package/src/RequestContext.ts +95 -0
  277. package/src/Schema/Class.ts +593 -59
  278. package/src/Schema/SchemaParser.ts +12 -0
  279. package/src/Schema/SpecialJsonSchema.ts +139 -0
  280. package/src/Schema/SpecialOpenApi.ts +130 -0
  281. package/src/Schema/brand.ts +22 -2
  282. package/src/Schema/email.ts +9 -4
  283. package/src/Schema/ext.ts +446 -91
  284. package/src/Schema/moreStrings.ts +147 -68
  285. package/src/Schema/numbers.ts +97 -28
  286. package/src/Schema/phoneNumber.ts +9 -5
  287. package/src/Schema/strings.ts +23 -14
  288. package/src/Schema.ts +389 -25
  289. package/src/Set.ts +6 -2
  290. package/src/Store.ts +277 -0
  291. package/src/_ext/Array.ts +4 -2
  292. package/src/_ext/misc.ts +4 -1
  293. package/src/_ext/ord.ext.ts +2 -1
  294. package/src/client/InvalidationKeys.ts +50 -0
  295. package/src/client/apiClientFactory.ts +234 -135
  296. package/src/client/clientFor.ts +105 -34
  297. package/src/client/errors.ts +100 -29
  298. package/src/client/makeClient.ts +594 -73
  299. package/src/client.ts +5 -4
  300. package/src/http/Request.ts +3 -3
  301. package/src/http.ts +1 -1
  302. package/src/ids.ts +33 -6
  303. package/src/index.ts +20 -23
  304. package/src/middleware.ts +13 -9
  305. package/src/rpc/Invalidation.ts +261 -0
  306. package/src/rpc/MiddlewareMaker.ts +88 -80
  307. package/src/rpc/README.md +2 -2
  308. package/src/rpc/RpcContextMap.ts +7 -6
  309. package/src/rpc/RpcMiddleware.ts +19 -13
  310. package/src/rpc.ts +4 -4
  311. package/src/runtime.ts +56 -0
  312. package/src/setupRequest.ts +134 -0
  313. package/src/toast.ts +54 -0
  314. package/src/transform.ts +4 -4
  315. package/src/utils/effectify.ts +1 -1
  316. package/src/utils/gen.ts +8 -8
  317. package/src/utils/logLevel.ts +1 -1
  318. package/src/utils/logger.ts +4 -3
  319. package/src/utils.ts +85 -158
  320. package/src/validation.ts +2 -2
  321. package/src/withToast.ts +133 -0
  322. package/test/dist/rpc.test.d.ts.map +1 -1
  323. package/test/dist/secretURL.test.d.ts.map +1 -0
  324. package/test/dist/special.test.d.ts.map +1 -0
  325. package/test/moreStrings.test.ts +1 -1
  326. package/test/rpc.test.ts +46 -6
  327. package/test/schema.test.ts +459 -30
  328. package/test/secretURL.test.ts +160 -0
  329. package/test/special.test.ts +1258 -0
  330. package/test/utils.test.ts +7 -7
  331. package/tsconfig.base.json +6 -5
  332. package/tsconfig.json +3 -1
  333. package/tsconfig.json.bak +2 -2
  334. package/tsconfig.src.json +29 -29
  335. package/tsconfig.test.json +2 -2
  336. package/dist/Operations.d.ts +0 -123
  337. package/dist/Operations.d.ts.map +0 -1
  338. package/dist/Operations.js +0 -29
  339. package/dist/ServiceMap.d.ts.map +0 -1
  340. package/dist/ServiceMap.js +0 -91
  341. package/eslint.config.mjs +0 -26
  342. package/src/Operations.ts +0 -55
@@ -1 +1 @@
1
- {"version":3,"file":"rpc.test.d.ts","sourceRoot":"","sources":["../rpc.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACrF,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;AAE7C,qBAAa,iBAAkB,SAAQ,sBAIrC;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIL,qBAAa,KAAM,SAAQ,UAQzB;CAAG"}
1
+ {"version":3,"file":"rpc.test.d.ts","sourceRoot":"","sources":["../rpc.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAErF,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;AAE7C,qBAAa,iBAAkB,SAAQ,sBAIrC;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAI0B,KAAK;;;;AAKpC,qBAAa,KAAM,SAAQ,UAQzB;CAAG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretURL.test.d.ts","sourceRoot":"","sources":["../secretURL.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"special.test.d.ts","sourceRoot":"","sources":["../special.test.ts"],"names":[],"mappings":""}
@@ -1,4 +1,4 @@
1
- import { S } from "effect-app"
1
+ import * as S from "effect-app/Schema"
2
2
  import * as fc from "fast-check"
3
3
  import { urlAlphabet } from "nanoid"
4
4
  import { test } from "vitest"
package/test/rpc.test.ts CHANGED
@@ -1,4 +1,8 @@
1
+ import type * as Effect from "effect/Effect"
2
+ import type * as Option from "effect/Option"
3
+ import { expect, test } from "vitest"
1
4
  import { makeRpcClient, NotLoggedInError, UnauthorizedError } from "../src/client.js"
5
+ import { ForceVoid } from "../src/client/makeClient.js"
2
6
  import { S } from "../src/index.js"
3
7
  import { RpcContextMap } from "../src/rpc.js"
4
8
 
@@ -8,16 +12,52 @@ export class RequestContextMap extends RpcContextMap.makeMap({
8
12
  test: RpcContextMap.make()(S.Never)
9
13
  }) {}
10
14
 
11
- const { TaggedRequest } = makeRpcClient(RequestContextMap)
15
+ const stubMiddleware = {
16
+ requestContextMap: RequestContextMap.config,
17
+ requestContext: undefined as never
18
+ }
19
+ const { TaggedRequestFor } = makeRpcClient(stubMiddleware)
20
+ const TaggedRequest = TaggedRequestFor("Test").Query
12
21
 
13
22
  export class Stats extends TaggedRequest<Stats>()("Stats", {}, {
14
23
  allowedRoles: ["manager"],
15
24
  success: {
16
- usersActive24Hours: S.Number,
17
- usersActiveLastWeek: S.Number,
18
- newUsersLast24Hours: S.Number,
19
- newUsersLastWeek: S.Number
25
+ usersActive24Hours: S.Finite,
26
+ usersActiveLastWeek: S.Finite,
27
+ newUsersLast24Hours: S.Finite,
28
+ newUsersLastWeek: S.Finite
20
29
  }
21
30
  }) {}
22
31
 
23
- declare const _stats: typeof Stats.success.Type
32
+ declare const _stats: typeof Stats.Type
33
+ declare const _statsSuccess: typeof Stats.success.Type
34
+ declare const _statsError: typeof Stats.error.Type
35
+ declare const _statsRequestType: typeof Stats.type
36
+
37
+ test("ForceVoid decodes and encodes as void", () => {
38
+ const statsFromMake = Stats.make({})
39
+ const statsFromMakeOption = Stats.makeOption({})
40
+ const statsFromMakeEffect = Stats.makeEffect({})
41
+
42
+ expect(S.decodeUnknownSync(ForceVoid)(undefined)).toBe(undefined)
43
+ expect(S.is(ForceVoid)(undefined)).toBe(true)
44
+ expect(S.decodeUnknownSync(ForceVoid)("test")).toBe(undefined)
45
+ expect(S.is(ForceVoid)("test")).toBe(true)
46
+ expect(S.encodeUnknownSync(ForceVoid)("test")).toBe(undefined)
47
+ expect(S.encodeUnknownSync(S.toCodecJson(ForceVoid))("test")).toBe(null)
48
+ expectTypeOf<typeof _stats>().toEqualTypeOf<Stats>()
49
+ expectTypeOf<typeof _statsSuccess>().toEqualTypeOf<{
50
+ readonly usersActive24Hours: number
51
+ readonly usersActiveLastWeek: number
52
+ readonly newUsersLast24Hours: number
53
+ readonly newUsersLastWeek: number
54
+ }>()
55
+ // Resource error carries only `config.error` (and optional `generalErrors`); rcm-derived
56
+ // middleware errors no longer leak into `resource.error` — they reach the wire via the
57
+ // middleware tag attached to the rpc group (`rpc.middlewares[*].error` failure-union).
58
+ expectTypeOf<typeof _statsError>().toEqualTypeOf<never>()
59
+ expectTypeOf<typeof _statsRequestType>().toEqualTypeOf<"query">()
60
+ expectTypeOf(statsFromMake).toEqualTypeOf<Stats>()
61
+ expectTypeOf(statsFromMakeOption).toEqualTypeOf<Option.Option<Stats>>()
62
+ expectTypeOf(statsFromMakeEffect).toEqualTypeOf<Effect.Effect<Stats, S.SchemaError>>()
63
+ })
@@ -1,6 +1,8 @@
1
1
  // import { generateFromArbitrary } from "@effect-app/infra/test"
2
- import { Array, S } from "effect-app"
3
- import { expect, expectTypeOf, test } from "vitest"
2
+ import * as Array from "effect-app/Array"
3
+ import * as S from "effect-app/Schema"
4
+ import { specialJsonSchemaDocument } from "effect-app/Schema/SpecialJsonSchema"
5
+ import { describe, expect, expectTypeOf, test } from "vitest"
4
6
 
5
7
  const A = S.Struct({ a: S.NonEmptyString255, email: S.NullOr(S.Email) })
6
8
  test("works", () => {
@@ -14,22 +16,127 @@ test("works", () => {
14
16
  })
15
17
 
16
18
  test("literal default works", () => {
17
- const l = S.Literal("a", "b")
19
+ const l = S.Literals(["a", "b"])
18
20
  expect(l.Default).toBe("a")
19
- const s = S.Struct({ l: l.withDefault })
20
- expect(s.makeUnsafe({}).l).toBe("a")
21
+ expectTypeOf(l.Default).toEqualTypeOf<"a">()
22
+ const s = S.Struct({ l: l.withConstructorDefault })
23
+ expect(s.make({}).l).toBe("a")
21
24
 
22
25
  const l2 = l.changeDefault("b")
23
- const s2 = S.Struct({ l: l2.withDefault })
24
- expect(s2.makeUnsafe({}).l).toBe("b")
26
+ const s2 = S.Struct({ l: l2.withConstructorDefault })
27
+ expect(s2.make({}).l).toBe("b")
28
+ })
29
+
30
+ test("NonEmptyString255.Type uses the named brand alias", () => {
31
+ type A = typeof S.NonEmptyString255.Type
32
+ type B = string & S.NonEmptyString255Brand
33
+ expectTypeOf<A>().toEqualTypeOf<B>()
34
+ })
35
+
36
+ test("Opaque accepts an explicit Encoded type", () => {
37
+ interface User {
38
+ readonly id: string
39
+ readonly _tag: "User"
40
+ }
41
+
42
+ interface UserEncoded {
43
+ readonly id: string
44
+ }
45
+
46
+ const baseSchema = S.Struct({ id: S.String, name: S.String })
47
+ const _UserSchema = S.Opaque<User, UserEncoded>()(baseSchema)
48
+
49
+ expectTypeOf<S.Codec.Encoded<typeof _UserSchema>>().toEqualTypeOf<UserEncoded>()
50
+ expectTypeOf<S.Schema.Type<typeof _UserSchema>>().toEqualTypeOf<User>()
51
+ expectTypeOf<S.Codec.Encoded<typeof _UserSchema>>().not.toEqualTypeOf<S.Codec.Encoded<typeof baseSchema>>()
52
+ })
53
+
54
+ test("Opaque with one generic keeps the base encoded shape", () => {
55
+ interface User {
56
+ readonly id: string
57
+ }
58
+
59
+ const baseSchema = S.Struct({ id: S.String })
60
+ const _UserSchema = S.Opaque<User>()(baseSchema)
61
+
62
+ expectTypeOf<S.Codec.Encoded<typeof _UserSchema>>().toEqualTypeOf<{ readonly id: string }>()
63
+ expectTypeOf<S.Schema.Type<typeof _UserSchema>>().toEqualTypeOf<User>()
64
+ })
65
+
66
+ test("Opaque preserves optional Struct.make input", () => {
67
+ interface User {
68
+ readonly a?: string | undefined
69
+ readonly b?: number | undefined
70
+ }
71
+
72
+ const schema = S.Opaque<User>()(S.Struct({
73
+ a: S.optional(S.String),
74
+ b: S.optional(S.Number)
75
+ }))
76
+
77
+ const made = schema.make()
78
+ expect(made).toEqual({})
79
+ expectTypeOf(made).toEqualTypeOf<User>()
80
+ })
81
+
82
+ test("Opaque preserves optional TaggedStruct.make input", () => {
83
+ interface OnlyTag {
84
+ readonly _tag: "OnlyTag"
85
+ }
86
+
87
+ const schema = S.Opaque<OnlyTag>()(S.TaggedStruct("OnlyTag", {}))
88
+
89
+ const made = schema.make()
90
+ expect(made).toEqual({ _tag: "OnlyTag" })
91
+ expectTypeOf(made).toEqualTypeOf<OnlyTag>()
92
+ })
93
+
94
+ test("S.Literals([\"A\", \"B\"]).Default is typed as \"A\"", () => {
95
+ const l = S.Literals(["A", "B"])
96
+ expect(l.Default).toBe("A")
97
+ expectTypeOf(l.Default).toEqualTypeOf<"A">()
98
+ })
99
+
100
+ test("Struct.make accepts void when all fields are optional", () => {
101
+ const schema = S.Struct({
102
+ a: S.optional(S.String),
103
+ b: S.optional(S.Number)
104
+ })
105
+
106
+ const made = schema.make()
107
+ expect(made).toEqual({})
108
+ expectTypeOf(made).toEqualTypeOf<{ readonly a?: string | undefined; readonly b?: number | undefined }>()
109
+ })
110
+
111
+ test("StructNestedEncoded resolves Struct and from.Encoded", () => {
112
+ const plain = S.Struct({ a: S.String, b: S.NullOr(S.String) })
113
+ const encodedKeys = plain.pipe(S.encodeKeys({ b: "b_encoded" }))
114
+
115
+ expectTypeOf<S.StructNestedEncoded<typeof plain>>().toEqualTypeOf<{
116
+ readonly a: string
117
+ readonly b: string | null
118
+ }>()
119
+
120
+ expectTypeOf<S.StructNestedEncoded<typeof encodedKeys>>().toEqualTypeOf<{
121
+ readonly a: string
122
+ readonly b_encoded: string | null
123
+ }>()
124
+ })
125
+
126
+ test("TaggedStruct.make accepts void when only constructor-default fields exist", () => {
127
+ const schema = S.TaggedStruct("OnlyTag", {})
128
+
129
+ const made = schema.make()
130
+ expect(made).toEqual({ _tag: "OnlyTag" })
131
+ expectTypeOf(made).toEqualTypeOf<{ readonly _tag: "OnlyTag" }>()
25
132
  })
26
133
 
27
134
  test("tagged union derives tag map and tags from v4 literal ast", () => {
28
- const schema = S.TaggedUnion(
135
+ const schema = S.TaggedUnion([
29
136
  S.TaggedStruct("A", { a: S.String }),
30
- S.TaggedStruct("B", { b: S.Number }),
137
+ S.TaggedStruct("B", { b: S.Finite }),
31
138
  S.TaggedStruct("C", { c: S.Boolean })
32
- )
139
+ ])
33
140
  const caseA = schema.cases["A"]
34
141
  const caseB = schema.cases["B"]
35
142
  const caseC = schema.cases["C"]
@@ -56,21 +163,21 @@ test("tagged union derives tag map and tags from v4 literal ast", () => {
56
163
  })
57
164
 
58
165
  test("TaggedUnion tags returns a Literals schema with correct literal values", () => {
59
- const schema = S.TaggedUnion(
166
+ const schema = S.TaggedUnion([
60
167
  S.TaggedStruct("X", { x: S.String }),
61
- S.TaggedStruct("Y", { y: S.Number })
62
- )
168
+ S.TaggedStruct("Y", { y: S.Finite })
169
+ ])
63
170
 
64
171
  expect(schema.tags.literals).toEqual(["X", "Y"])
65
172
  expectTypeOf(schema.tags.literals).toMatchTypeOf<readonly ["X", "Y"]>()
66
173
  })
67
174
 
68
175
  test("TaggedUnion tags.pick returns a subset of the tag literals", () => {
69
- const schema = S.TaggedUnion(
176
+ const schema = S.TaggedUnion([
70
177
  S.TaggedStruct("A", { a: S.String }),
71
- S.TaggedStruct("B", { b: S.Number }),
178
+ S.TaggedStruct("B", { b: S.Finite }),
72
179
  S.TaggedStruct("C", { c: S.Boolean })
73
- )
180
+ ])
74
181
 
75
182
  const subset = schema.tags.pick(["A", "C"])
76
183
  expect(subset.literals).toEqual(["A", "C"])
@@ -82,7 +189,7 @@ test("TaggedUnion tags.pick returns a subset of the tag literals", () => {
82
189
  test("tags standalone function extracts tags from member schemas", () => {
83
190
  const members = [
84
191
  S.TaggedStruct("Foo", { foo: S.String }),
85
- S.TaggedStruct("Bar", { bar: S.Number })
192
+ S.TaggedStruct("Bar", { bar: S.Finite })
86
193
  ] as const
87
194
 
88
195
  const tagSchema = S.tags(members)
@@ -95,7 +202,7 @@ test("tags standalone function extracts tags from member schemas", () => {
95
202
  test("ExtendTaggedUnion adds tags to an existing Union", () => {
96
203
  const union = S.Union([
97
204
  S.TaggedStruct("P", { p: S.String }),
98
- S.TaggedStruct("Q", { q: S.Number })
205
+ S.TaggedStruct("Q", { q: S.Finite })
99
206
  ])
100
207
  const extended = S.ExtendTaggedUnion(union)
101
208
 
@@ -109,10 +216,10 @@ test("ExtendTaggedUnion adds tags to an existing Union", () => {
109
216
  })
110
217
 
111
218
  test("TaggedUnion match dispatches on _tag", () => {
112
- const schema = S.TaggedUnion(
219
+ const schema = S.TaggedUnion([
113
220
  S.TaggedStruct("A", { a: S.String }),
114
- S.TaggedStruct("B", { b: S.Number })
115
- )
221
+ S.TaggedStruct("B", { b: S.Finite })
222
+ ])
116
223
  type T = S.Schema.Type<typeof schema>
117
224
 
118
225
  const matcher = schema.match({
@@ -124,9 +231,9 @@ test("TaggedUnion match dispatches on _tag", () => {
124
231
  })
125
232
 
126
233
  test("TaggedUnion with single member", () => {
127
- const schema = S.TaggedUnion(
234
+ const schema = S.TaggedUnion([
128
235
  S.TaggedStruct("Only", { val: S.String })
129
- )
236
+ ])
130
237
 
131
238
  expect(schema.tags.literals).toEqual(["Only"])
132
239
  expect(S.decodeSync(schema.tags)("Only")).toBe("Only")
@@ -135,11 +242,11 @@ test("TaggedUnion with single member", () => {
135
242
  })
136
243
 
137
244
  test("TaggedUnion tags type is narrowed to the exact tag literals", () => {
138
- const schema = S.TaggedUnion(
245
+ const schema = S.TaggedUnion([
139
246
  S.TaggedStruct("Alpha", { a: S.String }),
140
- S.TaggedStruct("Beta", { b: S.Number }),
247
+ S.TaggedStruct("Beta", { b: S.Finite }),
141
248
  S.TaggedStruct("Gamma", { c: S.Boolean })
142
- )
249
+ ])
143
250
 
144
251
  type Tags = S.Schema.Type<typeof schema.tags>
145
252
  expectTypeOf<Tags>().toEqualTypeOf<"Alpha" | "Beta" | "Gamma">()
@@ -149,11 +256,11 @@ test("TaggedUnion with encodeKeys renaming a non-tag key", () => {
149
256
  const MemberA = S.TaggedStruct("A", { firstName: S.String }).pipe(
150
257
  S.encodeKeys({ firstName: "first_name" })
151
258
  )
152
- const MemberB = S.TaggedStruct("B", { lastName: S.Number }).pipe(
259
+ const MemberB = S.TaggedStruct("B", { lastName: S.Finite }).pipe(
153
260
  S.encodeKeys({ lastName: "last_name" })
154
261
  )
155
262
 
156
- const schema = S.TaggedUnion(MemberA, MemberB)
263
+ const schema = S.TaggedUnion([MemberA, MemberB])
157
264
 
158
265
  expect(schema.tags.literals).toEqual(["A", "B"])
159
266
  expect(S.decodeSync(schema.tags)("A")).toBe("A")
@@ -179,9 +286,9 @@ test("TaggedUnion with encodeKeys renaming a non-tag key", () => {
179
286
 
180
287
  test("TaggedUnion with TaggedClass members", () => {
181
288
  class Foo extends S.TaggedClass<Foo>()("Foo", { name: S.String }) {}
182
- class Bar extends S.TaggedClass<Bar>()("Bar", { count: S.Number }) {}
289
+ class Bar extends S.TaggedClass<Bar>()("Bar", { count: S.Finite }) {}
183
290
 
184
- const schema = S.TaggedUnion(Foo, Bar)
291
+ const schema = S.TaggedUnion([Foo, Bar])
185
292
 
186
293
  expect(schema.tags.literals).toEqual(["Foo", "Bar"])
187
294
  expect(S.decodeSync(schema.tags)("Foo")).toBe("Foo")
@@ -200,3 +307,325 @@ test("TaggedUnion with TaggedClass members", () => {
200
307
  expect(schema.guards.Foo(new Bar({ count: 3 }))).toBe(false)
201
308
  expect(schema.guards.Bar(new Bar({ count: 3 }))).toBe(true)
202
309
  })
310
+
311
+ describe("ReadonlySetFromArray", () => {
312
+ test("decodes an array of strings to a Set", () => {
313
+ const schema = S.ReadonlySetFromArray(S.String)
314
+ const decoded = S.decodeUnknownSync(schema)(["a", "b", "c"])
315
+ expect(decoded).toEqual(new Set(["a", "b", "c"]))
316
+ })
317
+
318
+ test("encodes a Set back to an array", () => {
319
+ const schema = S.ReadonlySetFromArray(S.String)
320
+ const encoded = S.encodeSync(schema)(new Set(["a", "b"]))
321
+ expect(encoded).toEqual(["a", "b"])
322
+ })
323
+
324
+ test("decodes with NumberFromString as value", () => {
325
+ const schema = S.ReadonlySetFromArray(S.NumberFromString)
326
+ const decoded = S.decodeUnknownSync(schema)(["1", "2", "3"])
327
+ expect(decoded).toEqual(new Set([1, 2, 3]))
328
+ expectTypeOf(decoded).toEqualTypeOf<ReadonlySet<number>>()
329
+ })
330
+
331
+ test("encodes with NumberFromString as value", () => {
332
+ const schema = S.ReadonlySetFromArray(S.NumberFromString)
333
+ const encoded = S.encodeSync(schema)(new Set([1, 2, 3]))
334
+ expect(encoded).toEqual(["1", "2", "3"])
335
+ })
336
+
337
+ test("rejects invalid input", () => {
338
+ const schema = S.ReadonlySetFromArray(S.NumberFromString)
339
+ expect(() => S.decodeUnknownSync(schema)([1, 2])).toThrow()
340
+ })
341
+ })
342
+
343
+ describe("ReadonlyMapFromArray", () => {
344
+ test("decodes an array of tuples to a Map", () => {
345
+ const schema = S.ReadonlyMap({ key: S.String, value: S.Finite })
346
+ const decoded = S.decodeUnknownSync(schema)([["a", 1], ["b", 2]])
347
+ expect(decoded).toEqual(new Map([["a", 1], ["b", 2]]))
348
+ })
349
+
350
+ test("encodes a Map back to an array of tuples", () => {
351
+ const schema = S.ReadonlyMapFromArray({ key: S.String, value: S.Finite })
352
+ const encoded = S.encodeSync(schema)(new Map([["a", 1], ["b", 2]]))
353
+ expect(encoded).toEqual([["a", 1], ["b", 2]])
354
+ })
355
+
356
+ test("decodes with NumberFromString as key", () => {
357
+ const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.String })
358
+ const decoded = S.decodeUnknownSync(schema)([["1", "one"], ["2", "two"]])
359
+ expect(decoded).toEqual(new Map([[1, "one"], [2, "two"]]))
360
+ expectTypeOf(decoded).toEqualTypeOf<ReadonlyMap<number, string>>()
361
+ })
362
+
363
+ test("encodes with NumberFromString as key", () => {
364
+ const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.String })
365
+ const encoded = S.encodeSync(schema)(new Map([[1, "one"], [2, "two"]]))
366
+ expect(encoded).toEqual([["1", "one"], ["2", "two"]])
367
+ })
368
+
369
+ test("decodes with NumberFromString as value", () => {
370
+ const schema = S.ReadonlyMapFromArray({ key: S.String, value: S.NumberFromString })
371
+ const decoded = S.decodeUnknownSync(schema)([["a", "10"], ["b", "20"]])
372
+ expect(decoded).toEqual(new Map([["a", 10], ["b", 20]]))
373
+ expectTypeOf(decoded).toEqualTypeOf<ReadonlyMap<string, number>>()
374
+ })
375
+
376
+ test("encodes with NumberFromString as value", () => {
377
+ const schema = S.ReadonlyMapFromArray({ key: S.String, value: S.NumberFromString })
378
+ const encoded = S.encodeSync(schema)(new Map([["a", 10], ["b", 20]]))
379
+ expect(encoded).toEqual([["a", "10"], ["b", "20"]])
380
+ })
381
+
382
+ test("decodes with NumberFromString as both key and value", () => {
383
+ const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.NumberFromString })
384
+ const decoded = S.decodeUnknownSync(schema)([["1", "10"], ["2", "20"]])
385
+ expect(decoded).toEqual(new Map([[1, 10], [2, 20]]))
386
+ expectTypeOf(decoded).toEqualTypeOf<ReadonlyMap<number, number>>()
387
+ })
388
+
389
+ test("rejects invalid input", () => {
390
+ const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.String })
391
+ expect(() => S.decodeUnknownSync(schema)([[1, "val"]])).toThrow()
392
+ })
393
+ })
394
+
395
+ describe("ReadonlySet (with withConstructorDefault)", () => {
396
+ test("make provides withConstructorDefault", () => {
397
+ const schema = S.ReadonlySet(S.NumberFromString)
398
+ const struct = S.Struct({ items: schema.withConstructorDefault })
399
+ const made = struct.make({})
400
+ expect(made.items).toEqual(new Set())
401
+ })
402
+
403
+ test("decodes array with NumberFromString values", () => {
404
+ const schema = S.ReadonlySet(S.NumberFromString)
405
+ const decoded = S.decodeUnknownSync(schema)(["1", "2"])
406
+ expect(decoded).toEqual(new Set([1, 2]))
407
+ })
408
+ })
409
+
410
+ describe("ReadonlyMap (with withConstructorDefault)", () => {
411
+ test("make provides withConstructorDefault", () => {
412
+ const schema = S.ReadonlyMap({ key: S.NumberFromString, value: S.String })
413
+ const struct = S.Struct({ items: schema.withConstructorDefault })
414
+ const made = struct.make({})
415
+ expect(made.items).toEqual(new Map())
416
+ })
417
+
418
+ test("decodes array of tuples with NumberFromString keys", () => {
419
+ const schema = S.ReadonlyMap({ key: S.NumberFromString, value: S.String })
420
+ const decoded = S.decodeUnknownSync(schema)([["1", "one"]])
421
+ expect(decoded).toEqual(new Map([[1, "one"]]))
422
+ })
423
+ })
424
+
425
+ describe("JSON Schema", () => {
426
+ test("Email has format, minLength, maxLength", () => {
427
+ const doc = S.toJsonSchemaDocument(S.Email)
428
+ expect(doc).toStrictEqual({
429
+ dialect: "draft-2020-12",
430
+ schema: { "$ref": "#/$defs/Email" },
431
+ definitions: {
432
+ Email: {
433
+ type: "string",
434
+ title: "Email",
435
+ description: "an email according to RFC 5322",
436
+ format: "email",
437
+ allOf: [
438
+ { minLength: 3 },
439
+ { maxLength: 998 }
440
+ ]
441
+ }
442
+ }
443
+ })
444
+ })
445
+
446
+ test("Email specialJsonSchemaDocument flattens allOf", () => {
447
+ const doc = specialJsonSchemaDocument(S.Email)
448
+ expect(doc).toStrictEqual({
449
+ dialect: "draft-2020-12",
450
+ schema: { "$ref": "#/$defs/Email" },
451
+ definitions: {
452
+ Email: {
453
+ type: "string",
454
+ title: "Email",
455
+ description: "an email according to RFC 5322",
456
+ format: "email",
457
+ minLength: 3,
458
+ maxLength: 998
459
+ }
460
+ }
461
+ })
462
+ })
463
+
464
+ test("Date has identifier DateOrInvalid and ISO 8601 description", () => {
465
+ const doc = S.toJsonSchemaDocument(S.Date)
466
+ expect(doc).toStrictEqual({
467
+ dialect: "draft-2020-12",
468
+ schema: { "$ref": "#/$defs/DateOrInvalid" },
469
+ definitions: {
470
+ DateOrInvalid: {
471
+ type: "string",
472
+ description: "an ISO 8601 date string that will be decoded as a Date (may be invalid)",
473
+ format: "date-time"
474
+ }
475
+ }
476
+ })
477
+ })
478
+
479
+ test("DateValid has identifier Date and ISO 8601 description", () => {
480
+ const doc = S.toJsonSchemaDocument(S.DateValid)
481
+ expect(doc).toStrictEqual({
482
+ dialect: "draft-2020-12",
483
+ schema: { "$ref": "#/$defs/Date" },
484
+ definitions: {
485
+ Date: {
486
+ type: "string",
487
+ description: "a valid ISO 8601 date string that will be decoded as a Date",
488
+ format: "date-time"
489
+ }
490
+ }
491
+ })
492
+ })
493
+
494
+ test("PhoneNumber has format phone", () => {
495
+ const doc = specialJsonSchemaDocument(S.PhoneNumber)
496
+ expect(doc).toStrictEqual({
497
+ dialect: "draft-2020-12",
498
+ schema: { "$ref": "#/$defs/PhoneNumber" },
499
+ definitions: {
500
+ PhoneNumber: {
501
+ type: "string",
502
+ title: "PhoneNumber",
503
+ description: "a phone number with at least 7 digits",
504
+ format: "phone"
505
+ }
506
+ }
507
+ })
508
+ })
509
+
510
+ test("Url has format uri", () => {
511
+ const doc = specialJsonSchemaDocument(S.Url)
512
+ expect(doc).toStrictEqual({
513
+ dialect: "draft-2020-12",
514
+ schema: { "$ref": "#/$defs/Url" },
515
+ definitions: {
516
+ Url: {
517
+ type: "string",
518
+ title: "Url",
519
+ format: "uri"
520
+ }
521
+ }
522
+ })
523
+ })
524
+ })
525
+
526
+ describe("generateGuards", () => {
527
+ const StateSchema = S.TaggedUnion([
528
+ S.TaggedStruct("Active", { since: S.String }),
529
+ S.TaggedStruct("Inactive", { reason: S.String }),
530
+ S.TaggedStruct("Pending", { eta: S.Finite })
531
+ ])
532
+
533
+ type State = S.Schema.Type<typeof StateSchema>
534
+ type Entity = { readonly state: State; readonly name: string }
535
+
536
+ const { isActive, isAnyOf, isInactive, isPending } = StateSchema.generateGuards("state")
537
+
538
+ test("isActive narrows to Active member", () => {
539
+ const entity: Entity = { state: { _tag: "Active", since: "2024-01-01" }, name: "foo" }
540
+ expect(isActive(entity)).toBe(true)
541
+ if (isActive(entity)) {
542
+ expectTypeOf(entity.state).toEqualTypeOf<{ readonly _tag: "Active"; readonly since: string }>()
543
+ }
544
+ })
545
+
546
+ test("isActive returns false for non-Active", () => {
547
+ const entity: Entity = { state: { _tag: "Inactive", reason: "expired" }, name: "foo" }
548
+ expect(isActive(entity)).toBe(false)
549
+ })
550
+
551
+ test("isInactive narrows to Inactive member", () => {
552
+ const entity: Entity = { state: { _tag: "Inactive", reason: "expired" }, name: "foo" }
553
+ expect(isInactive(entity)).toBe(true)
554
+ })
555
+
556
+ test("isPending narrows to Pending member", () => {
557
+ const entity: Entity = { state: { _tag: "Pending", eta: 42 }, name: "foo" }
558
+ expect(isPending(entity)).toBe(true)
559
+ })
560
+
561
+ test("isAnyOf narrows to union of specified members", () => {
562
+ const isActiveOrPending = isAnyOf(["Active", "Pending"])
563
+ const active: Entity = { state: { _tag: "Active", since: "2024-01-01" }, name: "foo" }
564
+ const pending: Entity = { state: { _tag: "Pending", eta: 5 }, name: "bar" }
565
+ const inactive: Entity = { state: { _tag: "Inactive", reason: "expired" }, name: "baz" }
566
+
567
+ expect(isActiveOrPending(active)).toBe(true)
568
+ expect(isActiveOrPending(pending)).toBe(true)
569
+ expect(isActiveOrPending(inactive)).toBe(false)
570
+
571
+ if (isActiveOrPending(active)) {
572
+ expectTypeOf(active.state).toEqualTypeOf<
573
+ { readonly _tag: "Active"; readonly since: string } | { readonly _tag: "Pending"; readonly eta: number }
574
+ >()
575
+ }
576
+ })
577
+
578
+ test("guards use schema-based validation (built-in guards)", () => {
579
+ expect(StateSchema.guards.Active({ _tag: "Active" })).toBe(false)
580
+ expect(StateSchema.guards.Active({ _tag: "Active", since: "2024-01-01" })).toBe(true)
581
+ })
582
+ })
583
+
584
+ describe("generateGuardsFor", () => {
585
+ const StateSchema = S.TaggedUnion([
586
+ S.TaggedStruct("Active", { since: S.String }),
587
+ S.TaggedStruct("Inactive", { reason: S.String }),
588
+ S.TaggedStruct("Pending", { eta: S.Finite })
589
+ ])
590
+
591
+ type State = S.Schema.Type<typeof StateSchema>
592
+ type Entity = { readonly state: State; readonly name: string }
593
+
594
+ const { isActive, isAnyOf } = StateSchema.generateGuardsFor<Entity>()("state")
595
+
596
+ test("isActive narrows to Active member", () => {
597
+ const entity: Entity = { state: { _tag: "Active", since: "2024-01-01" }, name: "foo" }
598
+ expect(isActive(entity)).toBe(true)
599
+ if (isActive(entity)) {
600
+ expectTypeOf(entity.state).toEqualTypeOf<{ readonly _tag: "Active"; readonly since: string }>()
601
+ }
602
+ })
603
+
604
+ test("isActive returns false for non-Active", () => {
605
+ const entity: Entity = { state: { _tag: "Inactive", reason: "expired" }, name: "foo" }
606
+ expect(isActive(entity)).toBe(false)
607
+ })
608
+
609
+ test("isAnyOf narrows to union of specified members", () => {
610
+ const isActiveOrPending = isAnyOf(["Active", "Pending"])
611
+ const active: Entity = { state: { _tag: "Active", since: "2024-01-01" }, name: "foo" }
612
+ const inactive: Entity = { state: { _tag: "Inactive", reason: "expired" }, name: "baz" }
613
+
614
+ expect(isActiveOrPending(active)).toBe(true)
615
+ expect(isActiveOrPending(inactive)).toBe(false)
616
+ })
617
+
618
+ test("ExtendTaggedUnion also exposes generateGuardsFor", () => {
619
+ const union = S.Union([
620
+ S.TaggedStruct("X", { x: S.String }),
621
+ S.TaggedStruct("Y", { y: S.Finite })
622
+ ])
623
+ const extended = S.ExtendTaggedUnion(union)
624
+ type Obj = { readonly field: S.Schema.Type<typeof extended> }
625
+ const { isX, isY } = extended.generateGuardsFor<Obj>()("field")
626
+
627
+ expect(isX({ field: { _tag: "X", x: "hi" } })).toBe(true)
628
+ expect(isX({ field: { _tag: "Y", y: 1 } })).toBe(false)
629
+ expect(isY({ field: { _tag: "Y", y: 1 } })).toBe(true)
630
+ })
631
+ })