effect-app 4.0.0-beta.17 → 4.0.0-beta.170

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 (207) hide show
  1. package/CHANGELOG.md +683 -0
  2. package/dist/Array.d.ts +1 -1
  3. package/dist/Chunk.d.ts +1 -1
  4. package/dist/Chunk.d.ts.map +1 -1
  5. package/dist/Config/SecretURL.d.ts +1 -1
  6. package/dist/Config/SecretURL.d.ts.map +1 -1
  7. package/dist/Config/SecretURL.js +2 -2
  8. package/dist/Config/internal/configSecretURL.d.ts +1 -1
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config.d.ts +7 -0
  11. package/dist/Config.d.ts.map +1 -0
  12. package/dist/Config.js +6 -0
  13. package/dist/ConfigProvider.d.ts +39 -0
  14. package/dist/ConfigProvider.d.ts.map +1 -0
  15. package/dist/ConfigProvider.js +42 -0
  16. package/dist/Context.d.ts +40 -0
  17. package/dist/Context.d.ts.map +1 -0
  18. package/dist/Context.js +67 -0
  19. package/dist/Effect.d.ts +9 -10
  20. package/dist/Effect.d.ts.map +1 -1
  21. package/dist/Effect.js +3 -6
  22. package/dist/Function.d.ts +1 -1
  23. package/dist/Function.d.ts.map +1 -1
  24. package/dist/Inputify.type.d.ts +1 -1
  25. package/dist/Layer.d.ts +6 -5
  26. package/dist/Layer.d.ts.map +1 -1
  27. package/dist/Layer.js +1 -1
  28. package/dist/NonEmptySet.d.ts +1 -1
  29. package/dist/NonEmptySet.d.ts.map +1 -1
  30. package/dist/Operations.d.ts +369 -47
  31. package/dist/Operations.d.ts.map +1 -1
  32. package/dist/Operations.js +10 -10
  33. package/dist/Option.d.ts +1 -1
  34. package/dist/Option.d.ts.map +1 -1
  35. package/dist/Pure.d.ts +5 -5
  36. package/dist/Pure.d.ts.map +1 -1
  37. package/dist/Pure.js +13 -13
  38. package/dist/Schema/Class.d.ts +69 -20
  39. package/dist/Schema/Class.d.ts.map +1 -1
  40. package/dist/Schema/Class.js +190 -22
  41. package/dist/Schema/FastCheck.d.ts +1 -1
  42. package/dist/Schema/FastCheck.d.ts.map +1 -1
  43. package/dist/Schema/Methods.d.ts +1 -1
  44. package/dist/Schema/SchemaParser.d.ts +5 -0
  45. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  46. package/dist/Schema/SchemaParser.js +6 -0
  47. package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
  48. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  49. package/dist/Schema/SpecialJsonSchema.js +122 -0
  50. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  51. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  52. package/dist/Schema/SpecialOpenApi.js +123 -0
  53. package/dist/Schema/brand.d.ts +7 -2
  54. package/dist/Schema/brand.d.ts.map +1 -1
  55. package/dist/Schema/brand.js +1 -1
  56. package/dist/Schema/email.d.ts +1 -1
  57. package/dist/Schema/email.d.ts.map +1 -1
  58. package/dist/Schema/email.js +7 -4
  59. package/dist/Schema/ext.d.ts +121 -48
  60. package/dist/Schema/ext.d.ts.map +1 -1
  61. package/dist/Schema/ext.js +129 -53
  62. package/dist/Schema/moreStrings.d.ts +111 -11
  63. package/dist/Schema/moreStrings.d.ts.map +1 -1
  64. package/dist/Schema/moreStrings.js +14 -15
  65. package/dist/Schema/numbers.d.ts +127 -15
  66. package/dist/Schema/numbers.d.ts.map +1 -1
  67. package/dist/Schema/numbers.js +10 -12
  68. package/dist/Schema/phoneNumber.d.ts +1 -1
  69. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  70. package/dist/Schema/phoneNumber.js +6 -3
  71. package/dist/Schema/schema.d.ts +1 -1
  72. package/dist/Schema/strings.d.ts +37 -5
  73. package/dist/Schema/strings.d.ts.map +1 -1
  74. package/dist/Schema/strings.js +1 -5
  75. package/dist/Schema.d.ts +102 -56
  76. package/dist/Schema.d.ts.map +1 -1
  77. package/dist/Schema.js +128 -64
  78. package/dist/Set.d.ts +1 -1
  79. package/dist/Set.d.ts.map +1 -1
  80. package/dist/TypeTest.d.ts +1 -1
  81. package/dist/Types.d.ts +1 -1
  82. package/dist/Widen.type.d.ts +1 -1
  83. package/dist/_ext/Array.d.ts +1 -1
  84. package/dist/_ext/Array.d.ts.map +1 -1
  85. package/dist/_ext/date.d.ts +1 -1
  86. package/dist/_ext/misc.d.ts +1 -1
  87. package/dist/_ext/ord.ext.d.ts +1 -1
  88. package/dist/_ext/ord.ext.d.ts.map +1 -1
  89. package/dist/builtin.d.ts +1 -1
  90. package/dist/builtin.d.ts.map +1 -1
  91. package/dist/client/apiClientFactory.d.ts +14 -30
  92. package/dist/client/apiClientFactory.d.ts.map +1 -1
  93. package/dist/client/apiClientFactory.js +18 -19
  94. package/dist/client/clientFor.d.ts +30 -11
  95. package/dist/client/clientFor.d.ts.map +1 -1
  96. package/dist/client/clientFor.js +9 -1
  97. package/dist/client/errors.d.ts +44 -19
  98. package/dist/client/errors.d.ts.map +1 -1
  99. package/dist/client/errors.js +35 -10
  100. package/dist/client/makeClient.d.ts +243 -29
  101. package/dist/client/makeClient.d.ts.map +1 -1
  102. package/dist/client/makeClient.js +53 -23
  103. package/dist/client.d.ts +1 -1
  104. package/dist/faker.d.ts +1 -1
  105. package/dist/faker.d.ts.map +1 -1
  106. package/dist/http/Request.d.ts +2 -2
  107. package/dist/http/Request.d.ts.map +1 -1
  108. package/dist/http/Request.js +5 -5
  109. package/dist/http/internal/lib.d.ts +1 -1
  110. package/dist/http.d.ts +1 -1
  111. package/dist/ids.d.ts +3 -3
  112. package/dist/ids.d.ts.map +1 -1
  113. package/dist/ids.js +3 -2
  114. package/dist/index.d.ts +5 -8
  115. package/dist/index.d.ts.map +1 -1
  116. package/dist/index.js +6 -8
  117. package/dist/logger.d.ts +1 -1
  118. package/dist/middleware.d.ts +8 -8
  119. package/dist/middleware.d.ts.map +1 -1
  120. package/dist/middleware.js +8 -8
  121. package/dist/rpc/MiddlewareMaker.d.ts +5 -4
  122. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  123. package/dist/rpc/MiddlewareMaker.js +26 -27
  124. package/dist/rpc/RpcContextMap.d.ts +3 -3
  125. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  126. package/dist/rpc/RpcContextMap.js +4 -4
  127. package/dist/rpc/RpcMiddleware.d.ts +5 -4
  128. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  129. package/dist/rpc/RpcMiddleware.js +1 -1
  130. package/dist/rpc.d.ts +1 -2
  131. package/dist/rpc.d.ts.map +1 -1
  132. package/dist/rpc.js +1 -2
  133. package/dist/transform.d.ts +1 -1
  134. package/dist/transform.d.ts.map +1 -1
  135. package/dist/transform.js +3 -3
  136. package/dist/utils/effectify.d.ts +1 -1
  137. package/dist/utils/extend.d.ts +1 -1
  138. package/dist/utils/extend.d.ts.map +1 -1
  139. package/dist/utils/gen.d.ts +2 -2
  140. package/dist/utils/gen.d.ts.map +1 -1
  141. package/dist/utils/logLevel.d.ts +2 -2
  142. package/dist/utils/logLevel.d.ts.map +1 -1
  143. package/dist/utils/logger.d.ts +3 -3
  144. package/dist/utils/logger.d.ts.map +1 -1
  145. package/dist/utils/logger.js +3 -3
  146. package/dist/utils.d.ts +30 -10
  147. package/dist/utils.d.ts.map +1 -1
  148. package/dist/utils.js +10 -4
  149. package/dist/validation/validators.d.ts +1 -1
  150. package/dist/validation/validators.d.ts.map +1 -1
  151. package/dist/validation.d.ts +1 -1
  152. package/dist/validation.d.ts.map +1 -1
  153. package/eslint.config.mjs +2 -2
  154. package/package.json +39 -19
  155. package/src/Config/SecretURL.ts +2 -1
  156. package/src/Config.ts +14 -0
  157. package/src/ConfigProvider.ts +48 -0
  158. package/src/{ServiceMap.ts → Context.ts} +52 -59
  159. package/src/Effect.ts +12 -14
  160. package/src/Layer.ts +5 -4
  161. package/src/Operations.ts +14 -14
  162. package/src/Pure.ts +17 -18
  163. package/src/Schema/Class.ts +279 -62
  164. package/src/Schema/SchemaParser.ts +12 -0
  165. package/src/Schema/SpecialJsonSchema.ts +137 -0
  166. package/src/Schema/SpecialOpenApi.ts +130 -0
  167. package/src/Schema/brand.ts +9 -1
  168. package/src/Schema/email.ts +7 -2
  169. package/src/Schema/ext.ts +217 -87
  170. package/src/Schema/moreStrings.ts +22 -20
  171. package/src/Schema/numbers.ts +14 -16
  172. package/src/Schema/phoneNumber.ts +5 -1
  173. package/src/Schema/strings.ts +4 -8
  174. package/src/Schema.ts +265 -105
  175. package/src/client/apiClientFactory.ts +107 -113
  176. package/src/client/clientFor.ts +45 -12
  177. package/src/client/errors.ts +42 -17
  178. package/src/client/makeClient.ts +319 -63
  179. package/src/http/Request.ts +7 -4
  180. package/src/ids.ts +2 -1
  181. package/src/index.ts +5 -10
  182. package/src/middleware.ts +7 -9
  183. package/src/rpc/MiddlewareMaker.ts +36 -47
  184. package/src/rpc/README.md +2 -2
  185. package/src/rpc/RpcContextMap.ts +6 -5
  186. package/src/rpc/RpcMiddleware.ts +5 -4
  187. package/src/rpc.ts +0 -1
  188. package/src/transform.ts +2 -2
  189. package/src/utils/gen.ts +1 -1
  190. package/src/utils/logger.ts +2 -2
  191. package/src/utils.ts +47 -11
  192. package/test/dist/rpc.test.d.ts.map +1 -1
  193. package/test/dist/secretURL.test.d.ts.map +1 -0
  194. package/test/dist/special.test.d.ts.map +1 -0
  195. package/test/rpc.test.ts +38 -6
  196. package/test/schema.test.ts +591 -17
  197. package/test/secretURL.test.ts +157 -0
  198. package/test/special.test.ts +1023 -0
  199. package/test/utils.test.ts +6 -6
  200. package/tsconfig.base.json +3 -4
  201. package/tsconfig.json +0 -1
  202. package/tsconfig.json.bak +2 -2
  203. package/tsconfig.src.json +29 -29
  204. package/tsconfig.test.json +2 -2
  205. package/dist/ServiceMap.d.ts +0 -44
  206. package/dist/ServiceMap.d.ts.map +0 -1
  207. package/dist/ServiceMap.js +0 -91
