effect-app 4.0.0-beta.33 → 4.0.0-beta.331

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 (369) hide show
  1. package/CHANGELOG.md +1628 -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/DataDependencies.d.ts +136 -0
  21. package/dist/DataDependencies.d.ts.map +1 -0
  22. package/dist/DataDependencies.js +112 -0
  23. package/dist/Effect.d.ts +10 -9
  24. package/dist/Effect.d.ts.map +1 -1
  25. package/dist/Effect.js +6 -9
  26. package/dist/Emailer.d.ts +51 -0
  27. package/dist/Emailer.d.ts.map +1 -0
  28. package/dist/Emailer.js +7 -0
  29. package/dist/Function.d.ts.map +1 -1
  30. package/dist/Layer.d.ts +10 -6
  31. package/dist/Layer.d.ts.map +1 -1
  32. package/dist/Layer.js +3 -2
  33. package/dist/Model/Repository/Registry.d.ts +22 -0
  34. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  35. package/dist/Model/Repository/Registry.js +18 -0
  36. package/dist/Model/Repository/dependency.d.ts +6 -0
  37. package/dist/Model/Repository/dependency.d.ts.map +1 -0
  38. package/dist/Model/Repository/dependency.js +29 -0
  39. package/dist/Model/Repository/ext.d.ts +60 -0
  40. package/dist/Model/Repository/ext.d.ts.map +1 -0
  41. package/dist/Model/Repository/ext.js +124 -0
  42. package/dist/Model/Repository/internal/internal.d.ts +68 -0
  43. package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
  44. package/dist/Model/Repository/internal/internal.js +611 -0
  45. package/dist/Model/Repository/legacy.d.ts +21 -0
  46. package/dist/Model/Repository/legacy.d.ts.map +1 -0
  47. package/dist/Model/Repository/legacy.js +2 -0
  48. package/dist/Model/Repository/makeRepo.d.ts +71 -0
  49. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  50. package/dist/Model/Repository/makeRepo.js +30 -0
  51. package/dist/Model/Repository/service.d.ts +121 -0
  52. package/dist/Model/Repository/service.d.ts.map +1 -0
  53. package/dist/Model/Repository/service.js +2 -0
  54. package/dist/Model/Repository/validation.d.ts +72 -0
  55. package/dist/Model/Repository/validation.d.ts.map +1 -0
  56. package/dist/Model/Repository/validation.js +39 -0
  57. package/dist/Model/Repository.d.ts +8 -0
  58. package/dist/Model/Repository.d.ts.map +1 -0
  59. package/dist/Model/Repository.js +8 -0
  60. package/dist/Model/dsl.d.ts +33 -0
  61. package/dist/Model/dsl.d.ts.map +1 -0
  62. package/dist/Model/dsl.js +43 -0
  63. package/dist/Model/filter/filterApi.d.ts +30 -0
  64. package/dist/Model/filter/filterApi.d.ts.map +1 -0
  65. package/dist/Model/filter/filterApi.js +2 -0
  66. package/dist/Model/filter/types/errors.d.ts +29 -0
  67. package/dist/Model/filter/types/errors.d.ts.map +1 -0
  68. package/dist/Model/filter/types/errors.js +2 -0
  69. package/dist/Model/filter/types/fields.d.ts +15 -0
  70. package/dist/Model/filter/types/fields.d.ts.map +1 -0
  71. package/dist/Model/filter/types/fields.js +2 -0
  72. package/dist/Model/filter/types/path/common.d.ts +316 -0
  73. package/dist/Model/filter/types/path/common.d.ts.map +1 -0
  74. package/dist/Model/filter/types/path/common.js +2 -0
  75. package/dist/Model/filter/types/path/eager.d.ts +94 -0
  76. package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
  77. package/dist/Model/filter/types/path/eager.js +36 -0
  78. package/dist/Model/filter/types/path/index.d.ts +4 -0
  79. package/dist/Model/filter/types/path/index.d.ts.map +1 -0
  80. package/dist/Model/filter/types/path/index.js +3 -0
  81. package/dist/Model/filter/types/utils.d.ts +79 -0
  82. package/dist/Model/filter/types/utils.d.ts.map +1 -0
  83. package/dist/Model/filter/types/utils.js +2 -0
  84. package/dist/Model/filter/types/validator.d.ts +30 -0
  85. package/dist/Model/filter/types/validator.d.ts.map +1 -0
  86. package/dist/Model/filter/types/validator.js +2 -0
  87. package/dist/Model/filter/types.d.ts +5 -0
  88. package/dist/Model/filter/types.d.ts.map +1 -0
  89. package/dist/Model/filter/types.js +7 -0
  90. package/dist/Model/query/dsl.d.ts +630 -0
  91. package/dist/Model/query/dsl.d.ts.map +1 -0
  92. package/dist/Model/query/dsl.js +376 -0
  93. package/dist/Model/query/new-kid-interpreter.d.ts +132 -0
  94. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
  95. package/dist/Model/query/new-kid-interpreter.js +370 -0
  96. package/dist/Model/query.d.ts +15 -0
  97. package/dist/Model/query.d.ts.map +1 -0
  98. package/dist/Model/query.js +3 -0
  99. package/dist/Model.d.ts +6 -0
  100. package/dist/Model.d.ts.map +1 -0
  101. package/dist/Model.js +6 -0
  102. package/dist/NonEmptySet.d.ts +3 -1
  103. package/dist/NonEmptySet.d.ts.map +1 -1
  104. package/dist/NonEmptySet.js +2 -2
  105. package/dist/Option.d.ts +1 -0
  106. package/dist/Option.d.ts.map +1 -1
  107. package/dist/Option.js +3 -1
  108. package/dist/Pure.d.ts +7 -5
  109. package/dist/Pure.d.ts.map +1 -1
  110. package/dist/Pure.js +7 -4
  111. package/dist/QueueMaker.d.ts +11 -0
  112. package/dist/QueueMaker.d.ts.map +1 -0
  113. package/dist/QueueMaker.js +11 -0
  114. package/dist/RequestContext.d.ts +87 -0
  115. package/dist/RequestContext.d.ts.map +1 -0
  116. package/dist/RequestContext.js +45 -0
  117. package/dist/RequestScopedDependencies.d.ts +21 -0
  118. package/dist/RequestScopedDependencies.d.ts.map +1 -0
  119. package/dist/RequestScopedDependencies.js +16 -0
  120. package/dist/Schema/Class.d.ts +200 -20
  121. package/dist/Schema/Class.d.ts.map +1 -1
  122. package/dist/Schema/Class.js +268 -17
  123. package/dist/Schema/FastCheck.d.ts.map +1 -1
  124. package/dist/Schema/SchemaParser.d.ts +5 -0
  125. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  126. package/dist/Schema/SchemaParser.js +6 -0
  127. package/dist/Schema/SpecialJsonSchema.d.ts +34 -0
  128. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  129. package/dist/Schema/SpecialJsonSchema.js +117 -0
  130. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  131. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  132. package/dist/Schema/SpecialOpenApi.js +123 -0
  133. package/dist/Schema/brand.d.ts +5 -5
  134. package/dist/Schema/brand.d.ts.map +1 -1
  135. package/dist/Schema/brand.js +3 -1
  136. package/dist/Schema/email.d.ts.map +1 -1
  137. package/dist/Schema/email.js +12 -6
  138. package/dist/Schema/ext.d.ts +335 -61
  139. package/dist/Schema/ext.d.ts.map +1 -1
  140. package/dist/Schema/ext.js +388 -63
  141. package/dist/Schema/moreStrings.d.ts +82 -36
  142. package/dist/Schema/moreStrings.d.ts.map +1 -1
  143. package/dist/Schema/moreStrings.js +90 -53
  144. package/dist/Schema/numbers.d.ts +33 -21
  145. package/dist/Schema/numbers.d.ts.map +1 -1
  146. package/dist/Schema/numbers.js +65 -12
  147. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  148. package/dist/Schema/phoneNumber.js +11 -7
  149. package/dist/Schema/schema.d.ts +1 -1
  150. package/dist/Schema/schema.d.ts.map +1 -1
  151. package/dist/Schema/schema.js +2 -2
  152. package/dist/Schema/strings.d.ts +18 -4
  153. package/dist/Schema/strings.d.ts.map +1 -1
  154. package/dist/Schema/strings.js +1 -5
  155. package/dist/Schema.d.ts +225 -7
  156. package/dist/Schema.d.ts.map +1 -1
  157. package/dist/Schema.js +193 -26
  158. package/dist/SchemaAST.d.ts +2 -0
  159. package/dist/SchemaAST.d.ts.map +1 -0
  160. package/dist/SchemaAST.js +2 -0
  161. package/dist/Set.d.ts +4 -1
  162. package/dist/Set.d.ts.map +1 -1
  163. package/dist/Set.js +3 -2
  164. package/dist/Store.d.ts +215 -0
  165. package/dist/Store.d.ts.map +1 -0
  166. package/dist/Store.js +113 -0
  167. package/dist/_ext/Array.d.ts +1 -1
  168. package/dist/_ext/Array.d.ts.map +1 -1
  169. package/dist/_ext/Array.js +4 -2
  170. package/dist/_ext/misc.d.ts +4 -1
  171. package/dist/_ext/misc.d.ts.map +1 -1
  172. package/dist/_ext/misc.js +4 -2
  173. package/dist/_ext/ord.ext.d.ts +2 -1
  174. package/dist/_ext/ord.ext.d.ts.map +1 -1
  175. package/dist/_ext/ord.ext.js +2 -2
  176. package/dist/builtin.d.ts +2 -14
  177. package/dist/builtin.d.ts.map +1 -1
  178. package/dist/builtin.js +1 -1
  179. package/dist/client/InvalidationKeys.d.ts +30 -0
  180. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  181. package/dist/client/InvalidationKeys.js +37 -0
  182. package/dist/client/apiClientFactory.d.ts +19 -31
  183. package/dist/client/apiClientFactory.d.ts.map +1 -1
  184. package/dist/client/apiClientFactory.js +157 -39
  185. package/dist/client/clientFor.d.ts +54 -18
  186. package/dist/client/clientFor.d.ts.map +1 -1
  187. package/dist/client/clientFor.js +9 -1
  188. package/dist/client/errors.d.ts +82 -27
  189. package/dist/client/errors.d.ts.map +1 -1
  190. package/dist/client/errors.js +75 -19
  191. package/dist/client/makeClient.d.ts +490 -34
  192. package/dist/client/makeClient.d.ts.map +1 -1
  193. package/dist/client/makeClient.js +62 -30
  194. package/dist/client.d.ts +2 -0
  195. package/dist/client.d.ts.map +1 -1
  196. package/dist/client.js +3 -1
  197. package/dist/faker.d.ts.map +1 -1
  198. package/dist/http/Request.d.ts +1 -1
  199. package/dist/http/Request.d.ts.map +1 -1
  200. package/dist/http/Request.js +2 -2
  201. package/dist/ids.d.ts +32 -14
  202. package/dist/ids.d.ts.map +1 -1
  203. package/dist/ids.js +38 -6
  204. package/dist/index.d.ts +6 -7
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js +8 -8
  207. package/dist/middleware.d.ts +13 -7
  208. package/dist/middleware.d.ts.map +1 -1
  209. package/dist/middleware.js +14 -8
  210. package/dist/rpc/Invalidation.d.ts +1124 -0
  211. package/dist/rpc/Invalidation.d.ts.map +1 -0
  212. package/dist/rpc/Invalidation.js +178 -0
  213. package/dist/rpc/MiddlewareMaker.d.ts +11 -7
  214. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  215. package/dist/rpc/MiddlewareMaker.js +59 -38
  216. package/dist/rpc/RpcContextMap.d.ts +3 -3
  217. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  218. package/dist/rpc/RpcContextMap.js +4 -4
  219. package/dist/rpc/RpcMiddleware.d.ts +14 -10
  220. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  221. package/dist/rpc/RpcMiddleware.js +1 -1
  222. package/dist/rpc.d.ts +1 -1
  223. package/dist/rpc.d.ts.map +1 -1
  224. package/dist/rpc.js +2 -2
  225. package/dist/runtime.d.ts +19 -0
  226. package/dist/runtime.d.ts.map +1 -0
  227. package/dist/runtime.js +40 -0
  228. package/dist/setupRequest.d.ts +20 -0
  229. package/dist/setupRequest.d.ts.map +1 -0
  230. package/dist/setupRequest.js +72 -0
  231. package/dist/toast.d.ts +51 -0
  232. package/dist/toast.d.ts.map +1 -0
  233. package/dist/toast.js +34 -0
  234. package/dist/transform.d.ts +1 -1
  235. package/dist/transform.d.ts.map +1 -1
  236. package/dist/transform.js +4 -5
  237. package/dist/utils/effectify.d.ts +1 -1
  238. package/dist/utils/effectify.d.ts.map +1 -1
  239. package/dist/utils/effectify.js +2 -2
  240. package/dist/utils/extend.d.ts.map +1 -1
  241. package/dist/utils/gen.d.ts +4 -4
  242. package/dist/utils/gen.d.ts.map +1 -1
  243. package/dist/utils/logLevel.d.ts +2 -2
  244. package/dist/utils/logLevel.d.ts.map +1 -1
  245. package/dist/utils/logger.d.ts +4 -3
  246. package/dist/utils/logger.d.ts.map +1 -1
  247. package/dist/utils/logger.js +4 -4
  248. package/dist/utils.d.ts +34 -39
  249. package/dist/utils.d.ts.map +1 -1
  250. package/dist/utils.js +33 -37
  251. package/dist/validation/validators.d.ts.map +1 -1
  252. package/dist/validation.d.ts.map +1 -1
  253. package/dist/withToast.d.ts +30 -0
  254. package/dist/withToast.d.ts.map +1 -0
  255. package/dist/withToast.js +64 -0
  256. package/package.json +27 -251
  257. package/src/Array.ts +5 -5
  258. package/src/Chunk.ts +3 -3
  259. package/src/Config/SecretURL.ts +6 -3
  260. package/src/Config/internal/configSecretURL.ts +2 -2
  261. package/src/Config.ts +7 -0
  262. package/src/ConfigProvider.ts +5 -0
  263. package/src/{ServiceMap.ts → Context.ts} +56 -63
  264. package/src/DataDependencies.ts +171 -0
  265. package/src/Effect.ts +15 -17
  266. package/src/Emailer.ts +51 -0
  267. package/src/Inputify.type.ts +1 -1
  268. package/src/Layer.ts +11 -7
  269. package/src/Model/Repository/Registry.ts +35 -0
  270. package/src/Model/Repository/dependency.ts +32 -0
  271. package/src/Model/Repository/ext.ts +374 -0
  272. package/src/Model/Repository/internal/internal.ts +988 -0
  273. package/src/Model/Repository/legacy.ts +29 -0
  274. package/src/Model/Repository/makeRepo.ts +168 -0
  275. package/src/Model/Repository/service.ts +676 -0
  276. package/src/Model/Repository/validation.ts +38 -0
  277. package/src/Model/Repository.ts +7 -0
  278. package/src/Model/dsl.ts +129 -0
  279. package/src/Model/filter/filterApi.ts +78 -0
  280. package/src/Model/filter/types/errors.ts +47 -0
  281. package/src/Model/filter/types/fields.ts +50 -0
  282. package/src/Model/filter/types/path/common.ts +404 -0
  283. package/src/Model/filter/types/path/eager.ts +329 -0
  284. package/src/Model/filter/types/path/index.ts +4 -0
  285. package/src/Model/filter/types/utils.ts +128 -0
  286. package/src/Model/filter/types/validator.ts +46 -0
  287. package/src/Model/filter/types.ts +6 -0
  288. package/src/Model/query/dsl.ts +3092 -0
  289. package/src/Model/query/new-kid-interpreter.ts +532 -0
  290. package/src/Model/query.ts +13 -0
  291. package/src/Model.ts +5 -0
  292. package/src/NonEmptySet.ts +6 -4
  293. package/src/Option.ts +2 -0
  294. package/src/Pure.ts +10 -7
  295. package/src/QueueMaker.ts +11 -0
  296. package/src/RequestContext.ts +48 -0
  297. package/src/RequestScopedDependencies.ts +45 -0
  298. package/src/Schema/Class.ts +648 -60
  299. package/src/Schema/SchemaParser.ts +12 -0
  300. package/src/Schema/SpecialJsonSchema.ts +137 -0
  301. package/src/Schema/SpecialOpenApi.ts +130 -0
  302. package/src/Schema/brand.ts +24 -9
  303. package/src/Schema/email.ts +14 -7
  304. package/src/Schema/ext.ts +508 -102
  305. package/src/Schema/moreStrings.ts +217 -86
  306. package/src/Schema/numbers.ts +107 -28
  307. package/src/Schema/phoneNumber.ts +13 -9
  308. package/src/Schema/schema.ts +1 -1
  309. package/src/Schema/strings.ts +23 -14
  310. package/src/Schema.ts +403 -46
  311. package/src/SchemaAST.ts +1 -0
  312. package/src/Set.ts +6 -2
  313. package/src/Store.ts +325 -0
  314. package/src/_ext/Array.ts +4 -2
  315. package/src/_ext/misc.ts +4 -1
  316. package/src/_ext/ord.ext.ts +2 -1
  317. package/src/builtin-json.d.ts +24 -0
  318. package/src/builtin.ts +16 -21
  319. package/src/client/InvalidationKeys.ts +55 -0
  320. package/src/client/apiClientFactory.ts +301 -141
  321. package/src/client/clientFor.ts +107 -34
  322. package/src/client/errors.ts +100 -29
  323. package/src/client/makeClient.ts +594 -80
  324. package/src/client.ts +6 -4
  325. package/src/http/Request.ts +3 -3
  326. package/src/http.ts +1 -1
  327. package/src/ids.ts +45 -7
  328. package/src/index.ts +20 -23
  329. package/src/middleware.ts +13 -9
  330. package/src/rpc/Invalidation.ts +278 -0
  331. package/src/rpc/MiddlewareMaker.ts +88 -80
  332. package/src/rpc/README.md +2 -2
  333. package/src/rpc/RpcContextMap.ts +7 -6
  334. package/src/rpc/RpcMiddleware.ts +19 -13
  335. package/src/rpc.ts +4 -4
  336. package/src/runtime.ts +57 -0
  337. package/src/setupRequest.ts +138 -0
  338. package/src/toast.ts +54 -0
  339. package/src/transform.ts +4 -4
  340. package/src/utils/effectify.ts +1 -1
  341. package/src/utils/gen.ts +8 -8
  342. package/src/utils/logLevel.ts +1 -1
  343. package/src/utils/logger.ts +4 -3
  344. package/src/utils.ts +85 -158
  345. package/src/validation.ts +2 -2
  346. package/src/withToast.ts +133 -0
  347. package/test/dist/rpc.test.d.ts.map +1 -1
  348. package/test/dist/rpcMiddlewareContext.test.d.ts.map +1 -0
  349. package/test/dist/secretURL.test.d.ts.map +1 -0
  350. package/test/dist/special.test.d.ts.map +1 -0
  351. package/test/moreStrings.test.ts +55 -10
  352. package/test/rpc.test.ts +38 -10
  353. package/test/rpcMiddlewareContext.test.ts +63 -0
  354. package/test/schema.test.ts +505 -26
  355. package/test/secretURL.test.ts +160 -0
  356. package/test/special.test.ts +1261 -0
  357. package/test/utils.test.ts +7 -7
  358. package/tsconfig.base.json +6 -5
  359. package/tsconfig.json +9 -2
  360. package/tsconfig.src.json +29 -29
  361. package/tsconfig.test.json +2 -2
  362. package/dist/Operations.d.ts +0 -123
  363. package/dist/Operations.d.ts.map +0 -1
  364. package/dist/Operations.js +0 -29
  365. package/dist/ServiceMap.d.ts.map +0 -1
  366. package/dist/ServiceMap.js +0 -91
  367. package/eslint.config.mjs +0 -26
  368. package/src/Operations.ts +0 -55
  369. package/tsconfig.json.bak +0 -12
