effect 4.0.0-rc.118 → 4.0.1

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 (254) hide show
  1. package/README.md +17 -4
  2. package/dist/Array.d.ts +25 -25
  3. package/dist/Array.d.ts.map +1 -1
  4. package/dist/Array.js +16 -16
  5. package/dist/Array.js.map +1 -1
  6. package/dist/Cache.d.ts.map +1 -1
  7. package/dist/Cache.js +20 -16
  8. package/dist/Cache.js.map +1 -1
  9. package/dist/Cause.d.ts.map +1 -1
  10. package/dist/Cause.js +1 -1
  11. package/dist/Cause.js.map +1 -1
  12. package/dist/Chunk.d.ts +26 -26
  13. package/dist/Chunk.d.ts.map +1 -1
  14. package/dist/Chunk.js +15 -15
  15. package/dist/Chunk.js.map +1 -1
  16. package/dist/Effect.d.ts +167 -39
  17. package/dist/Effect.d.ts.map +1 -1
  18. package/dist/Effect.js +16 -10
  19. package/dist/Effect.js.map +1 -1
  20. package/dist/HashRing.d.ts.map +1 -1
  21. package/dist/HashRing.js +13 -5
  22. package/dist/HashRing.js.map +1 -1
  23. package/dist/MutableHashMap.d.ts.map +1 -1
  24. package/dist/MutableHashMap.js +4 -22
  25. package/dist/MutableHashMap.js.map +1 -1
  26. package/dist/Option.d.ts +17 -17
  27. package/dist/Option.d.ts.map +1 -1
  28. package/dist/Option.js +6 -6
  29. package/dist/Option.js.map +1 -1
  30. package/dist/Pool.d.ts +9 -5
  31. package/dist/Pool.d.ts.map +1 -1
  32. package/dist/Pool.js +34 -41
  33. package/dist/Pool.js.map +1 -1
  34. package/dist/Queue.d.ts +19 -2
  35. package/dist/Queue.d.ts.map +1 -1
  36. package/dist/Queue.js +9 -4
  37. package/dist/Queue.js.map +1 -1
  38. package/dist/RcMap.d.ts.map +1 -1
  39. package/dist/RcMap.js +16 -1
  40. package/dist/RcMap.js.map +1 -1
  41. package/dist/Record.d.ts +14 -14
  42. package/dist/Record.d.ts.map +1 -1
  43. package/dist/Record.js +10 -10
  44. package/dist/Record.js.map +1 -1
  45. package/dist/Schema.d.ts +37 -21
  46. package/dist/Schema.d.ts.map +1 -1
  47. package/dist/Schema.js +29 -10
  48. package/dist/Schema.js.map +1 -1
  49. package/dist/SchemaAST.d.ts.map +1 -1
  50. package/dist/SchemaAST.js +116 -97
  51. package/dist/SchemaAST.js.map +1 -1
  52. package/dist/SchemaRepresentation.d.ts +3 -1
  53. package/dist/SchemaRepresentation.d.ts.map +1 -1
  54. package/dist/SchemaRepresentation.js +3 -1
  55. package/dist/SchemaRepresentation.js.map +1 -1
  56. package/dist/ScopedCache.d.ts.map +1 -1
  57. package/dist/ScopedCache.js +24 -16
  58. package/dist/ScopedCache.js.map +1 -1
  59. package/dist/Stream.d.ts +45 -6
  60. package/dist/Stream.d.ts.map +1 -1
  61. package/dist/Stream.js +55 -44
  62. package/dist/Stream.js.map +1 -1
  63. package/dist/ai/LanguageModel.d.ts +8 -0
  64. package/dist/ai/LanguageModel.d.ts.map +1 -1
  65. package/dist/ai/LanguageModel.js +13 -2
  66. package/dist/ai/LanguageModel.js.map +1 -1
  67. package/dist/ai/McpSchema.d.ts +402 -402
  68. package/dist/ai/McpSchema.d.ts.map +1 -1
  69. package/dist/ai/McpSchema.js +1 -1
  70. package/dist/ai/McpSchema.js.map +1 -1
  71. package/dist/ai/McpServer.d.ts.map +1 -1
  72. package/dist/ai/McpServer.js +11 -3
  73. package/dist/ai/McpServer.js.map +1 -1
  74. package/dist/ai/Response.d.ts.map +1 -1
  75. package/dist/ai/Response.js +22 -12
  76. package/dist/ai/Response.js.map +1 -1
  77. package/dist/ai/internal/mcpProtocol/v2024_11_05.js +1 -0
  78. package/dist/ai/internal/mcpProtocol/v2024_11_05.js.map +1 -1
  79. package/dist/ai/internal/mcpProtocol/v2025_03_26.js +1 -0
  80. package/dist/ai/internal/mcpProtocol/v2025_03_26.js.map +1 -1
  81. package/dist/ai/internal/mcpProtocol/v2025_06_18.js +1 -0
  82. package/dist/ai/internal/mcpProtocol/v2025_06_18.js.map +1 -1
  83. package/dist/ai/internal/mcpProtocol/v2025_11_25.js +1 -0
  84. package/dist/ai/internal/mcpProtocol/v2025_11_25.js.map +1 -1
  85. package/dist/ai/internal/mcpSchema/v2024_11_05.js +3 -1
  86. package/dist/ai/internal/mcpSchema/v2024_11_05.js.map +1 -1
  87. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  88. package/dist/cluster/ClusterWorkflowEngine.js +16 -3
  89. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  90. package/dist/cluster/MessageStorage.d.ts +32 -8
  91. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  92. package/dist/cluster/MessageStorage.js +2 -1
  93. package/dist/cluster/MessageStorage.js.map +1 -1
  94. package/dist/cluster/Sharding.d.ts +5 -2
  95. package/dist/cluster/Sharding.d.ts.map +1 -1
  96. package/dist/cluster/Sharding.js +15 -8
  97. package/dist/cluster/Sharding.js.map +1 -1
  98. package/dist/cluster/SqlMessageStorage.d.ts.map +1 -1
  99. package/dist/cluster/SqlMessageStorage.js +36 -16
  100. package/dist/cluster/SqlMessageStorage.js.map +1 -1
  101. package/dist/cluster/SqlRunnerStorage.d.ts.map +1 -1
  102. package/dist/cluster/SqlRunnerStorage.js +30 -44
  103. package/dist/cluster/SqlRunnerStorage.js.map +1 -1
  104. package/dist/cluster/internal/entityManager.js +63 -62
  105. package/dist/cluster/internal/entityManager.js.map +1 -1
  106. package/dist/cluster/internal/interruptors.js +8 -0
  107. package/dist/cluster/internal/interruptors.js.map +1 -1
  108. package/dist/cluster/internal/resourceMap.js +39 -14
  109. package/dist/cluster/internal/resourceMap.js.map +1 -1
  110. package/dist/cluster/internal/resourceRef.js +168 -87
  111. package/dist/cluster/internal/resourceRef.js.map +1 -1
  112. package/dist/http/HttpEffect.d.ts +1 -0
  113. package/dist/http/HttpEffect.d.ts.map +1 -1
  114. package/dist/http/HttpEffect.js +29 -22
  115. package/dist/http/HttpEffect.js.map +1 -1
  116. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  117. package/dist/http/HttpMiddleware.js +3 -2
  118. package/dist/http/HttpMiddleware.js.map +1 -1
  119. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  120. package/dist/http/HttpStaticServer.js +14 -0
  121. package/dist/http/HttpStaticServer.js.map +1 -1
  122. package/dist/http/HttpTraceContext.d.ts +1 -1
  123. package/dist/http/HttpTraceContext.d.ts.map +1 -1
  124. package/dist/http/HttpTraceContext.js +4 -4
  125. package/dist/http/HttpTraceContext.js.map +1 -1
  126. package/dist/http-api/HttpApiError.d.ts +1 -0
  127. package/dist/http-api/HttpApiError.d.ts.map +1 -1
  128. package/dist/http-api/HttpApiError.js +1 -0
  129. package/dist/http-api/HttpApiError.js.map +1 -1
  130. package/dist/internal/core.js +43 -2
  131. package/dist/internal/core.js.map +1 -1
  132. package/dist/internal/dateTime.js +1 -1
  133. package/dist/internal/dateTime.js.map +1 -1
  134. package/dist/internal/effect.js +138 -165
  135. package/dist/internal/effect.js.map +1 -1
  136. package/dist/internal/rcRef.js +59 -23
  137. package/dist/internal/rcRef.js.map +1 -1
  138. package/dist/internal/schema/annotations.js +1 -7
  139. package/dist/internal/schema/annotations.js.map +1 -1
  140. package/dist/internal/schema/codegen.js +56 -28
  141. package/dist/internal/schema/codegen.js.map +1 -1
  142. package/dist/internal/schema/fromJsonSchemaDocument.js +11 -8
  143. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  144. package/dist/internal/schema/toCodeDocument.js +10 -30
  145. package/dist/internal/schema/toCodeDocument.js.map +1 -1
  146. package/dist/internal/schema/toCodec.js +1 -15
  147. package/dist/internal/schema/toCodec.js.map +1 -1
  148. package/dist/internal/schema/toEquivalence.js +4 -3
  149. package/dist/internal/schema/toEquivalence.js.map +1 -1
  150. package/dist/internal/schema/toFormatter.js +4 -4
  151. package/dist/internal/schema/toFormatter.js.map +1 -1
  152. package/dist/reactivity/Atom.d.ts.map +1 -1
  153. package/dist/reactivity/Atom.js +6 -2
  154. package/dist/reactivity/Atom.js.map +1 -1
  155. package/dist/reactivity/AtomRpc.d.ts +3 -3
  156. package/dist/reactivity/AtomRpc.d.ts.map +1 -1
  157. package/dist/rpc/Rpc.d.ts +2 -2
  158. package/dist/rpc/Rpc.d.ts.map +1 -1
  159. package/dist/rpc/RpcClient.d.ts +7 -7
  160. package/dist/rpc/RpcClient.d.ts.map +1 -1
  161. package/dist/rpc/RpcClient.js +21 -16
  162. package/dist/rpc/RpcClient.js.map +1 -1
  163. package/dist/rpc/RpcMessage.d.ts +1 -1
  164. package/dist/rpc/RpcMessage.d.ts.map +1 -1
  165. package/dist/rpc/RpcServer.d.ts.map +1 -1
  166. package/dist/rpc/RpcServer.js +2 -2
  167. package/dist/rpc/RpcServer.js.map +1 -1
  168. package/dist/rpc/Utils.d.ts +2 -3
  169. package/dist/rpc/Utils.d.ts.map +1 -1
  170. package/dist/rpc/Utils.js +3 -4
  171. package/dist/rpc/Utils.js.map +1 -1
  172. package/dist/schema/SchemaCompiler/runtime.js +6 -1
  173. package/dist/schema/SchemaCompiler/runtime.js.map +1 -1
  174. package/dist/testing/TestSchema.d.ts +234 -6
  175. package/dist/testing/TestSchema.d.ts.map +1 -1
  176. package/dist/testing/TestSchema.js +244 -25
  177. package/dist/testing/TestSchema.js.map +1 -1
  178. package/dist/testing/index.d.ts +1 -0
  179. package/dist/testing/index.d.ts.map +1 -1
  180. package/dist/testing/index.js +1 -0
  181. package/dist/testing/index.js.map +1 -1
  182. package/dist/workflow/DurableDeferred.js +1 -1
  183. package/dist/workflow/DurableDeferred.js.map +1 -1
  184. package/dist/workflow/Workflow.js +1 -1
  185. package/dist/workflow/Workflow.js.map +1 -1
  186. package/dist/workflow/WorkflowEngine.d.ts +2 -0
  187. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  188. package/dist/workflow/WorkflowEngine.js +1 -0
  189. package/dist/workflow/WorkflowEngine.js.map +1 -1
  190. package/package.json +1 -1
  191. package/src/Array.ts +31 -31
  192. package/src/Cache.ts +22 -16
  193. package/src/Cause.ts +1 -1
  194. package/src/Chunk.ts +29 -29
  195. package/src/Effect.ts +170 -39
  196. package/src/HashRing.ts +14 -5
  197. package/src/MutableHashMap.ts +4 -24
  198. package/src/Option.ts +19 -19
  199. package/src/Pool.ts +28 -36
  200. package/src/Queue.ts +26 -6
  201. package/src/RcMap.ts +26 -2
  202. package/src/Record.ts +20 -20
  203. package/src/Schema.ts +48 -24
  204. package/src/SchemaAST.ts +135 -115
  205. package/src/SchemaRepresentation.ts +3 -1
  206. package/src/ScopedCache.ts +27 -16
  207. package/src/Stream.ts +158 -88
  208. package/src/ai/LanguageModel.ts +27 -2
  209. package/src/ai/McpSchema.ts +3 -3
  210. package/src/ai/McpServer.ts +12 -3
  211. package/src/ai/Response.ts +27 -12
  212. package/src/ai/internal/mcpProtocol/v2024_11_05.ts +1 -0
  213. package/src/ai/internal/mcpProtocol/v2025_03_26.ts +1 -0
  214. package/src/ai/internal/mcpProtocol/v2025_06_18.ts +1 -0
  215. package/src/ai/internal/mcpProtocol/v2025_11_25.ts +1 -0
  216. package/src/ai/internal/mcpSchema/v2024_11_05.ts +4 -2
  217. package/src/cluster/ClusterWorkflowEngine.ts +26 -5
  218. package/src/cluster/MessageStorage.ts +22 -5
  219. package/src/cluster/Sharding.ts +32 -28
  220. package/src/cluster/SqlMessageStorage.ts +45 -21
  221. package/src/cluster/SqlRunnerStorage.ts +85 -113
  222. package/src/cluster/internal/entityManager.ts +68 -80
  223. package/src/cluster/internal/interruptors.ts +13 -0
  224. package/src/cluster/internal/resourceMap.ts +55 -17
  225. package/src/cluster/internal/resourceRef.ts +189 -96
  226. package/src/http/HttpEffect.ts +15 -9
  227. package/src/http/HttpMiddleware.ts +8 -2
  228. package/src/http/HttpStaticServer.ts +16 -0
  229. package/src/http/HttpTraceContext.ts +8 -4
  230. package/src/http-api/HttpApiError.ts +1 -0
  231. package/src/internal/core.ts +56 -2
  232. package/src/internal/dateTime.ts +3 -1
  233. package/src/internal/effect.ts +146 -173
  234. package/src/internal/rcRef.ts +83 -33
  235. package/src/internal/schema/annotations.ts +0 -9
  236. package/src/internal/schema/codegen.ts +90 -43
  237. package/src/internal/schema/fromJsonSchemaDocument.ts +5 -3
  238. package/src/internal/schema/toCodeDocument.ts +10 -47
  239. package/src/internal/schema/toCodec.ts +3 -16
  240. package/src/internal/schema/toEquivalence.ts +4 -5
  241. package/src/internal/schema/toFormatter.ts +4 -6
  242. package/src/reactivity/Atom.ts +6 -2
  243. package/src/reactivity/AtomRpc.ts +5 -5
  244. package/src/rpc/Rpc.ts +2 -2
  245. package/src/rpc/RpcClient.ts +35 -32
  246. package/src/rpc/RpcMessage.ts +1 -1
  247. package/src/rpc/RpcServer.ts +2 -1
  248. package/src/rpc/Utils.ts +4 -10
  249. package/src/schema/SchemaCompiler/runtime.ts +17 -1
  250. package/src/testing/TestSchema.ts +315 -63
  251. package/src/testing/index.ts +1 -0
  252. package/src/workflow/DurableDeferred.ts +1 -1
  253. package/src/workflow/Workflow.ts +1 -1
  254. package/src/workflow/WorkflowEngine.ts +4 -0