package/src/Schema.ts CHANGED
@@ -1,22 +1,19 @@
1
- import { Array, Option, pipe, SchemaAST, type Tracer } from "effect"
1
+ import { SchemaAST, type Tracer } from "effect"
2
2
  import * as S from "effect/Schema"
3
3
  import type { NonEmptyReadonlyArray } from "./Array.js"
4
4
  import { fakerArb } from "./faker.js"
5
5
  import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
6
- import { withDefaultMake } from "./Schema/ext.js"
6
+ import { concurrencyUnbounded, withDefaultMake, withDefaultParseOptions } from "./Schema/ext.js"
7
7
  import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
8
- import type { AST } from "./Schema/schema.js"
9
- import { extendM } from "./utils.js"
8
+ import { copy, extendM, type StructuralCopyOrigin } from "./utils.js"
10
9
 
11
10
  export * from "effect/Schema"
12
- // v4: TaggedError renamed to TaggedErrorClass
13
- export { TaggedErrorClass as TaggedError } from "effect/Schema"
14
11
 
15
12
  export * from "./Schema/Class.js"
16
- export { Class, TaggedClass } from "./Schema/Class.js"
13
+ export { Class, ErrorClass, Opaque, TaggedClass, TaggedErrorClass } from "./Schema/Class.js"
17
14
 