@@ -0,0 +1,1261 @@
1
+ import { InvalidStateError, LoginError, NotFoundError, NotLoggedInError, OptimisticConcurrencyException, ServiceUnavailableError, UnauthorizedError, ValidationError } from "effect-app/client/errors"
2
+ import { RequestContext } from "effect-app/RequestContext"
3
+ import * as AppSchema from "effect-app/Schema"
4
+ import { Class } from "effect-app/Schema/Class"
5
+ import { flattenNestedAnyOf, flattenSimpleAllOf, specialJsonSchemaDocument } from "effect-app/Schema/SpecialJsonSchema"
6
+ import { deduplicateOpenApiSchemas } from "effect-app/Schema/SpecialOpenApi"
7
+ import * as Effect from "effect/Effect"
8
+ import * as Option from "effect/Option"
9
+ import * as Predicate from "effect/Predicate"
10
+ import * as S from "effect/Schema"
11
+ import * as SchemaGetter from "effect/SchemaGetter"
12
+ import { describe, expect, it } from "vitest"
13
+
14
+ class _FacadeModel extends AppSchema.Class<_FacadeModel>("FacadeModel")({ value: S.String }) {
15
+ get upperValue() {
16
+ return this.value.toUpperCase()
17
+ }
18
+
19
+ static describe(self: { readonly value: string }) {
20
+ return `value:${self.value}`
21
+ }
22
+ }
23
+
24
+ interface FacadeModelEncoded {
25
+ readonly value: string
26
+ }
27
+
28
+ interface FacadeModelMake {
29
+ readonly value: string
30
+ }
31
+
32
+ class FacadeModel extends AppSchema.OpaqueFacade<
33
+ FacadeModel,
34
+ FacadeModelEncoded,
35
+ FacadeModelMake,
36
+ never,
37
+ never
38
+ >()(_FacadeModel) {
39
+ declare readonly value: string
40
+ declare readonly upperValue: string
41
+
42
+ get decoratedValue() {
43
+ return `facade:${this.value}`
44
+ }
45
+
46
+ label() {
47
+ return this.decoratedValue
48
+ }
49
+ }
50
+
51
+ class _RenamedFacadeModel extends AppSchema.Class<_RenamedFacadeModel>("RenamedFacadeModel")({
52
+ internalName: S.String
53
+ }) {
54
+ get upperName() {
55
+ return this.internalName.toUpperCase()
56
+ }
57
+ }
58
+
59
+ interface RenamedFacadeModelEncoded {
60
+ readonly external_name: string
61
+ }
62
+
63
+ interface RenamedFacadeModelMake {
64
+ readonly internalName: string
65
+ }
66
+
67
+ class RenamedFacadeModel extends AppSchema.OpaqueFacade<
68
+ RenamedFacadeModel,
69
+ RenamedFacadeModelEncoded,
70
+ RenamedFacadeModelMake,
71
+ never,
72
+ never
73
+ >()(_RenamedFacadeModel.pipe(S.encodeKeys({ internalName: "external_name" }))) {
74
+ declare readonly internalName: string
75
+ declare readonly upperName: string
76
+
77
+ get decoratedName() {
78
+ return `facade:${this.internalName}`
79
+ }
80
+ }
81
+
82
+ const _StructFacadeModel = AppSchema.Struct({ value: S.String })
83
+
84
+ interface StructFacadeModelEncoded {
85
+ readonly value: string
86
+ }
87
+
88
+ interface StructFacadeModelMake {
89
+ readonly value: string
90
+ }
91
+
92
+ class StructFacadeModel extends AppSchema.OpaqueFacade<
93
+ StructFacadeModel,
94
+ StructFacadeModelEncoded,
95
+ StructFacadeModelMake,
96
+ never,
97
+ never
98
+ >()(_StructFacadeModel) {
99
+ declare readonly value: string
100
+ }
101
+
102
+ const _RenamedStructFacadeModel = AppSchema.Struct({ internalName: S.String }).pipe(
103
+ S.encodeKeys({ internalName: "external_name" })
104
+ )
105
+
106
+ interface RenamedStructFacadeModelEncoded {
107
+ readonly external_name: string
108
+ }
109
+
110
+ interface RenamedStructFacadeModelMake {
111
+ readonly internalName: string
112
+ }
113
+
114
+ class RenamedStructFacadeModel extends AppSchema.OpaqueFacade<
115
+ RenamedStructFacadeModel,
116
+ RenamedStructFacadeModelEncoded,
117
+ RenamedStructFacadeModelMake,
118
+ never,
119
+ never
120
+ >()(_RenamedStructFacadeModel) {
121
+ declare readonly internalName: string
122
+ }
123
+
124
+ const _RenamedOpaqueStructFacadeModel = AppSchema
125
+ .Opaque<
126
+ { readonly internalName: string },
127
+ RenamedStructFacadeModelEncoded
128
+ >()(AppSchema.Struct({ internalName: S.String }))
129
+ .pipe(S.encodeKeys({ internalName: "external_name" }))
130
+
131
+ class RenamedOpaqueStructFacadeModel extends AppSchema.OpaqueFacade<
132
+ RenamedOpaqueStructFacadeModel,
133
+ RenamedStructFacadeModelEncoded,
134
+ RenamedStructFacadeModelMake,
135
+ never,
136
+ never
137
+ >()(_RenamedOpaqueStructFacadeModel) {
138
+ declare readonly internalName: string
139
+ }
140
+
141
+ describe("Class", () => {
142
+ describe("strict", () => {
143
+ it("encoding doesnt accepts plain objects matching the struct (Fields argument)", () => {
144
+ class A extends Class<A>("A")({ a: S.String }) {}
145
+
146
+ expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
147
+ expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
148
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
149
+ })
150
+
151
+ it("encoding rejects plain objects matching the struct (Struct argument)", () => {
152
+ class A extends Class<A>("A")(S.Struct({ a: S.String })) {}
153
+
154
+ expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
155
+ expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
156
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
157
+ })
158
+
159
+ it("decoding still works normally", () => {
160
+ class A extends Class<A>("A")({ a: S.String }) {}
161
+
162
+ const decoded = S.decodeUnknownSync(A)({ a: "hello" })
163
+ expect(decoded).toBeInstanceOf(A)
164
+ expect(decoded.a).toBe("hello")
165
+
166
+ expect(() => S.decodeUnknownSync(A)(null)).toThrow()
167
+ expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
168
+ })
169
+
170
+ it("S.is accepts class instances and not matching plain objects", () => {
171
+ class A extends Class<A>("A")({ a: S.String }) {}
172
+
173
+ expect(S.is(A)(new A({ a: "hello" }))).toBe(true)
174
+ expect(S.is(A)({ a: "world" })).toBe(false)
175
+ expect(S.is(A)({ a: 1 })).toBe(false)
176
+ expect(S.is(A)(null)).toBe(false)
177
+ })
178
+
179
+ it("rejects values that don't match the struct", () => {
180
+ class A extends Class<A>("A")({ a: S.String }) {}
181
+
182
+ expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
183
+ expect(() => S.encodeUnknownSync(A)("not an object")).toThrow()
184
+ })
185
+
186
+ it("returns a class constructor — new and instanceof work", () => {
187
+ class A extends Class<A>("A")({ a: S.String }) {}
188
+
189
+ const instance = new A({ a: "hello" })
190
+ expect(instance).toBeInstanceOf(A)
191
+ expect(instance.a).toBe("hello")
192
+ })
193
+
194
+ it("preserves fields and identifier", () => {
195
+ class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
196
+
197
+ expect(A.identifier).toBe("A")
198
+ expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
199
+ })
200
+ })
201
+
202
+ describe("non strict", () => {
203
+ it("encoding accepts plain objects matching the struct (Fields argument)", () => {
204
+ class A extends Class<A>("A")({ a: S.String }, undefined, { strict: false }) {}
205
+
206
+ expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
207
+ expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
208
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
209
+ })
210
+
211
+ it("encoding accepts plain objects matching the struct (Struct argument)", () => {
212
+ class A extends Class<A>("A")(S.Struct({ a: S.String }), undefined, { strict: false }) {}
213
+
214
+ expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
215
+ expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
216
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
217
+ })
218
+
219
+ it("S.is accepts class instances and matching plain objects", () => {
220
+ class A extends Class<A>("A")({ a: S.String }, undefined, { strict: false }) {}
221
+
222
+ expect(S.is(A)(new A({ a: "hello" }))).toBe(true)
223
+ expect(S.is(A)({ a: "world" })).toBe(true)
224
+ expect(S.is(A)({ a: 1 })).toBe(false)
225
+ expect(S.is(A)(null)).toBe(false)
226
+ })
227
+ })
228
+ })
229
+
230
+ describe("Class constructor", () => {
231
+ describe("strict", () => {
232
+ it("works as a base class — new, instanceof, encoding plain objects", () => {
233
+ class A extends Class<A>("A")({ a: S.String }) {}
234
+
235
+ const instance = new A({ a: "hello" })
236
+ expect(instance).toBeInstanceOf(A)
237
+ expect(instance.a).toBe("hello")
238
+
239
+ expect(S.encodeUnknownSync(A)(instance)).toStrictEqual({ a: "hello" })
240
+ expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
241
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
242
+ expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
243
+ })
244
+
245
+ it("decoding works normally", () => {
246
+ class A extends Class<A>("A")({ a: S.String }) {}
247
+
248
+ const decoded = S.decodeUnknownSync(A)({ a: "hello" })
249
+ expect(decoded).toBeInstanceOf(A)
250
+ expect(decoded.a).toBe("hello")
251
+
252
+ expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
253
+ })
254
+
255
+ it("exposes fields, identifier", () => {
256
+ class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
257
+
258
+ expect(A.identifier).toBe("A")
259
+ expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
260
+ })
261
+ })
262
+
263
+ describe("non strict", () => {
264
+ it("works as a base class — new, instanceof, encoding plain objects", () => {
265
+ class A extends Class<A>("A")({ a: S.String }, undefined, { strict: false }) {}
266
+
267
+ const instance = new A({ a: "hello" })
268
+ expect(instance).toBeInstanceOf(A)
269
+ expect(instance.a).toBe("hello")
270
+
271
+ expect(S.encodeUnknownSync(A)(instance)).toStrictEqual({ a: "hello" })
272
+ expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
273
+ expect(() => S.encodeUnknownSync(A)(null)).toThrow()
274
+ expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
275
+ })
276
+ })
277
+ })
278
+
279
+ describe("TaggedStruct constructor", () => {
280
+ it("works with _tag, make, and encoding plain objects", () => {
281
+ const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
282
+
283
+ const instance = Circle.make({ radius: 5 })
284
+ expect(instance).toEqual({ _tag: "Circle", radius: 5 })
285
+
286
+ expect(S.encodeUnknownSync(Circle)(instance)).toStrictEqual({ _tag: "Circle", radius: 5 })
287
+ expect(S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: 10 })).toStrictEqual({ _tag: "Circle", radius: 10 })
288
+
289
+ expect(() => S.encodeUnknownSync(Circle)(null)).toThrow()
290
+ expect(() => S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: "nope" })).toThrow()
291
+ })
292
+
293
+ it("decoding works normally", () => {
294
+ const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
295
+
296
+ const decoded = S.decodeUnknownSync(Circle)({ _tag: "Circle", radius: 5 })
297
+ expect(decoded).toEqual({ _tag: "Circle", radius: 5 })
298
+ })
299
+
300
+ it("S.decodeSync(S.toType(X)) should report n length schema error", () => {
301
+ class X extends S.Opaque<X>()(S.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
302
+
303
+ try {
304
+ S.decodeSync(S.toType(X))({ _tag: "X", n: "a" /* not length 3 */ })
305
+ expect.fail("expected decode to fail with a SchemaError")
306
+ } catch (error) {
307
+ expect(S.isSchemaError(error)).toBe(true)
308
+ if (S.isSchemaError(error)) {
309
+ expect(error.message).toContain("n")
310
+ expect(error.message.toLowerCase()).toContain("length")
311
+ }
312
+ }
313
+ })
314
+
315
+ it("exposes fields", () => {
316
+ const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
317
+
318
+ expect(Object.keys(Circle.fields)).toContain("_tag")
319
+ expect(Object.keys(Circle.fields)).toContain("radius")
320
+ })
321
+ })
322
+
323
+ describe("strict declaration option", () => {
324
+ it("Class strict: true keeps class-level expected errors", () => {
325
+ class X extends Class<X>("X")({ n: S.String.pipe(S.check(S.isMinLength(3))) }, undefined, { strict: true }) {}
326
+
327
+ expect(() => S.decodeSync(S.toType(X))({ n: "a" })).toThrow("Expected X")
328
+ })
329
+
330
+ it("TaggedStruct preserves field-level expected errors", () => {
331
+ class X extends S.Opaque<X>()(AppSchema.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
332
+
333
+ expect(() => S.decodeSync(S.toType(X))({ _tag: "X", n: "a" })).toThrow()
334
+ })
335
+
336
+ it("Class with encoded override strict: true keeps class-level expected errors", () => {
337
+ class X extends Class<X, never>("X")({ n: S.String.pipe(S.check(S.isMinLength(3))) }, undefined, {
338
+ strict: true
339
+ }) {}
340
+
341
+ expect(() => S.decodeSync(S.toType(X))({ n: "a" })).toThrow("Expected X")
342
+ })
343
+
344
+ it("TaggedStruct with opaque wrapper decodes tagged input", () => {
345
+ class X extends S.Opaque<X>()(AppSchema.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
346
+
347
+ expect(() => S.decodeSync(S.toType(X))({ _tag: "X", n: "a" })).toThrow()
348
+ })
349
+ })
350
+
351
+ describe("Class.copy", () => {
352
+ it("creates a new instance with updated fields", () => {
353
+ class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
354
+
355
+ const instance = new A({ a: "hello", b: 1 })
356
+ const copied: A = A.copy(instance, { b: 2 })
357
+ expect(copied).toBeInstanceOf(A)
358
+ expect(copied.a).toBe("hello")
359
+ expect(copied.b).toBe(2)
360
+ })
361
+
362
+ it("accepts a function for updates", () => {
363
+ class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
364
+
365
+ const instance = new A({ a: "hello", b: 1 })
366
+ const copied: A = A.copy(instance, (a) => ({ b: a.b + 1 }))
367
+ expect(copied).toBeInstanceOf(A)
368
+ expect(copied.b).toBe(2)
369
+ })
370
+
371
+ it("is pipeable", () => {
372
+ class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
373
+
374
+ const instance = new A({ a: "hello", b: 1 })
375
+ const copied: A = A.copy({ b: 2 })(instance)
376
+ expect(copied).toBeInstanceOf(A)
377
+ expect(copied.b).toBe(2)
378
+ })
379
+ })
380
+
381
+ describe("TaggedStruct (opaque).copy", () => {
382
+ it("creates a new instance with updated fields", () => {
383
+ const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
384
+
385
+ const instance = Circle.make({ radius: 5 })
386
+ const copied = Circle.copy(instance, { radius: 10 })
387
+ expect(copied).toEqual({ _tag: "Circle", radius: 10 })
388
+ })
389
+
390
+ it("accepts a function for updates", () => {
391
+ const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
392
+
393
+ const instance = Circle.make({ radius: 5 })
394
+ const copied = Circle.copy(instance, (c) => ({ radius: c.radius * 2 }))
395
+ expect(copied).toEqual({ _tag: "Circle", radius: 10 })
396
+ })
397
+ })
398
+
399
+ describe("OpaqueFacade", () => {
400
+ it("keeps OpaqueFacade usable with struct schemas", () => {
401
+ const decoded = S.decodeSync(StructFacadeModel)({ value: "hello" })
402
+
403
+ expect(decoded).toStrictEqual({ value: "hello" })
404
+ expect(S.encodeSync(StructFacadeModel)(decoded)).toStrictEqual({ value: "hello" })
405
+ expect(S.is(StructFacadeModel)(decoded)).toBe(true)
406
+ expect(S.is(StructFacadeModel)({ value: 1 })).toBe(false)
407
+ })
408
+
409
+ it("keeps encoded-key struct facades on the struct path", () => {
410
+ const decoded = S.decodeSync(RenamedStructFacadeModel)({ external_name: "fran" })
411
+
412
+ expect(decoded).toStrictEqual({ internalName: "fran" })
413
+ expect(decoded).not.toBeInstanceOf(RenamedStructFacadeModel)
414
+ expect(S.encodeSync(RenamedStructFacadeModel)(decoded)).toStrictEqual({ external_name: "fran" })
415
+ expect(S.is(RenamedStructFacadeModel)(decoded)).toBe(true)
416
+ expect(S.is(RenamedStructFacadeModel)({ external_name: "fran" })).toBe(false)
417
+ })
418
+
419
+ it("keeps encoded-key opaque struct facades on the struct path", () => {
420
+ const decoded = S.decodeSync(RenamedOpaqueStructFacadeModel)({ external_name: "fran" })
421
+
422
+ expect(decoded).toStrictEqual({ internalName: "fran" })
423
+ expect(decoded).not.toBeInstanceOf(RenamedOpaqueStructFacadeModel)
424
+ expect(S.encodeSync(RenamedOpaqueStructFacadeModel)(decoded)).toStrictEqual({ external_name: "fran" })
425
+ expect(S.is(RenamedOpaqueStructFacadeModel)(decoded)).toBe(true)
426
+ expect(S.is(RenamedOpaqueStructFacadeModel)({ external_name: "fran" })).toBe(false)
427
+ })
428
+
429
+ it("decodes through the public facade class", () => {
430
+ const decoded = S.decodeSync(FacadeModel)({ value: "hello" })
431
+ const decodedEffect = Effect.runSync(S.decodeUnknownEffect(FacadeModel)({ value: "effect" }))
432
+
433
+ expect(decoded).toBeInstanceOf(FacadeModel)
434
+ expect(decodedEffect).toBeInstanceOf(FacadeModel)
435
+ expect(decoded).toBeInstanceOf(_FacadeModel)
436
+ expect(decoded.value).toBe("hello")
437
+ expect(decoded.upperValue).toBe("HELLO")
438
+ expect(decoded.decoratedValue).toBe("facade:hello")
439
+ expect(decoded.label()).toBe("facade:hello")
440
+ expect(FacadeModel.describe(decoded)).toBe("value:hello")
441
+ })
442
+
443
+ it("validates facade instances with S.is", () => {
444
+ const instance = new FacadeModel({ value: "hello" })
445
+
446
+ expect(instance).toBeInstanceOf(FacadeModel)
447
+ expect(instance.upperValue).toBe("HELLO")
448
+ expect(instance.label()).toBe("facade:hello")
449
+ expect(S.is(FacadeModel)(instance)).toBe(true)
450
+ expect(S.is(FacadeModel)(S.decodeSync(FacadeModel)({ value: "world" }))).toBe(true)
451
+ expect(S.is(FacadeModel)({ value: "plain" })).toBe(false)
452
+ expect(S.is(FacadeModel)({ value: 1 })).toBe(false)
453
+ })
454
+
455
+ it("decodes renamed keys through the public facade class", () => {
456
+ const decoded = S.decodeSync(RenamedFacadeModel)({ external_name: "fran" })
457
+
458
+ expect(decoded).toBeInstanceOf(RenamedFacadeModel)
459
+ expect(decoded).toBeInstanceOf(_RenamedFacadeModel)
460
+ expect(Object.getPrototypeOf(decoded)).toBe(RenamedFacadeModel.prototype)
461
+ expect(decoded.internalName).toBe("fran")
462
+ expect(decoded.upperName).toBe("FRAN")
463
+ expect(decoded.decoratedName).toBe("facade:fran")
464
+ expect(S.encodeSync(RenamedFacadeModel)(decoded)).toStrictEqual({ external_name: "fran" })
465
+ expect(S.is(RenamedFacadeModel)(decoded)).toBe(true)
466
+ expect(S.is(RenamedFacadeModel)({ internalName: "plain" })).toBe(false)
467
+ expect(S.is(RenamedFacadeModel)({ external_name: "plain" })).toBe(false)
468
+ })
469
+
470
+ it("keeps generated struct facades decodable with public statics", () => {
471
+ const input: typeof RequestContext.Encoded = {
472
+ span: { traceId: "trace", spanId: "span", sampled: true },
473
+ name: "operation",
474
+ locale: "en",
475
+ namespace: "tenant"
476
+ }
477
+
478
+ const decoded = S.decodeSync(RequestContext)(input)
479
+
480
+ expect(decoded).not.toBeInstanceOf(RequestContext)
481
+ expect(S.is(RequestContext)(decoded)).toBe(true)
482
+ expect(RequestContext.toMonitoring(decoded)).toStrictEqual({ operationName: "operation", locale: "en" })
483
+ })
484
+
485
+ it("keeps error facades class-only", () => {
486
+ const makeStructErrorFacade = () =>
487
+ AppSchema.OpaqueErrorFacadeClass<
488
+ { readonly value: string },
489
+ { readonly value: string },
490
+ { readonly value: string },
491
+ never,
492
+ never
493
+ >()(
494
+ // Runtime guard: only class schemas are accepted (struct schemas throw).
495
+ AppSchema.Struct({ value: S.String }) as never
496
+ )
497
+
498
+ expect(makeStructErrorFacade).toThrow("OpaqueErrorFacadeClass requires a class schema")
499
+ })
500
+ })
501
+
502
+ describe("Struct.copy", () => {
503
+ it("creates a new value with updated fields", () => {
504
+ const A = AppSchema.Struct({ a: S.String, b: S.Number })
505
+
506
+ const instance = A.make({ a: "hello", b: 1 })
507
+ const copied = A.copy(instance, { b: 2 })
508
+
509
+ expect(copied).toEqual({ a: "hello", b: 2 })
510
+ expect(copied).not.toBe(instance)
511
+ })
512
+
513
+ it("accepts a function for updates", () => {
514
+ const A = AppSchema.Struct({ a: S.String, b: S.Number })
515
+
516
+ const instance = A.make({ a: "hello", b: 1 })
517
+ const copied = A.copy(instance, (a) => ({ b: a.b + 1 }))
518
+
519
+ expect(copied).toEqual({ a: "hello", b: 2 })
520
+ })
521
+
522
+ it("allows widening updates against the full struct type", () => {
523
+ const A = AppSchema.Struct({
524
+ name: S.String,
525
+ state: S.Union([
526
+ S.Struct({ _tag: S.tag("a"), a: S.String }),
527
+ S.Struct({ _tag: S.tag("b"), b: S.Number })
528
+ ])
529
+ })
530
+
531
+ const instance = A.make({ name: "x", state: { _tag: "a", a: "a" } })
532
+ const copied = A.copy(instance, { state: { _tag: "b", b: 1 } })
533
+
534
+ expect(copied).toEqual({ name: "x", state: { _tag: "b", b: 1 } })
535
+ })
536
+
537
+ it("copy function is preserved when using .annotate()", () => {
538
+ const A = AppSchema.Struct({ a: S.String, b: S.Number }).annotate({ title: "A" })
539
+
540
+ const instance = A.make({ a: "hello", b: 1 })
541
+ const copied = A.copy(instance, { b: 2 })
542
+
543
+ expect(copied).toEqual({ a: "hello", b: 2 })
544
+ expect(copied).not.toBe(instance)
545
+ })
546
+
547
+ it("copy function is preserved when using .annotateKey()", () => {
548
+ const A = AppSchema.Struct({ a: S.String, b: S.Number }).annotateKey({ title: "A" })
549
+
550
+ const instance = A.make({ a: "hello", b: 1 })
551
+ const copied = A.copy(instance, { b: 2 })
552
+
553
+ expect(copied).toEqual({ a: "hello", b: 2 })
554
+ expect(copied).not.toBe(instance)
555
+ })
556
+
557
+ it("copy function is preserved when using .mapFields()", () => {
558
+ const A = AppSchema.Struct({ a: S.String, b: S.Number }).mapFields((f) => ({ ...f }))
559
+
560
+ const instance = A.make({ a: "hello", b: 1 })
561
+ const copied = A.copy(instance, { b: 2 })
562
+
563
+ expect(copied).toEqual({ a: "hello", b: 2 })
564
+ expect(copied).not.toBe(instance)
565
+ })
566
+
567
+ it("copy function is preserved through chained calls", () => {
568
+ const A = AppSchema
569
+ .Struct({ a: S.String, b: S.Number })
570
+ .annotate({ title: "A" })
571
+ .annotateKey({ description: "test" })
572
+
573
+ const instance = A.make({ a: "hello", b: 1 })
574
+ const copied = A.copy(instance, { b: 2 })
575
+
576
+ expect(copied).toEqual({ a: "hello", b: 2 })
577
+ expect(copied).not.toBe(instance)
578
+ })
579
+ })
580
+
581
+ describe("TaggedStruct.copy", () => {
582
+ it("creates a new tagged value with updated fields", () => {
583
+ const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
584
+
585
+ const instance = Circle.make({ radius: 5 })
586
+ const copied = Circle.copy(instance, { radius: 10 })
587
+
588
+ expect(copied).toEqual({ _tag: "Circle", radius: 10 })
589
+ expect(copied).not.toBe(instance)
590
+ })
591
+ })
592
+
593
+ describe("TaggedError", () => {
594
+ it("InvalidStateError toString includes the message", () => {
595
+ const error = new InvalidStateError("something went wrong")
596
+ expect(error.toString()).toContain("something went wrong")
597
+ })
598
+
599
+ it("NotFoundError toString includes the message", () => {
600
+ const error = new NotFoundError({ type: "User", id: "123" })
601
+ expect(error.toString()).toContain("Didn't find User")
602
+ expect(error.toString()).toContain("123")
603
+ })
604
+
605
+ it("ServiceUnavailableError toString includes the message", () => {
606
+ const error = new ServiceUnavailableError("service down")
607
+ expect(error.toString()).toContain("service down")
608
+ })
609
+
610
+ it("ValidationError toString includes the message", () => {
611
+ const error = new ValidationError({ errors: ["field required"] })
612
+ expect(error.toString()).toContain("Validation failed")
613
+ expect(error.toString()).toContain("field required")
614
+ })
615
+
616
+ it("NotLoggedInError toString includes the message", () => {
617
+ const error = new NotLoggedInError("not logged in")
618
+ expect(error.toString()).toContain("not logged in")
619
+ })
620
+
621
+ it("LoginError toString includes the message", () => {
622
+ const error = new LoginError("login failed")
623
+ expect(error.toString()).toContain("login failed")
624
+ })
625
+
626
+ it("UnauthorizedError toString includes the message", () => {
627
+ const error = new UnauthorizedError("forbidden")
628
+ expect(error.toString()).toContain("forbidden")
629
+ })
630
+
631
+ it("OptimisticConcurrencyException toString includes the message", () => {
632
+ const error = new OptimisticConcurrencyException({ message: "conflict" })
633
+ expect(error.toString()).toContain("conflict")
634
+ })
635
+
636
+ it("OptimisticConcurrencyException from details toString includes the message", () => {
637
+ const error = new OptimisticConcurrencyException({ type: "User", id: "123", code: 409 })
638
+ expect(error.toString()).toContain("Existing User 123 record changed")
639
+ })
640
+ })
641
+
642
+ describe("SpecialJsonSchema", () => {
643
+ it("nullable to optional — from NullOr", () => {
644
+ const nullableDecodedUndefinedEncoded = (schema: S.Top) => {
645
+ const isNullableSchema = "members" in schema
646
+ && globalThis.Array.isArray((schema as any).members)
647
+ && (schema as any).members.length === 2
648
+ && (schema as any).members.some((member: any) => member.ast._tag === "Null")
649
+
650
+ const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<S.Top> : undefined
651
+ const innerSchema = nullableMembers
652
+ ? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
653
+ : schema
654
+
655
+ const nullableSchema = isNullableSchema ? schema : S.NullOr(schema)
656
+
657
+ return nullableSchema.pipe(
658
+ S.encodeTo(S.optionalKey(innerSchema), {
659
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
660
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
661
+ })
662
+ )
663
+ }
664
+
665
+ const fromNullOr = nullableDecodedUndefinedEncoded(S.NullOr(S.String))
666
+ const structFromNullOr = S.Struct({ status: fromNullOr })
667
+
668
+ const encode = S.encodeUnknownSync(structFromNullOr as any)
669
+ const encodedNull = encode({ status: null })
670
+ expect("status" in encodedNull).toBe(false)
671
+ expect(encode({ status: "test" })).toStrictEqual({ status: "test" })
672
+
673
+ const decode = S.decodeUnknownSync(structFromNullOr as any)
674
+ expect(decode({})).toStrictEqual({ status: null })
675
+ expect(decode({ status: "test" })).toStrictEqual({ status: "test" })
676
+
677
+ const doc = specialJsonSchemaDocument(structFromNullOr)
678
+ expect(doc).toStrictEqual({
679
+ dialect: "draft-2020-12",
680
+ schema: {
681
+ "type": "object",
682
+ "additionalProperties": true,
683
+ "properties": {
684
+ "status": { "type": "string" }
685
+ }
686
+ },
687
+ definitions: {}
688
+ })
689
+ })
690
+
691
+ it("identifies X universally — deduplicates same-fingerprint references", () => {
692
+ const X = S.String.annotate({ title: "X", identifier: "X" })
693
+
694
+ const s = S.Struct({
695
+ a: S.NullOr(X).pipe(
696
+ S.encodeTo(S.optionalKey(X), {
697
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
698
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
699
+ })
700
+ ),
701
+ b: S.NullOr(X).pipe(
702
+ S.encodeTo(S.optionalKey(X), {
703
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
704
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
705
+ })
706
+ ),
707
+ c: S.NullOr(X),
708
+ d: X,
709
+ e: X.pipe(S.optionalKey)
710
+ })
711
+
712
+ const doc = specialJsonSchemaDocument(s)
713
+ expect(doc).toStrictEqual({
714
+ dialect: "draft-2020-12",
715
+ schema: {
716
+ "type": "object",
717
+ "additionalProperties": true,
718
+ "properties": {
719
+ "a": { "$ref": "#/$defs/X" },
720
+ "b": { "$ref": "#/$defs/X" },
721
+ "c": {
722
+ "anyOf": [
723
+ { "$ref": "#/$defs/X" },
724
+ { "type": "null" }
725
+ ]
726
+ },
727
+ "d": { "$ref": "#/$defs/X" },
728
+ "e": { "$ref": "#/$defs/X" }
729
+ },
730
+ "required": ["c", "d"]
731
+ },
732
+ definitions: {
733
+ X: {
734
+ "type": "string",
735
+ "title": "X"
736
+ }
737
+ }
738
+ })
739
+ })
740
+
741
+ it("shared annotated schema via helper — deduplicates", () => {
742
+ const X = S.String.annotate({ title: "X", identifier: "X" })
743
+
744
+ const cache = new WeakMap()
745
+ const nullableDecodedUndefinedEncoded = (schema: S.Top) => {
746
+ const isNullableSchema = "members" in schema
747
+ && globalThis.Array.isArray((schema as any).members)
748
+ && (schema as any).members.length === 2
749
+ && (schema as any).members.some((member: any) => member.ast._tag === "Null")
750
+
751
+ const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<S.Top> : undefined
752
+ const innerSchema = nullableMembers
753
+ ? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
754
+ : schema
755
+
756
+ const cached = cache.get(innerSchema.ast)
757
+ if (cached !== undefined) return cached
758
+
759
+ const nullableSchema = isNullableSchema ? schema : S.NullOr(schema)
760
+ const out = nullableSchema.pipe(
761
+ S.encodeTo(S.optionalKey(innerSchema), {
762
+ decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
763
+ encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
764
+ })
765
+ )
766
+
767
+ cache.set(innerSchema.ast, out)
768
+ return out
769
+ }
770
+
771
+ const structWithShared = S.Struct({
772
+ a: nullableDecodedUndefinedEncoded(X),
773
+ b: nullableDecodedUndefinedEncoded(S.NullOr(X)),
774
+ c: S.NullOr(X),
775
+ d: X,
776
+ e: X.pipe(S.optionalKey)
777
+ })
778
+
779
+ const doc = specialJsonSchemaDocument(structWithShared)
780
+ expect(doc).toStrictEqual({
781
+ dialect: "draft-2020-12",
782
+ schema: {
783
+ "type": "object",
784
+ "additionalProperties": true,
785
+ "properties": {
786
+ "a": { "$ref": "#/$defs/X" },
787
+ "b": { "$ref": "#/$defs/X" },
788
+ "c": {
789
+ "anyOf": [
790
+ { "$ref": "#/$defs/X" },
791
+ { "type": "null" }
792
+ ]
793
+ },
794
+ "d": { "$ref": "#/$defs/X" },
795
+ "e": { "$ref": "#/$defs/X" }
796
+ },
797
+ "required": ["c", "d"]
798
+ },
799
+ definitions: {
800
+ X: {
801
+ "type": "string",
802
+ "title": "X"
803
+ }
804
+ }
805
+ })
806
+ })
807
+ })
808
+
809
+ describe("SpecialOpenApi", () => {
810
+ it("deduplicates identical components.schemas entries with same base identifier", () => {
811
+ const spec = {
812
+ openapi: "3.1.0",
813
+ info: { title: "Test", version: "1.0" },
814
+ paths: {
815
+ "/foo": {
816
+ get: {
817
+ responses: {
818
+ 200: {
819
+ content: {
820
+ "application/json": {
821
+ schema: { $ref: "#/components/schemas/X" }
822
+ }
823
+ }
824
+ }
825
+ }
826
+ }
827
+ },
828
+ "/bar": {
829
+ get: {
830
+ responses: {
831
+ 200: {
832
+ content: {
833
+ "application/json": {
834
+ schema: { $ref: "#/components/schemas/X1" }
835
+ }
836
+ }
837
+ }
838
+ }
839
+ }
840
+ }
841
+ },
842
+ components: {
843
+ schemas: {
844
+ X: { type: "string", title: "X" },
845
+ X1: { type: "string", title: "X" }
846
+ }
847
+ }
848
+ }
849
+
850
+ const result = deduplicateOpenApiSchemas(spec) as any
851
+
852
+ // X1 should be removed, and $ref to X1 rewritten to X
853
+ expect(result.components.schemas).toStrictEqual({
854
+ X: { type: "string", title: "X" }
855
+ })
856
+ expect(
857
+ result.paths["/bar"].get.responses[200].content["application/json"].schema
858
+ )
859
+ .toStrictEqual({ $ref: "#/components/schemas/X" })
860
+ })
861
+
862
+ it("does not deduplicate entries with different representations", () => {
863
+ const spec = {
864
+ openapi: "3.1.0",
865
+ info: { title: "Test", version: "1.0" },
866
+ paths: {},
867
+ components: {
868
+ schemas: {
869
+ X: { type: "string", title: "X" },
870
+ X1: { type: "number", title: "X" }
871
+ }
872
+ }
873
+ }
874
+
875
+ const result = deduplicateOpenApiSchemas(spec) as any
876
+
877
+ // Both should remain since they have different representations
878
+ expect(result.components.schemas).toStrictEqual({
879
+ X: { type: "string", title: "X" },
880
+ X1: { type: "number", title: "X" }
881
+ })
882
+ })
883
+
884
+ it("returns spec unchanged when no duplicates exist", () => {
885
+ const spec = {
886
+ openapi: "3.1.0",
887
+ info: { title: "Test", version: "1.0" },
888
+ paths: {},
889
+ components: {
890
+ schemas: {
891
+ Foo: { type: "string" },
892
+ Bar: { type: "number" }
893
+ }
894
+ }
895
+ }
896
+
897
+ const result = deduplicateOpenApiSchemas(spec)
898
+ expect(result).toStrictEqual(spec)
899
+ })
900
+
901
+ it("rewrites nested $ref pointers in allOf/anyOf/oneOf", () => {
902
+ const spec = {
903
+ openapi: "3.1.0",
904
+ info: { title: "Test", version: "1.0" },
905
+ paths: {
906
+ "/baz": {
907
+ post: {
908
+ requestBody: {
909
+ content: {
910
+ "application/json": {
911
+ schema: {
912
+ anyOf: [
913
+ { $ref: "#/components/schemas/Y1" },
914
+ { type: "null" }
915
+ ]
916
+ }
917
+ }
918
+ }
919
+ }
920
+ }
921
+ }
922
+ },
923
+ components: {
924
+ schemas: {
925
+ Y: { type: "object", properties: { name: { type: "string" } } },
926
+ Y1: { type: "object", properties: { name: { type: "string" } } }
927
+ }
928
+ }
929
+ }
930
+
931
+ const result = deduplicateOpenApiSchemas(spec) as any
932
+
933
+ expect(result.components.schemas).toStrictEqual({
934
+ Y: { type: "object", properties: { name: { type: "string" } } }
935
+ })
936
+ expect(
937
+ result.paths["/baz"].post.requestBody.content["application/json"].schema.anyOf[0]
938
+ )
939
+ .toStrictEqual({ $ref: "#/components/schemas/Y" })
940
+ })
941
+
942
+ it("rewrites $ref pointers inside definitions themselves", () => {
943
+ const spec = {
944
+ openapi: "3.1.0",
945
+ info: { title: "Test", version: "1.0" },
946
+ paths: {},
947
+ components: {
948
+ schemas: {
949
+ Inner: { type: "string" },
950
+ Inner1: { type: "string" },
951
+ Outer: {
952
+ type: "object",
953
+ properties: {
954
+ field: { $ref: "#/components/schemas/Inner1" }
955
+ }
956
+ }
957
+ }
958
+ }
959
+ }
960
+
961
+ const result = deduplicateOpenApiSchemas(spec) as any
962
+
963
+ expect(Object.keys(result.components.schemas)).toStrictEqual(["Inner", "Outer"])
964
+ expect(result.components.schemas.Outer.properties.field).toStrictEqual({
965
+ $ref: "#/components/schemas/Inner"
966
+ })
967
+ })
968
+
969
+ it("handles spec without components gracefully", () => {
970
+ const spec = { openapi: "3.1.0", info: { title: "Test", version: "1.0" }, paths: {} }
971
+ const result = deduplicateOpenApiSchemas(spec)
972
+ expect(result).toStrictEqual(spec)
973
+ })
974
+
975
+ it("flattens allOf in components.schemas", () => {
976
+ const spec = {
977
+ openapi: "3.1.0",
978
+ info: { title: "Test", version: "1.0" },
979
+ paths: {},
980
+ components: {
981
+ schemas: {
982
+ PositiveInt: {
983
+ type: "integer",
984
+ allOf: [{ exclusiveMinimum: 0, title: "PositiveInt" }]
985
+ }
986
+ }
987
+ }
988
+ }
989
+
990
+ const result = deduplicateOpenApiSchemas(spec) as any
991
+
992
+ expect(result.components.schemas.PositiveInt).toStrictEqual({
993
+ type: "integer",
994
+ exclusiveMinimum: 0,
995
+ title: "PositiveInt"
996
+ })
997
+ })
998
+
999
+ it("does not flatten allOf containing $ref entries", () => {
1000
+ const spec = {
1001
+ openapi: "3.1.0",
1002
+ info: { title: "Test", version: "1.0" },
1003
+ paths: {},
1004
+ components: {
1005
+ schemas: {
1006
+ Composed: {
1007
+ type: "object",
1008
+ allOf: [{ $ref: "#/components/schemas/Base" }]
1009
+ },
1010
+ Base: { type: "object", properties: { id: { type: "string" } } }
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ const result = deduplicateOpenApiSchemas(spec) as any
1016
+
1017
+ expect(result.components.schemas.Composed).toStrictEqual({
1018
+ type: "object",
1019
+ allOf: [{ $ref: "#/components/schemas/Base" }]
1020
+ })
1021
+ })
1022
+
1023
+ it("does not flatten allOf entries that define their own type", () => {
1024
+ const spec = {
1025
+ openapi: "3.1.0",
1026
+ info: { title: "Test", version: "1.0" },
1027
+ paths: {},
1028
+ components: {
1029
+ schemas: {
1030
+ Mixed: {
1031
+ type: "object",
1032
+ allOf: [{ type: "string", minLength: 1 }]
1033
+ }
1034
+ }
1035
+ }
1036
+ }
1037
+
1038
+ const result = deduplicateOpenApiSchemas(spec) as any
1039
+
1040
+ expect(result.components.schemas.Mixed).toStrictEqual({
1041
+ type: "object",
1042
+ allOf: [{ type: "string", minLength: 1 }]
1043
+ })
1044
+ })
1045
+ })
1046
+
1047
+ describe("flattenSimpleAllOf", () => {
1048
+ it("flattens constraint-only allOf into parent with type", () => {
1049
+ const input = {
1050
+ type: "integer",
1051
+ allOf: [{ exclusiveMinimum: 0, title: "PositiveInt" }]
1052
+ }
1053
+ expect(flattenSimpleAllOf(input)).toStrictEqual({
1054
+ type: "integer",
1055
+ exclusiveMinimum: 0,
1056
+ title: "PositiveInt"
1057
+ })
1058
+ })
1059
+
1060
+ it("flattens string type with multiple constraints", () => {
1061
+ const input = {
1062
+ type: "string",
1063
+ allOf: [
1064
+ { minLength: 1, maxLength: 255 },
1065
+ { title: "NonEmptyString255" }
1066
+ ]
1067
+ }
1068
+ expect(flattenSimpleAllOf(input)).toStrictEqual({
1069
+ type: "string",
1070
+ minLength: 1,
1071
+ maxLength: 255,
1072
+ title: "NonEmptyString255"
1073
+ })
1074
+ })
1075
+
1076
+ it("does not flatten allOf with $ref", () => {
1077
+ const input = {
1078
+ type: "object",
1079
+ allOf: [{ $ref: "#/components/schemas/Base" }]
1080
+ }
1081
+ expect(flattenSimpleAllOf(input)).toStrictEqual(input)
1082
+ })
1083
+
1084
+ it("does not flatten allOf entries with their own type", () => {
1085
+ const input = {
1086
+ type: "object",
1087
+ allOf: [{ type: "string", minLength: 1 }]
1088
+ }
1089
+ expect(flattenSimpleAllOf(input)).toStrictEqual(input)
1090
+ })
1091
+
1092
+ it("allOf entry wins on property conflict", () => {
1093
+ const input = {
1094
+ type: "integer",
1095
+ title: "OldTitle",
1096
+ allOf: [{ title: "NewTitle", minimum: 0 }]
1097
+ }
1098
+ expect(flattenSimpleAllOf(input)).toStrictEqual({
1099
+ type: "integer",
1100
+ title: "NewTitle",
1101
+ minimum: 0
1102
+ })
1103
+ })
1104
+ })
1105
+
1106
+ describe("Post-processing integration — real Effect Schema types", () => {
1107
+ it("PositiveInt — allOf flattened, no wrapping", () => {
1108
+ const doc = specialJsonSchemaDocument(AppSchema.PositiveInt)
1109
+ expect(doc.definitions["PositiveInt"]).toStrictEqual({
1110
+ type: "integer",
1111
+ exclusiveMinimum: 0
1112
+ })
1113
+ })
1114
+
1115
+ it("NonEmptyString255 — multiple allOf constraints merged", () => {
1116
+ const doc = specialJsonSchemaDocument(AppSchema.NonEmptyString255)
1117
+ expect(doc.definitions["NonEmptyString255"]).toStrictEqual({
1118
+ type: "string",
1119
+ minLength: 1,
1120
+ maxLength: 255
1121
+ })
1122
+ })
1123
+
1124
+ it("NullOr(NonEmptyString64k) — null preserved in anyOf, allOf flattened in definition", () => {
1125
+ const schema = S.Struct({ note: S.NullOr(AppSchema.NonEmptyString64k) })
1126
+ const doc = specialJsonSchemaDocument(schema)
1127
+
1128
+ // null variant preserved (correct JSON Schema for NullOr)
1129
+ expect(doc.schema).toStrictEqual({
1130
+ type: "object",
1131
+ additionalProperties: true,
1132
+ properties: {
1133
+ note: {
1134
+ anyOf: [
1135
+ { $ref: "#/$defs/NonEmptyString64k" },
1136
+ { type: "null" }
1137
+ ]
1138
+ }
1139
+ },
1140
+ required: ["note"]
1141
+ })
1142
+
1143
+ // allOf flattened in the referenced definition
1144
+ expect(doc.definitions["NonEmptyString64k"]).toStrictEqual({
1145
+ type: "string",
1146
+ minLength: 1,
1147
+ maxLength: 65536
1148
+ })
1149
+ })
1150
+
1151
+ it("NonNegativeInt — allOf flattened", () => {
1152
+ const doc = specialJsonSchemaDocument(AppSchema.NonNegativeInt)
1153
+ expect(doc.definitions["NonNegativeInt"]).toStrictEqual({
1154
+ type: "integer",
1155
+ minimum: 0
1156
+ })
1157
+ })
1158
+
1159
+ it("NullOr union flattens nested anyOf members", () => {
1160
+ const A = S.String.annotate({ identifier: "A" })
1161
+ const B = S.Boolean.annotate({ identifier: "B" })
1162
+ const schema = S.Struct({
1163
+ value: S.NullOr(S.Union([A, B]))
1164
+ })
1165
+ const doc = specialJsonSchemaDocument(schema)
1166
+ const valueProp = (doc.schema as Record<string, any>)["properties"]["value"]
1167
+
1168
+ expect(valueProp).toStrictEqual({
1169
+ anyOf: [
1170
+ { $ref: "#/$defs/A" },
1171
+ { $ref: "#/$defs/B" },
1172
+ { type: "null" }
1173
+ ]
1174
+ })
1175
+ })
1176
+ })
1177
+
1178
+ describe("flattenNestedAnyOf", () => {
1179
+ it("flattens nested anyOf with no sibling keys", () => {
1180
+ const input = {
1181
+ anyOf: [
1182
+ { anyOf: [{ type: "string" }, { type: "number" }] },
1183
+ { type: "null" }
1184
+ ]
1185
+ }
1186
+ expect(flattenNestedAnyOf(input)).toStrictEqual({
1187
+ anyOf: [
1188
+ { type: "string" },
1189
+ { type: "number" },
1190
+ { type: "null" }
1191
+ ]
1192
+ })
1193
+ })
1194
+
1195
+ it("does not flatten anyOf entry with sibling keys", () => {
1196
+ const input = {
1197
+ anyOf: [
1198
+ { anyOf: [{ type: "string" }], title: "X" },
1199
+ { type: "null" }
1200
+ ]
1201
+ }
1202
+ // The inner anyOf is not flattened into the outer (sibling "title" prevents it),
1203
+ // but the single-element inner anyOf is unwrapped within the entry itself
1204
+ expect(flattenNestedAnyOf(input)).toStrictEqual({
1205
+ anyOf: [
1206
+ { type: "string", title: "X" },
1207
+ { type: "null" }
1208
+ ]
1209
+ })
1210
+ })
1211
+
1212
+ it("unwraps anyOf with single item after flattening", () => {
1213
+ const input = {
1214
+ anyOf: [
1215
+ { anyOf: [{ type: "string" }] }
1216
+ ]
1217
+ }
1218
+ expect(flattenNestedAnyOf(input)).toStrictEqual({ type: "string" })
1219
+ })
1220
+
1221
+ it("unwraps anyOf with single item, merging sibling properties", () => {
1222
+ const input = {
1223
+ title: "MyField",
1224
+ anyOf: [{ type: "string" }]
1225
+ }
1226
+ expect(flattenNestedAnyOf(input)).toStrictEqual({
1227
+ title: "MyField",
1228
+ type: "string"
1229
+ })
1230
+ })
1231
+
1232
+ it("recurses into nested objects", () => {
1233
+ const input = {
1234
+ properties: {
1235
+ field: {
1236
+ anyOf: [
1237
+ { anyOf: [{ $ref: "#/defs/A" }, { $ref: "#/defs/B" }] },
1238
+ { type: "null" }
1239
+ ]
1240
+ }
1241
+ }
1242
+ }
1243
+ expect(flattenNestedAnyOf(input)).toStrictEqual({
1244
+ properties: {
1245
+ field: {
1246
+ anyOf: [
1247
+ { $ref: "#/defs/A" },
1248
+ { $ref: "#/defs/B" },
1249
+ { type: "null" }
1250
+ ]
1251
+ }
1252
+ }
1253
+ })
1254
+ })
1255
+
1256
+ it("passes through non-objects unchanged", () => {
1257
+ expect(flattenNestedAnyOf(null)).toBe(null)
1258
+ expect(flattenNestedAnyOf(42)).toBe(42)
1259
+ expect(flattenNestedAnyOf("hello")).toBe("hello")
1260
+ })
1261
+ })