@@ -7,17 +7,26 @@ import { identity } from "../Function.ts"
7
7
  import { pipeArguments } from "../Pipeable.ts"
8
8
  import type * as RcRef from "../RcRef.ts"
9
9
  import * as Scope from "../Scope.ts"
10
- import * as Semaphore from "../Semaphore.ts"
11
10
 
12
11
  const TypeId = "~effect/RcRef"
13
12
 
14
- type State<A> = State.Empty | State.Acquired<A> | State.Closed
13
+ type State<A> = State.Empty | State.Acquiring<A> | State.Acquired<A> | State.Closed
15
14
 
16
15
  declare namespace State {
17
16
  interface Empty {
18
17
  readonly _tag: "Empty"
19
18
  }
20
19
 
20
+ interface Acquiring<A> {
21
+ readonly _tag: "Acquiring"
22
+ // Assigned as soon as the acquisition is forked.
23
+ fiber: Fiber.Fiber<Acquired<A>, unknown>
24
+ awaiters: number
25
+ // Set once references are allocated to the waiters, which can happen
26
+ // before the acquisition fiber exits.
27
+ acquired: Acquired<A> | undefined
28
+ }
29
+
21
30
  interface Acquired<A> {
22
31
  readonly _tag: "Acquired"
23
32
  readonly value: A
@@ -48,7 +57,6 @@ class RcRefImpl<A, E> implements RcRef.RcRef<A, E> {
48
57
  }
49
58
 
50
59
  state: State<A> = stateEmpty
51
- readonly semaphore = Semaphore.makeUnsafe(1)
52
60
  readonly acquire: Effect.Effect<A, E>
53
61
  readonly context: Context.Context<never>
54
62
  readonly scope: Scope.Scope
@@ -85,11 +93,11 @@ export const make = <A, E, R>(options: {
85
93
  )
86
94
  return Effect.as(
87
95
  Scope.addFinalizerExit(scope, () => {
88
- const close = ref.state._tag === "Acquired"
89
- ? Scope.close(ref.state.scope, Exit.void)
90
- : Effect.void
96
+ const state = ref.state
91
97
  ref.state = stateClosed
92
- return close
98
+ if (state._tag === "Acquired") return Scope.close(state.scope, Exit.void)
99
+ if (state._tag === "Acquiring") return Fiber.interrupt(state.fiber)
100
+ return Effect.void
93
101
  }),
94
102
  ref
95
103
  )
@@ -110,40 +118,82 @@ const getState = <A, E>(
110
118
  ? Effect.as(Fiber.interrupt(state.fiber), state)
111
119
  : Effect.succeed(state)
112
120
  }
121
+ case "Acquiring": {
122
+ const state = self.state
123
+ state.awaiters++
124
+ return awaitAcquiring(self, state, restore)
125
+ }
113
126
  case "Empty": {
114
- return self.semaphore.withPermit(
115
- Effect.suspend(() => {
116
- if (self.state._tag !== "Empty") {
117
- return getState(self, restore)
127
+ // The caller that starts the acquisition counts as its first awaiter.
128
+ const acquiring: State.Acquiring<A> = {
129
+ _tag: "Acquiring",
130
+ fiber: undefined as any,
131
+ awaiters: 1,
132
+ acquired: undefined
133
+ }
134
+ self.state = acquiring
135
+ const scope = Scope.makeUnsafe()
136
+ // Acquire on a detached fiber so the acquisition is shared by every
137
+ // caller and only interrupted once all of them have been interrupted.
138
+ const acquire = Effect.provideContext(
139
+ self.acquire as Effect.Effect<A, E>,
140
+ Context.add(self.context, Scope.Scope, scope)
141
+ ).pipe(
142
+ Effect.flatMap((value) => {
143
+ // The ref was closed, or the acquisition was abandoned.
144
+ if (self.state !== acquiring) {
145
+ return Effect.interrupt
146
+ }
147
+ const state: State.Acquired<A> = {
148
+ _tag: "Acquired",
149
+ value,
150
+ scope,
151
+ fiber: undefined,
152
+ // Every caller still waiting holds a reference.
153
+ refCount: acquiring.awaiters,
154
+ invalidated: false
118
155
  }
119
- const scope = Scope.makeUnsafe()
120
- return restore(Effect.provideContext(
121
- self.acquire as Effect.Effect<A, E>,
122
- Context.add(self.context, Scope.Scope, scope)
123
- )).pipe(
124
- Effect.flatMap((value) => {
125
- if (self.state._tag === "Closed") {
126
- return Effect.interrupt
127
- }
128
- const state: State.Acquired<A> = {
129
- _tag: "Acquired",
130
- value,
131
- scope,
132
- fiber: undefined,
133
- refCount: 1,
134
- invalidated: false
135
- }
136
- self.state = state
137
- return Effect.succeed(state)
138
- }),
139
- Effect.onExit((exit) => Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void)
140
- )
156
+ self.state = state
157
+ acquiring.acquired = state
158
+ return Effect.succeed(state)
159
+ }),
160
+ Effect.onExit((exit) => {
161
+ if (Exit.isSuccess(exit)) return Effect.void
162
+ if (self.state === acquiring) self.state = stateEmpty
163
+ return Scope.close(scope, exit)
141
164
  })
142
165
  )
166
+ acquiring.fiber = Effect.runForkWith(Fiber.getCurrent()!.context)(acquire)
167
+ return awaitAcquiring(self, acquiring, restore)
143
168
  }
144
169
  }
145
170
  }
146
171
 
172
+ const awaitAcquiring = <A, E>(
173
+ self: RcRefImpl<A, E>,
174
+ acquiring: State.Acquiring<A>,
175
+ restore: <AX, EX, RX>(effect: Effect.Effect<AX, EX, RX>) => Effect.Effect<AX, EX, RX>
176
+ ): Effect.Effect<State.Acquired<A>, E> =>
177
+ Effect.onExit(
178
+ restore(Fiber.join(acquiring.fiber)) as Effect.Effect<State.Acquired<A>, E>,
179
+ (exit) => {
180
+ const acquired = acquiring.acquired
181
+ if (acquired !== undefined) {
182
+ // The acquisition counted this caller as a reference, possibly before
183
+ // its fiber exited. Release it if the caller was interrupted before it
184
+ // could register its finalizer.
185
+ return Exit.isFailure(exit) ? release(self, acquired) : Effect.void
186
+ }
187
+ // Abandon the acquisition once every caller has left, unless it already
188
+ // failed (a failed acquisition allocated no references).
189
+ if (--acquiring.awaiters > 0 || self.state !== acquiring) return Effect.void
190
+ // Detach it first so new callers start a fresh acquisition instead of
191
+ // joining one that is being interrupted.
192
+ self.state = stateEmpty
193
+ return Fiber.interrupt(acquiring.fiber)
194
+ }
195
+ )
196
+
147
197
  /** @internal */
148
198
  export const get = <A, E>(self_: RcRef.RcRef<A, E>): Effect.Effect<A, E, Scope.Scope> => {
149
199
  const self = self_ as RcRefImpl<A, E>
@@ -50,9 +50,6 @@ export const resolveTitle = resolveAt<string>("title")
50
50
  /** @internal */
51
51
  export const resolveDescription = resolveAt<string>("description")
52
52
 
53
- /** @internal */
54
- export const resolveBrands = resolveAt<ReadonlyArray<string>>("brands")
55
-
56
53
  /** @internal */
57
54
  export const getExpected = memoize((ast: SchemaAST.AST): string => {
58
55
  const identifier = resolve(ast)?.identifier
@@ -60,18 +57,12 @@ export const getExpected = memoize((ast: SchemaAST.AST): string => {
60
57
  return ast.getExpected(getExpected)
61
58
  })
62
59
 
63
- /** @internal */
64
- export function collectBrands(annotations: Schema.Annotations.Annotations | undefined): ReadonlyArray<string> {
65
- return annotations !== undefined && Array.isArray(annotations.brands) ? annotations.brands : []
66
- }
67
-
68
60
  /** @internal */
69
61
  export const annotationExcludedKeys = new Set([
70
62
  SENTINELS_ANNOTATION_KEY,
71
63
  STRUCTURAL_ANNOTATION_KEY,
72
64
  "representation",
73
65
  "arbitraryConstraint",
74
- "brands",
75
66
  "toJsonSchema",
76
67
  "toCode",
77
68
  "toEquivalence",
@@ -22,13 +22,21 @@ type Facts = {
22
22
  supported: boolean
23
23
  outputFree: boolean
24
24
  makeSafe: boolean
25
+ hasOneOf: boolean
25
26
  nodes: number
26
27
  height: number
27
28
  }
28
29
 
29
30
  const factsCache = new WeakMap<SchemaAST.AST, Facts>()
30
31
 
31
- const unsupportedFacts: Facts = { supported: false, outputFree: false, makeSafe: false, nodes: 1, height: 0 }
32
+ const unsupportedFacts: Facts = {
33
+ supported: false,
34
+ outputFree: false,
35
+ makeSafe: false,
36
+ hasOneOf: false,
37
+ nodes: 1,
38
+ height: 0
39
+ }
32
40
 
33
41
  const isMakeSafeNode = (ast: SchemaAST.AST): boolean =>
34
42
  ast._tag !== "Union" && ast._tag !== "Declaration" && ast._tag !== "Suspend" &&
@@ -59,6 +67,7 @@ const addChild = (
59
67
  }
60
68
  if (!childFacts.outputFree) facts.outputFree = false
61
69
  if (!childFacts.makeSafe) facts.makeSafe = false
70
+ if (childFacts.hasOneOf) facts.hasOneOf = true
62
71
  facts.nodes += childFacts.nodes
63
72
  if (childFacts.height >= facts.height) facts.height = childFacts.height + 1
64
73
  return true
@@ -89,13 +98,25 @@ const getFacts = (
89
98
  return budget.remaining < 0 || depth + cached.height > maxGeneratedDepth ? undefined : cached
90
99
  }
91
100
  if (--budget.remaining < 0 || depth > maxGeneratedDepth) return undefined
92
- const facts: Facts = { supported: true, outputFree: true, makeSafe: true, nodes: 1, height: 0 }
101
+ const facts: Facts = {
102
+ supported: true,
103
+ outputFree: true,
104
+ makeSafe: true,
105
+ hasOneOf: ast._tag === "Union" && ast.options?.mode === "oneOf",
106
+ nodes: 1,
107
+ height: 0
108
+ }
93
109
  const next = depth + 1
94
110
  switch (ast._tag) {
95
111
  case "TemplateLiteral":
96
112
  for (const part of ast.parts) {
97
113
  if (!addChild(facts, part, next, budget)) return stop(ast, facts)
98
114
  }
115
+ // matchPart only segments the input; the tuple parser enforces oneOf.
116
+ if (facts.hasOneOf) {
117
+ facts.supported = false
118
+ return stop(ast, facts)
119
+ }
99
120
  break
100
121
  case "Arrays":
101
122
  for (const element of ast.elements) {
@@ -297,12 +318,10 @@ const emitUnionHelper = (ast: SchemaAST.Union, emitter: Emitter, operation: Oper
297
318
  if (cached !== undefined) return cached
298
319
  const name = `u${emitter.next++}`
299
320
  emitter.unionHelpers.set(ast, name)
300
- const entries = ast.types.map((type, index) =>
301
- `[${constant(emitter, type, `${path}.types[${index}]`)},${
302
- emitDecoderHelper(type, emitter, operation, `${path}.types[${index}]`)
303
- }]`
321
+ const decoders = ast.types.map((type, index) =>
322
+ emitDecoderHelper(type, emitter, operation, `${path}.types[${index}]`)
304
323
  )
305
- emitter.initializers.push(`const ${name}=new Map([${entries.join(",")}])`)
324
+ emitter.initializers.push(`const ${name}=[${decoders.join(",")}]`)
306
325
  return name
307
326
  }
308
327
 
@@ -607,20 +626,22 @@ const emitBase = (
607
626
  const decoder = variable(emitter)
608
627
  const types = constant(emitter, ast.types, `${path}.types`)
609
628
  const decoders = emitUnionHelper(ast, emitter, operation, path)
629
+ const select = variable(emitter)
630
+ emitter.initializers.push(`const ${select}=U(${types})`)
610
631
  statements.push(
611
- `const ${candidates}=U(${input},${types})`,
632
+ `const ${candidates}=${select}(${input},false)`,
612
633
  `let ${output}=${invalid},${candidate},${decoder}`
613
634
  )
614
635
  if (ast.options?.mode !== "oneOf") {
615
636
  statements.push(
616
- `for(let ${index}=0;${index}<${candidates}.length;${index}++){${decoder}=${decoders}.get(${candidates}[${index}]);${candidate}=${decoder}(${input},o);if(${candidate}!==${invalid}){${output}=${candidate};break}}`
637
+ `for(let ${index}=0;${index}<${candidates}.length;${index}++){${decoder}=${decoders}[${candidates}[${index}]];${candidate}=${decoder}(${input},o);if(${candidate}!==${invalid}){${output}=${candidate};break}}`
617
638
  )
618
639
  statements.push(`if(${output}===${invalid})return ${invalid}`)
619
640
  } else {
620
641
  const successes = variable(emitter)
621
642
  statements.push(`let ${successes}=0`)
622
643
  statements.push(
623
- `for(let ${index}=0;${index}<${candidates}.length;${index}++){${decoder}=${decoders}.get(${candidates}[${index}]);${candidate}=${decoder}(${input},o);if(${candidate}!==${invalid}){if(++${successes}>1)return ${invalid};${output}=${candidate}}}`
644
+ `for(let ${index}=0;${index}<${candidates}.length;${index}++){${decoder}=${decoders}[${candidates}[${index}]];${candidate}=${decoder}(${input},o);if(${candidate}!==${invalid}){if(++${successes}>1)return ${invalid};${output}=${candidate}}}`
624
645
  )
625
646
  statements.push(`if(${successes}!==1)return ${invalid}`)
626
647
  }
@@ -664,7 +685,7 @@ const emitOperation = (ast: SchemaAST.AST, operation: Operation, path = "ast"):
664
685
  const bindings = {
665
686
  K: "getCheckIssues",
666
687
  T: "matchesTemplateLiteral",
667
- U: "getCandidates",
688
+ U: "getCandidateIndex",
668
689
  G: "getIndexSignatureKeys",
669
690
  D: "defaultParseOptions",
670
691
  E: "hasExcessProperties"
@@ -761,17 +782,13 @@ const inlineTypePredicate = (ast: SchemaAST.AST, input: string, path: string): s
761
782
  case "BigInt":
762
783
  return `typeof ${input}==="bigint"`
763
784
  case "TemplateLiteral":
764
- return `R.matchesTemplateLiteral(${path},${input},o)`
785
+ // Its parser reports structured issues, not an InvalidType failure.
786
+ return undefined
765
787
  default:
766
788
  return undefined
767
789
  }
768
790
  }
769
791
 
770
- const inlineIdentityPredicate = (ast: SchemaAST.AST, input: string, path: string): string | undefined =>
771
- ast.checks === undefined && getEncodingChecks(ast) === undefined
772
- ? inlineTypePredicate(ast, input, path)
773
- : undefined
774
-
775
792
  const emitEncoding = (ast: SchemaAST.AST): string | undefined => {
776
793
  const links = ast.encoding
777
794
  if (links?.length !== 1 || getEncodingChecks(ast) !== undefined) return undefined
@@ -779,7 +796,6 @@ const emitEncoding = (ast: SchemaAST.AST): string | undefined => {
779
796
  const transformation = link.transformation
780
797
  if (
781
798
  link.to.encoding !== undefined ||
782
- link.to.checks !== undefined ||
783
799
  getEncodingChecks(link.to) !== undefined ||
784
800
  transformation._tag !== "Transformation" ||
785
801
  transformation.decode._tag !== "Transform"
@@ -790,10 +806,14 @@ const emitEncoding = (ast: SchemaAST.AST): string | undefined => {
790
806
  const target = inlineTypePredicate(ast, "value", "ast")
791
807
  if (source === undefined || target === undefined) return undefined
792
808
  const success = ast.checks === undefined ? "R.succeed(value)" : "R.check(ast,value,o)"
793
- return `const transform=ast.encoding[0].transformation.decode.transform;return function(i,o){try{
809
+ const sourceChecks = link.to.checks === undefined
810
+ ? ""
811
+ : "const issues=R.getCheckIssues(from,i,false,o);" +
812
+ "if(issues!==void 0)return R.invalidEncodingChecks(ast,i,issues,o);"
813
+ return `const transform=ast.encoding[0].transformation.decode.transform,from=ast.encoding[0].to;return function(i,o){try{
794
814
  if(i===R.missing)return R.missingExit;
795
815
  if(!(${source}))return R.invalidEncoding(ast,1,i,i,o);
796
- const value=transform(i);
816
+ ${sourceChecks}const value=transform(i);
797
817
  if(value===R.missing)return R.missingExit;
798
818
  if(!(${target}))return R.invalidEncoding(ast,0,i,value,o);
799
819
  return ${success}
@@ -804,10 +824,13 @@ const canInlineEncoding = (ast: SchemaAST.AST): ast is SchemaAST.AST & { readonl
804
824
  ast.encoding !== undefined &&
805
825
  ast.checks === undefined &&
806
826
  getEncodingChecks(ast) === undefined &&
807
- inlineIdentityPredicate(ast, "v", "ast") !== undefined &&
808
- ast.encoding.every((link) =>
827
+ inlineTypePredicate(ast, "v", "ast") !== undefined &&
828
+ ast.encoding.every((link, index, links) =>
809
829
  link.to.encoding === undefined &&
810
- inlineIdentityPredicate(link.to, "v", "ast") !== undefined &&
830
+ getEncodingChecks(link.to) === undefined &&
831
+ // Only the initial source may have checks; they run before any transformation.
832
+ (link.to.checks === undefined || index === links.length - 1) &&
833
+ inlineTypePredicate(link.to, "v", "ast") !== undefined &&
811
834
  link.transformation._tag === "Transformation" &&
812
835
  (link.transformation.decode._tag === "Passthrough" || link.transformation.decode._tag === "Transform")
813
836
  )
@@ -818,8 +841,18 @@ const inlinePropertyHandler =
818
841
  const emitObject = (ast: SchemaAST.Objects): string => {
819
842
  const initializers: Array<string> = []
820
843
  const transforms = new Map<object, string>()
844
+ const bindTransform = (transform: object, path: string): string => {
845
+ let name = transforms.get(transform)
846
+ if (name === undefined) {
847
+ name = `t${transforms.size}`
848
+ transforms.set(transform, name)
849
+ initializers.push(`const ${name}=${path}`)
850
+ }
851
+ return name
852
+ }
821
853
  let usesInlinePropertyHandler = false
822
854
  let usesInlinePropertyFailure = false
855
+ let usesInlineSourceFailure = false
823
856
  const lazyProperties = ast.propertySignatures.every((property) =>
824
857
  typeof property.name !== "symbol" && canInlineEncoding(property.type)
825
858
  )
@@ -845,51 +878,60 @@ const emitObject = (ast: SchemaAST.Objects): string => {
845
878
  const descriptor = lazyProperties ? `p(${index})` : `p${index}`
846
879
  const handleInline = `t=handle(state,${index},${descriptor},h${index},v${index},r);if(t)return t`
847
880
  const links = property.type.encoding
881
+ const sourceAst = links[links.length - 1].to
848
882
  const sourcePath = `${propertyPath}.encoding[${links.length - 1}].to`
849
- const source = inlineIdentityPredicate(links[links.length - 1].to, `v${index}`, sourcePath)!
883
+ const source = inlineTypePredicate(sourceAst, `v${index}`, sourcePath)!
884
+ const sourceChecks = sourceAst.checks !== undefined
885
+ if (sourceChecks) initializers.push(`const s${index}=${sourcePath}`)
886
+ const checkSource = sourceChecks ? `const c${index}=R.getCheckIssues(s${index},v${index},false,o);` : ""
850
887
  // Share cold diagnostics without adding a call to the successful path.
851
888
  if (lazyProperties && links.length === 1 && property.name !== "__proto__") {
852
889
  usesInlinePropertyFailure = true
853
890
  const transformation = links[0].transformation
854
891
  let decoded = `v${index}`
855
892
  if (transformation._tag === "Transformation" && transformation.decode._tag === "Transform") {
856
- let transform = transforms.get(transformation.decode.transform)
857
- if (transform === undefined) {
858
- transform = `t${transforms.size}`
859
- transforms.set(transformation.decode.transform, transform)
860
- initializers.push(`const ${transform}=${propertyPath}.encoding[0].transformation.decode.transform`)
861
- }
893
+ const transform = bindTransform(
894
+ transformation.decode.transform,
895
+ `${propertyPath}.encoding[0].transformation.decode.transform`
896
+ )
862
897
  decoded = `${transform}(v${index})`
863
898
  }
864
- const predicate = inlineIdentityPredicate(property.type, `x${index}`, propertyPath)!
899
+ const predicate = inlineTypePredicate(property.type, `x${index}`, propertyPath)!
900
+ let run = `const x${index}=${decoded};` +
901
+ `if(x${index}!==R.missing&&(${predicate})){out[${key}]=x${index}}` +
902
+ `else{t=invalid(state,${index},h${index},v${index},x${index},o);if(t)return t}`
903
+ if (sourceChecks) {
904
+ usesInlineSourceFailure = true
905
+ run = `${checkSource}if(c${index}===void 0){${run}}` +
906
+ `else{t=invalidSource(state,${index},h${index},v${index},c${index},o);if(t)return t}`
907
+ }
865
908
  statements.push(
866
909
  `const h${index}=${present},v${index}=h${index}?i[${key}]:R.missing`,
867
- `if(v${index}!==R.missing&&(${source})){const x${index}=${decoded};` +
868
- `if(x${index}!==R.missing&&(${predicate})){out[${key}]=x${index}}` +
869
- `else{t=invalid(state,${index},h${index},v${index},x${index},o);if(t)return t}}` +
910
+ `if(v${index}!==R.missing&&(${source})){${run}}` +
870
911
  `else{t=fallbackProperty(state,${index},h${index},v${index},o);if(t)return t}`
871
912
  )
872
913
  return
873
914
  }
874
915
  const fast: Array<string> = [`let x${index}=v${index};r=void 0;l${index}:{`]
916
+ if (sourceChecks) {
917
+ fast.push(
918
+ `${checkSource}if(c${index}!==void 0){r=R.invalidEncodingChecks(${descriptor}.type,v${index},c${index},o);break l${index}}`
919
+ )
920
+ }
875
921
  for (let linkIndex = links.length - 1; linkIndex >= 0; linkIndex--) {
876
922
  const transformation = links[linkIndex].transformation
877
923
  if (transformation._tag === "Transformation" && transformation.decode._tag === "Transform") {
878
- let transform = transforms.get(transformation.decode.transform)
879
- if (transform === undefined) {
880
- transform = `t${transforms.size}`
881
- transforms.set(transformation.decode.transform, transform)
882
- initializers.push(
883
- `const ${transform}=${propertyPath}.encoding[${linkIndex}].transformation.decode.transform`
884
- )
885
- }
924
+ const transform = bindTransform(
925
+ transformation.decode.transform,
926
+ `${propertyPath}.encoding[${linkIndex}].transformation.decode.transform`
927
+ )
886
928
  fast.push(`x${index}=${transform}(x${index})`)
887
929
  }
888
930
  const targetPath = linkIndex === 0
889
931
  ? propertyPath
890
932
  : `${propertyPath}.encoding[${linkIndex - 1}].to`
891
933
  const target = linkIndex === 0 ? property.type : links[linkIndex - 1].to
892
- const predicate = inlineIdentityPredicate(target, `x${index}`, targetPath)!
934
+ const predicate = inlineTypePredicate(target, `x${index}`, targetPath)!
893
935
  fast.push(
894
936
  `if(x${index}===R.missing){r=R.missingExit;break l${index}}else if(!(${predicate})){r=R.invalidEncoding(${descriptor}.type,${linkIndex},v${index},x${index},o);break l${index}}`
895
937
  )
@@ -924,6 +966,11 @@ const emitObject = (ast: SchemaAST.Objects): string => {
924
966
  "const fallbackProperty=(state,index,present,input,o)=>{const property=p(index);return handle(state,index,property,present,input,property.parser(input,o))}"
925
967
  )
926
968
  }
969
+ if (usesInlineSourceFailure) {
970
+ initializers.push(
971
+ "const invalidSource=(state,index,present,input,issues,o)=>{const property=p(index);return handle(state,index,property,present,input,R.invalidEncodingChecks(property.type,input,issues,o))}"
972
+ )
973
+ }
927
974
  return `function({ast,getProperties,fallback,resume,step}){${initializers.join(";")};return function(i,o){try{${
928
975
  statements.join(";")
929
976
  }}catch(e){return R.die(e)}}}`
@@ -1067,13 +1067,15 @@ function translateJsonSchemaMultiDocument(
1067
1067
  )
1068
1068
  case "ignore":
1069
1069
  return undefined
1070
- case "apply":
1070
+ case "apply": {
1071
+ let regex: RegExp
1071
1072
  try {
1072
- globalThis.RegExp(pattern, jsonSchemaPatternFlags)
1073
+ regex = globalThis.RegExp(pattern, jsonSchemaPatternFlags)
1073
1074
  } catch {
1074
1075
  throw errorWithPath("Cannot import pattern using ECMAScript Unicode mode.", path)
1075
1076
  }
1076
- return jsonSchemaFilter("effect/schema/isPattern", { source: pattern, flags: jsonSchemaPatternFlags })
1077
+ return jsonSchemaFilter("effect/schema/isPattern", { source: regex.source, flags: regex.flags })
1078
+ }
1077
1079
  }
1078
1080
  }
1079
1081
 
@@ -297,31 +297,6 @@ export function toCodeDocument(
297
297
  return representation?.schemas?.map((schema, index) => recur(schema, [...path, "schemas", index])) ?? []
298
298
  }
299
299
 
300
- function checkBrands(
301
- check: SchemaRepresentation.Check
302
- ): ReadonlyArray<string> {
303
- const own = InternalAnnotations.collectBrands(check.annotations)
304
- if (
305
- check._tag === "FilterGroup" &&
306
- check.annotations?.toCode === undefined
307
- ) {
308
- return [...own, ...check.checks.flatMap(checkBrands)]
309
- }
310
- return own
311
- }
312
-
313
- function runtimeBrands(brands: ReadonlyArray<string>): string {
314
- return brands.length === 0
315
- ? ""
316
- : `.pipe(${brands.map((brand) => `Schema.brand(${format(brand)})`).join(", ")})`
317
- }
318
-
319
- function typeBrands(brands: ReadonlyArray<string>): string {
320
- if (brands.length === 0) return ""
321
- addImport(`import type * as Brand from "effect/Brand"`)
322
- return brands.map((brand) => ` & Brand.Brand<${format(brand)}>`).join("")
323
- }
324
-
325
300
  function runtimeAnnotate(
326
301
  annotations: Schema.Annotations.Annotations | undefined,
327
302
  method: "annotate" | "annotateKey" = "annotate"
@@ -361,25 +336,19 @@ export function toCodeDocument(
361
336
  function applyNode(
362
337
  base: SchemaRepresentation.Code,
363
338
  representation: Exclude<SchemaRepresentation.Representation, SchemaRepresentation.Reference>,
364
- path: Path,
365
- includeTypeBrands: boolean = true
339
+ path: Path
366
340
  ): SchemaRepresentation.Code {
367
- const nodeBrands = InternalAnnotations.collectBrands(representation.annotations)
368
- let runtime = base.runtime + runtimeAnnotate(representation.annotations) + runtimeBrands(nodeBrands)
369
- let Type = base.Type + (includeTypeBrands ? typeBrands(nodeBrands) : "")
341
+ let runtime = base.runtime + runtimeAnnotate(representation.annotations)
370
342
  for (let index = 0; index < representation.checks.length; index++) {
371
343
  const check = representation.checks[index]
372
- const brands = checkBrands(check)
373
- runtime += `.check(${compileCheck(check, [...path, "checks", index])})${runtimeBrands(brands)}`
374
- if (includeTypeBrands) Type += typeBrands(brands)
344
+ runtime += `.check(${compileCheck(check, [...path, "checks", index])})`
375
345
  }
376
- return makeCode(runtime, Type)
346
+ return makeCode(runtime, base.Type)
377
347
  }
378
348
 
379
349
  function recur(
380
350
  representation: SchemaRepresentation.Representation,
381
- path: Path,
382
- includeTypeBrands: boolean = true
351
+ path: Path
383
352
  ): SchemaRepresentation.Code {
384
353
  if (representation._tag === "Reference") {
385
354
  if (!Object.hasOwn(document.references, representation.$ref)) {
@@ -392,18 +361,14 @@ export function toCodeDocument(
392
361
  ) {
393
362
  return makeCode(`Schema.suspend((): Schema.Codec<${identifier}> => ${identifier})`, identifier)
394
363
  }
395
- const Type = includeTypeBrands
396
- ? identifier
397
- : recur(document.references[representation.$ref], ["references", representation.$ref], false).Type
398
- return makeCode(identifier, Type)
364
+ return makeCode(identifier, identifier)
399
365
  }
400
- return applyNode(on(representation, path, includeTypeBrands), representation, path, includeTypeBrands)
366
+ return applyNode(on(representation, path), representation, path)
401
367
  }
402
368
 
403
369
  function on(
404
370
  representation: Exclude<SchemaRepresentation.Representation, SchemaRepresentation.Reference>,
405
- path: Path,
406
- includeTypeBrands: boolean
371
+ path: Path
407
372
  ): SchemaRepresentation.Code {
408
373
  switch (representation._tag) {
409
374
  case "Declaration": {
@@ -473,7 +438,7 @@ export function toCodeDocument(
473
438
  return makeCode(`Schema.Enum(${identifier})`, identifier)
474
439
  }
475
440
  case "TemplateLiteral": {
476
- const parts = representation.parts.map((part, index) => recur(part, [...path, "parts", index], false))
441
+ const parts = representation.parts.map((part, index) => recur(part, [...path, "parts", index]))
477
442
  const Type = `\`${parts.map((part) => `\${${part.Type}}`).join("")}\``
478
443
  return makeCode(`Schema.TemplateLiteral([${parts.map((part) => part.runtime).join(", ")}])`, Type)
479
444
  }
@@ -576,9 +541,7 @@ export function toCodeDocument(
576
541
  ? makeCode(`Schema.Literal(${literals[0]})`, literals[0])
577
542
  : makeCode(`Schema.Literals([${literals.join(", ")}])`, literals.join(" | "))
578
543
  }
579
- const types = representation.types.map((type, index) =>
580
- recur(type, [...path, "types", index], includeTypeBrands)
581
- )
544
+ const types = representation.types.map((type, index) => recur(type, [...path, "types", index]))
582
545
  const options = representation.options === undefined
583
546
  ? ""
584
547
  : `, ${unionOptionsRuntime(representation.options)}`
@@ -33,22 +33,9 @@ function validateCanonicalObjectPropertyNames(ast: SchemaAST.Objects): void {
33
33
 
34
34
  function makeReorder(getPriority: (ast: SchemaAST.AST) => number) {
35
35
  return (types: ReadonlyArray<SchemaAST.AST>): ReadonlyArray<SchemaAST.AST> => {
36
- // Create a map of original indices for O(1) lookup
37
- const indexMap = new Map<SchemaAST.AST, number>()
38
- for (let i = 0; i < types.length; i++) {
39
- indexMap.set(SchemaAST.toEncoded(types[i]), i)
40
- }
41
-
42
- // Create a sorted copy of the types array
43
- const sortedTypes = [...types].sort((a, b) => {
44
- a = SchemaAST.toEncoded(a)
45
- b = SchemaAST.toEncoded(b)
46
- const pa = getPriority(a)
47
- const pb = getPriority(b)
48
- if (pa !== pb) return pa - pb
49
- // If priorities are equal, maintain original order (stable sort)
50
- return indexMap.get(a)! - indexMap.get(b)!
51
- })
36
+ const sortedTypes = [...types].sort((a, b) =>
37
+ getPriority(SchemaAST.toEncoded(a)) - getPriority(SchemaAST.toEncoded(b))
38
+ )
52
39
 
53
40
  // Check if order changed by comparing arrays
54
41
  const orderChanged = sortedTypes.some((ast, index) => ast !== types[index])
@@ -136,13 +136,12 @@ function recur(ast: SchemaAST.AST): Equivalence.Equivalence<any> {
136
136
  }
137
137
  case "Union": {
138
138
  const types = SchemaAST.toType(ast).types
139
- const compiled = new Map(
140
- types.map((candidate, i) => [candidate, [SchemaParser._is(candidate), recur(ast.types[i])] as const] as const)
141
- )
139
+ const index = SchemaAST.getCandidateIndex(types)
140
+ const compiled = types.map((candidate, i) => [SchemaParser._is(candidate), recur(ast.types[i])] as const)
142
141
  return Equivalence.make((a, b) => {
143
- const candidates = SchemaAST.getCandidates(a, types)
142
+ const candidates = index(a, false)
144
143
  for (let i = 0; i < candidates.length; i++) {
145
- const [is, equivalence] = compiled.get(candidates[i])!
144
+ const [is, equivalence] = compiled[candidates[i]]
146
145
  if (is(a) && is(b)) {
147
146
  return equivalence(a, b)
148
147
  }
@@ -217,14 +217,12 @@ export function toFormatter<T>(ast: SchemaAST.AST, options?: {
217
217
  }
218
218
  case "Union": {
219
219
  const types = SchemaAST.toType(ast).types
220
- const getCandidates = (value: any) => SchemaAST.getCandidates(value, types)
221
- const compiled = new Map(
222
- types.map((candidate, i) => [candidate, [SchemaParser._is(candidate), recur(ast.types[i])] as const] as const)
223
- )
220
+ const index = SchemaAST.getCandidateIndex(types)
221
+ const compiled = types.map((candidate, i) => [SchemaParser._is(candidate), recur(ast.types[i])] as const)
224
222
  return (value) => {
225
- const candidates = getCandidates(value)
223
+ const candidates = index(value, false)
226
224
  for (let i = 0; i < candidates.length; i++) {
227
- const [is, formatter] = compiled.get(candidates[i])!
225
+ const [is, formatter] = compiled[candidates[i]]
228
226
  if (is(value)) {
229
227
  return formatter(value)
230
228
  }