18
15
  export { fromBrand, nominal } from "./Schema/brand.js"
19
- export { Array, Boolean, Date, Literal, Map, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
16
+ export { Array, Boolean, Date, DateFromString, DateValid, Finite, Literals, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
20
17
  export { Int, NonNegativeInt } from "./Schema/numbers.js"
21
18
 
22
19
  export * from "./Schema/email.js"
@@ -25,14 +22,153 @@ export * from "./Schema/moreStrings.js"
25
22
  export * from "./Schema/numbers.js"
26
23
  export * from "./Schema/phoneNumber.js"
27
24
  export * from "./Schema/schema.js"
25
+ export * from "./Schema/SpecialJsonSchema.js"
26
+ export * from "./Schema/SpecialOpenApi.js"
28
27
  export * from "./Schema/strings.js"
29
28
  export { NonEmptyString } from "./Schema/strings.js"
30
29
 
31
30
  export * as SchemaIssue from "effect/SchemaIssue"
32
- export * as SchemaParser from "effect/SchemaParser"
31
+
32
+ export const decodeEffectConcurrently: typeof S.decodeEffect = withDefaultParseOptions(S.decodeEffect)
33
+ export const decodeUnknownEffectConcurrently: typeof S.decodeUnknownEffect = withDefaultParseOptions(
34
+ S.decodeUnknownEffect
35
+ )
36
+ export * as SchemaParser from "./Schema/SchemaParser.js"
33
37
 
34
38
  export { Void as Void_ } from "effect/Schema"
35
39
 
40
+ // ---------------------------------------------------------------------------
41
+ // Struct / NonEmptyArray / Record
42
+ // ---------------------------------------------------------------------------
43
+
44
+ type WithSchemaCopy<Self extends S.Top & { readonly Type: object }> = Self & {
45
+ readonly copy: StructuralCopyOrigin<Self["Type"]>
46
+ }
47
+
48
+ type OptionalMakeInput<Fields extends S.Struct.Fields> = {} extends S.Struct.MakeIn<Fields> ? {
49
+ make(input?: S.Struct.MakeIn<Fields>, options?: S.MakeOptions): S.Struct.Type<Fields>
50
+ }
51
+ : {}
52
+
53
+ export function Struct<const Fields extends S.Struct.Fields>(
54
+ fields: Fields
55
+ ): Struct<Fields> & OptionalMakeInput<Fields> {
56
+ const result = S.Struct(fields).annotate(concurrencyUnbounded)
57
+ const allowVoidMake = (schema: any): any => {
58
+ // Normalize omitted input to an empty object so optional/default-only structs can be constructed with make().
59
+ const origMake: any = schema.make
60
+ const origMakeOption: any = schema.makeOption
61
+ const origMakeEffect: any = schema.makeEffect
62
+ schema.make = function(this: any, input: any, options?: any) {
63
+ return origMake.call(this, input === undefined ? {} : input, options)
64
+ }
65
+ schema.makeOption = function(this: any, input: any, options?: any) {
66
+ return origMakeOption.call(this, input === undefined ? {} : input, options)
67
+ }
68
+ schema.makeEffect = function(this: any, input: any, options?: any) {
69
+ return origMakeEffect.call(this, input === undefined ? {} : input, options)
70
+ }
71
+ return schema
72
+ }
73
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
74
+ const origMapFields: any = result.mapFields
75
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
76
+ const origAnnotate: any = result.annotate
77
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
78
+ const origAnnotateKey: any = result.annotateKey
79
+
80
+ const preserveCopyAndMethods = (schema: any): any => {
81
+ schema.copy = copy
82
+ schema.mapFields = function(this: any, f: any, options?: any) {
83
+ return (result as any).mapFields.call(this, f, options)
84
+ }
85
+ schema.annotate = function(this: any, annotations?: any) {
86
+ return (result as any).annotate.call(this, annotations)
87
+ }
88
+ schema.annotateKey = function(this: any, annotations?: any) {
89
+ return (result as any).annotateKey.call(this, annotations)
90
+ }
91
+ return allowVoidMake(schema)
92
+ }
93
+ ;(result as any).mapFields = function(this: any, f: any, options?: any) {
94
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
95
+ const mapped = origMapFields.call(this, f, options).annotate(concurrencyUnbounded)
96
+ return preserveCopyAndMethods(mapped)
97
+ }
98
+ ;(result as any).annotate = function(this: any, annotations?: any) {
99
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
100
+ const annotated = origAnnotate.call(this, annotations)
101
+ return preserveCopyAndMethods(annotated)
102
+ }
103
+ ;(result as any).annotateKey = function(this: any, annotations?: any) {
104
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
105
+ const annotated = origAnnotateKey.call(this, annotations)
106
+ return preserveCopyAndMethods(annotated)
107
+ }
108
+ ;(result as any).copy = copy
109
+ allowVoidMake(result)
110
+ return result as Struct<Fields> & OptionalMakeInput<Fields>
111
+ }
112
+ export interface Struct<Fields extends S.Struct.Fields> extends WithSchemaCopy<S.Struct<Fields>> {
113
+ annotate(
114
+ annotations: S.Annotations.Bottom<S.Struct.Type<Fields>, S.Struct<Fields>["~type.parameters"]>
115
+ ): Struct<Fields>
116
+ annotateKey(annotations: S.Annotations.Key<S.Struct.Type<Fields>>): Struct<Fields>
117
+ mapFields<To extends S.Struct.Fields>(
118
+ f: (fields: Fields) => To,
119
+ options?: {
120
+ readonly unsafePreserveChecks?: boolean | undefined
121
+ }
122
+ ): Struct<Readonly<To>>
123
+ }
124
+ export declare namespace Struct {
125
+ export type Fields = S.Struct.Fields
126
+ export type Type<F extends S.Struct.Fields> = S.Struct.Type<F>
127
+ export type Encoded<F extends S.Struct.Fields> = S.Struct.Encoded<F>
128
+ export type DecodingServices<F extends S.Struct.Fields> = S.Struct.DecodingServices<F>
129
+ export type EncodingServices<F extends S.Struct.Fields> = S.Struct.EncodingServices<F>
130
+ export type MakeIn<F extends S.Struct.Fields> = S.Struct.MakeIn<F>
131
+ export type Iso<F extends S.Struct.Fields> = S.Struct.Iso<F>
132
+ }
133
+
134
+ export type StructNestedEncodedError<T> = {
135
+ readonly _tag: "StructNestedEncodedError"
136
+ readonly message: "Expected a Struct schema or a schema with from.Encoded"
137
+ readonly schema: T
138
+ }
139
+
140
+ export type StructNestedEncoded<T> = T extends { fields: infer Fields extends S.Struct.Fields } ? Struct.Encoded<Fields>
141
+ : T extends { readonly from: { readonly Encoded: infer Encoded } } ? Encoded
142
+ : StructNestedEncodedError<T>
143
+
144
+ export function NonEmptyArray<Value extends S.Top>(value: Value): S.NonEmptyArray<Value> {
145
+ return S.NonEmptyArray(value).annotate(concurrencyUnbounded)
146
+ }
147
+
148
+ export function TaggedStruct<const Tag extends SchemaAST.LiteralValue, const Fields extends S.Struct.Fields>(
149
+ value: Tag,
150
+ fields: Fields
151
+ ): TaggedStruct<Tag, Fields> {
152
+ return Struct({ _tag: S.tag(value), ...fields }) as any
153
+ }
154
+ export type TaggedStruct<Tag extends SchemaAST.LiteralValue, Fields extends S.Struct.Fields> =
155
+ & WithSchemaCopy<
156
+ S.TaggedStruct<Tag, Fields>
157
+ >
158
+ & OptionalMakeInput<Readonly<{ readonly _tag: S.tag<Tag> } & Fields>>
159
+
160
+ export function Record<Key extends S.Record.Key, Value extends S.Top>(
161
+ key: Key,
162
+ value: Value
163
+ ): S.$Record<Key, Value> {
164
+ return S.Record(key, value).annotate(concurrencyUnbounded)
165
+ }
166
+ export declare namespace Record {
167
+ export type Key = S.Record.Key
168
+ export type Type<K extends S.Record.Key, V extends S.Top> = S.Record.Type<K, V>
169
+ export type Encoded<K extends S.Record.Key, V extends S.Top> = S.Record.Encoded<K, V>
170
+ }
171
+
36
172
  export const SpanId = Symbol()
37
173
  export type SpanId = typeof SpanId
38
174
 
@@ -66,117 +202,141 @@ export const PhoneNumber = PhoneNumberT
66
202
 
67
203
  export type PhoneNumber = PhoneNumberType
68
204
 
69
- export const makeIs = <A extends { _tag: string }, I, RD, RE>(
70
- schema: S.Codec<A, I, RD, RE>
71
- ) => {
72
- // In v4, transformations are stored as encoding on nodes, not as wrapper nodes.
73
- // Union member ASTs are directly Objects (TypeLiteral equivalent).
74
- if (SchemaAST.isUnion(schema.ast)) {
75
- return schema.ast.types.reduce((acc, t: AST.AST) => {
76
- if (!SchemaAST.isObjects(t)) return acc
77
- const tag = Array.findFirst(t.propertySignatures, (_: any) => {
78
- if (_.name === "_tag" && SchemaAST.isLiteral(_.type)) {
79
- return Option.some(_.type)
80
- }
81
- return Option.none()
82
- })
83
- const ast = Option.getOrUndefined(tag)
84
- if (!ast) {
85
- return acc
86
- }
87
- return {
88
- ...acc,
89
- [String((ast as SchemaAST.Literal).literal)]: (x: { _tag: string }) =>
90
- x._tag === (ast as SchemaAST.Literal).literal
91
- }
92
- }, {} as Is<A>)
93
- }
94
- throw new Error("Unsupported")
95
- }
96
-
97
- export const makeIsAnyOf = <A extends { _tag: string }, I, RD, RE>(
98
- schema: S.Codec<A, I, RD, RE>
99
- ): IsAny<A> => {
100
- if (SchemaAST.isUnion(schema.ast)) {
101
- return <Keys extends A["_tag"][]>(...keys: Keys) => (a: A): a is ExtractUnion<A, ElemType<Keys>> =>
102
- keys.includes(a._tag)
103
- }
104
- throw new Error("Unsupported")
105
- }
106
-
107
- export type ExtractUnion<A extends { _tag: string }, Tags extends A["_tag"]> = Extract<A, Record<"_tag", Tags>>
108
- export type Is<A extends { _tag: string }> = { [K in A as K["_tag"]]: (a: A) => a is K }
109
- export type ElemType<A> = A extends Array<infer E> ? E : never
110
- export interface IsAny<A extends { _tag: string }> {
111
- <Keys extends A["_tag"][]>(...keys: Keys): (a: A) => a is ExtractUnion<A, ElemType<Keys>>
112
- }
113
-
114
- const getTagLiteral = <Tag extends string>(schema: S.tag<Tag>): Tag => {
115
- if (!SchemaAST.isLiteral(schema.ast)) {
116
- throw new Error("Unsupported _tag schema: expected a literal AST")
117
- }
118
- return schema.ast.literal as Tag
205
+ // Copied from SchemaAST.collectSentinels (marked @internal in effect).
206
+ // Returns all { key, literal } pairs that can discriminate a union member.
207
+ const getTagFromAST = (schema: S.Top): string => {
208
+ const sentinels = collectSentinelsFromAST(schema.ast)
209
+ const sentinel = sentinels.find((s) => s.key === "_tag")
210
+ if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
211
+ throw new Error("No _tag literal found on schema member")
119
212
  }
120
213
 
121
- type TaggedUnionMap<Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]> = {
122
- [Key in Members[number] as Key["fields"]["_tag"]["Type"]]: Key
123
- }
124
-
125
- export const taggedUnionMap = <
126
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
- Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]
128
- >(
129
- self: Members
130
- ) => {
131
- const out = {} as TaggedUnionMap<Members>
132
- for (const key of self) {
133
- const tag = getTagLiteral(key.fields._tag) as keyof TaggedUnionMap<Members>
134
- out[tag] = key as TaggedUnionMap<Members>[typeof tag]
214
+ function collectSentinelsFromAST(
215
+ ast: SchemaAST.AST
216
+ ): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
217
+ switch (ast._tag) {
218
+ case "Declaration": {
219
+ const s = ast.annotations?.["~sentinels"]
220
+ return Array.isArray(s) ? s : []
221
+ }
222
+ case "Objects":
223
+ return ast.propertySignatures.flatMap(
224
+ (ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
225
+ const type = ps.type
226
+ if (!SchemaAST.isOptional(type)) {
227
+ if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
228
+ if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
229
+ }
230
+ return []
231
+ }
232
+ )
233
+ case "Suspend":
234
+ return collectSentinelsFromAST(ast.thunk())
235
+ default:
236
+ return []
135
237
  }
136
- return out
137
238
  }
138
239
 
139
240
  export const tags = <
140
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
- Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
241
+ Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
142
242
  >(
143
243
  self: Members
144
244
  ) =>
145
245
  S.Literals(
146
- self.map((key) => getTagLiteral(key.fields._tag)) as {
147
- [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
246
+ self.map(getTagFromAST) as {
247
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
148
248
  }
149
249
  ) as S.Literals<
150
250
  {
151
- [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
251
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
152
252
  }
153
253
  >
154
254
 
155
- export const ExtendTaggedUnion = <A extends { readonly _tag: string }, I, R>(
156
- schema: S.Codec<A, I, R>
157
- ) =>
158
- extendM(
159
- schema,
160
- (_) => ({
161
- // is: S.is(schema), // only works with never DecodingServices
162
- isA: makeIs(_),
163
- isAnyOf: makeIsAnyOf(_) /*, map: taggedUnionMap(a) */
164
- })
165
- )
255
+ type TaggedUnionMembers = NonEmptyReadonlyArray<
256
+ S.Top & { readonly Type: { readonly _tag: string } }
257
+ >
258
+
259
+ type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
260
+ {
261
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
262
+ }
263
+ >
264
+
265
+ type TaggedPropertyKeys<A, Members extends TaggedUnionMembers> = {
266
+ [K in keyof A & string]: A[K] extends Members[number]["Type"] ? K : never
267
+ }[keyof A & string]
268
+
269
+ type PropertyGuardsFor<
270
+ Members extends TaggedUnionMembers,
271
+ K extends string,
272
+ A
273
+ > =
274
+ & {
275
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: (
276
+ target: A
277
+ ) => target is A & { readonly [P in K]: M["Type"] }
278
+ }
279
+ & {
280
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
281
+ tags: Tags
282
+ ) => (
283
+ target: A
284
+ ) => target is A & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
285
+ }
286
+
287
+ type PropertyGuards<
288
+ Members extends TaggedUnionMembers,
289
+ K extends string
290
+ > =
291
+ & {
292
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: <
293
+ T extends { readonly [P in K]: Members[number]["Type"] }
294
+ >(target: T) => target is T & { readonly [P in K]: M["Type"] }
295
+ }
296
+ & {
297
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
298
+ tags: Tags
299
+ ) => <T extends { readonly [P in K]: Members[number]["Type"] }>(
300
+ target: T
301
+ ) => target is T & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
302
+ }
303
+
304
+ type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
305
+ readonly tags: TaggedUnionTags<Members>
306
+ readonly generateGuards: <K extends string>(property: K) => PropertyGuards<Members, K>
307
+ readonly generateGuardsFor: <A>() => <K extends TaggedPropertyKeys<A, Members>>(
308
+ property: K
309
+ ) => PropertyGuardsFor<Members, K, A>
310
+ }
311
+
312
+ const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
313
+ schema: S.Union<Members>
314
+ ): TaggedUnionWithTags<Members> =>
315
+ extendM(schema.pipe(S.toTaggedUnion("_tag")), (tagged) => {
316
+ const makeGuards = (property: string) => {
317
+ const result: any = {}
318
+ const guards: Record<string, (u: unknown) => boolean> = tagged.guards
319
+ for (const tag of Object.keys(guards)) {
320
+ const guard = guards[tag]!
321
+ result[`is${tag}`] = (target: any) => guard(target[property])
322
+ }
323
+ result.isAnyOf = (memberTags: Array<string>) => {
324
+ const check = tagged.isAnyOf(memberTags)
325
+ return (target: any) => check(target[property])
326
+ }
327
+ return result
328
+ }
329
+ return {
330
+ tags: tags(schema.members),
331
+ generateGuards: makeGuards,
332
+ generateGuardsFor: () => makeGuards
333
+ }
334
+ })
335
+
336
+ export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
337
+ schema: S.Union<Members>
338
+ ): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
166
339
 
167
340
  export const TaggedUnion = <
168
- Members extends NonEmptyReadonlyArray<
169
- S.Codec<{ readonly _tag: string }, any, any, any> & { fields: { _tag: S.tag<string> } }
170
- >
171
- >(...a: Members) =>
172
- pipe(
173
- S.Union(a),
174
- (_) =>
175
- extendM(_, (_) => ({
176
- // is: S.is(_), // only works with never DecodingServices
177
- isA: makeIs(_),
178
- isAnyOf: makeIsAnyOf(_),
179
- tagMap: taggedUnionMap(a),
180
- tags: tags(a)
181
- }))
182
- )
341
+ Members extends TaggedUnionMembers
342
+ >(members: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(members))