effect-app 4.0.0-beta.13 → 4.0.0-beta.131

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 (139) hide show
  1. package/CHANGELOG.md +503 -0
  2. package/dist/Config/SecretURL.js +2 -2
  3. package/dist/Config.d.ts +7 -0
  4. package/dist/Config.d.ts.map +1 -0
  5. package/dist/Config.js +6 -0
  6. package/dist/ConfigProvider.d.ts +39 -0
  7. package/dist/ConfigProvider.d.ts.map +1 -0
  8. package/dist/ConfigProvider.js +42 -0
  9. package/dist/{ServiceMap.d.ts → Context.d.ts} +14 -18
  10. package/dist/Context.d.ts.map +1 -0
  11. package/dist/Context.js +66 -0
  12. package/dist/Effect.d.ts +8 -9
  13. package/dist/Effect.d.ts.map +1 -1
  14. package/dist/Effect.js +3 -6
  15. package/dist/Layer.d.ts +5 -4
  16. package/dist/Layer.d.ts.map +1 -1
  17. package/dist/Layer.js +1 -1
  18. package/dist/Operations.d.ts +198 -33
  19. package/dist/Operations.d.ts.map +1 -1
  20. package/dist/Pure.d.ts +2 -2
  21. package/dist/Pure.d.ts.map +1 -1
  22. package/dist/Pure.js +13 -13
  23. package/dist/Schema/Class.d.ts +48 -10
  24. package/dist/Schema/Class.d.ts.map +1 -1
  25. package/dist/Schema/Class.js +120 -16
  26. package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
  27. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  28. package/dist/Schema/SpecialJsonSchema.js +122 -0
  29. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  30. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  31. package/dist/Schema/SpecialOpenApi.js +123 -0
  32. package/dist/Schema/brand.d.ts +10 -1
  33. package/dist/Schema/brand.d.ts.map +1 -1
  34. package/dist/Schema/brand.js +1 -1
  35. package/dist/Schema/email.d.ts.map +1 -1
  36. package/dist/Schema/email.js +9 -4
  37. package/dist/Schema/ext.d.ts +112 -47
  38. package/dist/Schema/ext.d.ts.map +1 -1
  39. package/dist/Schema/ext.js +115 -53
  40. package/dist/Schema/moreStrings.d.ts +110 -10
  41. package/dist/Schema/moreStrings.d.ts.map +1 -1
  42. package/dist/Schema/moreStrings.js +19 -10
  43. package/dist/Schema/numbers.d.ts +126 -14
  44. package/dist/Schema/numbers.d.ts.map +1 -1
  45. package/dist/Schema/numbers.js +10 -9
  46. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  47. package/dist/Schema/phoneNumber.js +8 -3
  48. package/dist/Schema/strings.d.ts +36 -4
  49. package/dist/Schema/strings.d.ts.map +1 -1
  50. package/dist/Schema/strings.js +1 -1
  51. package/dist/Schema.d.ts +74 -55
  52. package/dist/Schema.d.ts.map +1 -1
  53. package/dist/Schema.js +85 -64
  54. package/dist/client/apiClientFactory.d.ts +12 -28
  55. package/dist/client/apiClientFactory.d.ts.map +1 -1
  56. package/dist/client/apiClientFactory.js +16 -17
  57. package/dist/client/clientFor.d.ts +6 -5
  58. package/dist/client/clientFor.d.ts.map +1 -1
  59. package/dist/client/errors.d.ts +18 -9
  60. package/dist/client/errors.d.ts.map +1 -1
  61. package/dist/client/errors.js +35 -10
  62. package/dist/client/makeClient.d.ts +73 -28
  63. package/dist/client/makeClient.d.ts.map +1 -1
  64. package/dist/client/makeClient.js +49 -23
  65. package/dist/http/Request.d.ts.map +1 -1
  66. package/dist/http/Request.js +5 -5
  67. package/dist/ids.d.ts +2 -2
  68. package/dist/ids.d.ts.map +1 -1
  69. package/dist/ids.js +3 -2
  70. package/dist/index.d.ts +3 -7
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +4 -8
  73. package/dist/middleware.d.ts +2 -2
  74. package/dist/middleware.d.ts.map +1 -1
  75. package/dist/middleware.js +3 -3
  76. package/dist/rpc/MiddlewareMaker.d.ts +4 -3
  77. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  78. package/dist/rpc/MiddlewareMaker.js +23 -24
  79. package/dist/rpc/RpcContextMap.d.ts +2 -2
  80. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  81. package/dist/rpc/RpcContextMap.js +4 -4
  82. package/dist/rpc/RpcMiddleware.d.ts +4 -3
  83. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  84. package/dist/rpc/RpcMiddleware.js +1 -1
  85. package/dist/utils/gen.d.ts +1 -1
  86. package/dist/utils/gen.d.ts.map +1 -1
  87. package/dist/utils/logger.d.ts +2 -2
  88. package/dist/utils/logger.d.ts.map +1 -1
  89. package/dist/utils/logger.js +3 -3
  90. package/dist/utils.d.ts +7 -1
  91. package/dist/utils.d.ts.map +1 -1
  92. package/dist/utils.js +8 -2
  93. package/package.json +30 -14
  94. package/src/Config/SecretURL.ts +1 -1
  95. package/src/Config.ts +14 -0
  96. package/src/ConfigProvider.ts +48 -0
  97. package/src/{ServiceMap.ts → Context.ts} +51 -59
  98. package/src/Effect.ts +11 -14
  99. package/src/Layer.ts +5 -4
  100. package/src/Pure.ts +17 -18
  101. package/src/Schema/Class.ts +157 -30
  102. package/src/Schema/SpecialJsonSchema.ts +137 -0
  103. package/src/Schema/SpecialOpenApi.ts +130 -0
  104. package/src/Schema/brand.ts +18 -3
  105. package/src/Schema/email.ts +10 -2
  106. package/src/Schema/ext.ts +196 -87
  107. package/src/Schema/moreStrings.ts +31 -17
  108. package/src/Schema/numbers.ts +14 -13
  109. package/src/Schema/phoneNumber.ts +8 -1
  110. package/src/Schema/strings.ts +4 -4
  111. package/src/Schema.ts +195 -104
  112. package/src/client/apiClientFactory.ts +104 -112
  113. package/src/client/clientFor.ts +6 -1
  114. package/src/client/errors.ts +42 -17
  115. package/src/client/makeClient.ts +150 -61
  116. package/src/http/Request.ts +7 -4
  117. package/src/ids.ts +2 -1
  118. package/src/index.ts +3 -10
  119. package/src/middleware.ts +2 -2
  120. package/src/rpc/MiddlewareMaker.ts +33 -44
  121. package/src/rpc/RpcContextMap.ts +6 -5
  122. package/src/rpc/RpcMiddleware.ts +5 -4
  123. package/src/utils/gen.ts +1 -1
  124. package/src/utils/logger.ts +2 -2
  125. package/src/utils.ts +8 -4
  126. package/test/dist/moreStrings.test.d.ts.map +1 -0
  127. package/test/dist/rpc.test.d.ts.map +1 -1
  128. package/test/dist/secretURL.test.d.ts.map +1 -0
  129. package/test/dist/special.test.d.ts.map +1 -0
  130. package/test/moreStrings.test.ts +17 -0
  131. package/test/rpc.test.ts +30 -6
  132. package/test/schema.test.ts +517 -4
  133. package/test/secretURL.test.ts +157 -0
  134. package/test/special.test.ts +862 -0
  135. package/test/utils.test.ts +2 -2
  136. package/tsconfig.base.json +0 -1
  137. package/tsconfig.json +0 -1
  138. package/dist/ServiceMap.d.ts.map +0 -1
  139. package/dist/ServiceMap.js +0 -91
@@ -9,7 +9,7 @@ export type NonEmptyString = string & NonEmptyStringBrand
9
9
  export const NonEmptyString = S
10
10
  .NonEmptyString
11
11
  .pipe(
12
- fromBrand(nominal<NonEmptyString>(), {
12
+ fromBrand<NonEmptyString>(nominal<NonEmptyString>(), {
13
13
  identifier: "NonEmptyString",
14
14
  title: "NonEmptyString",
15
15
  jsonSchema: {}
@@ -23,7 +23,7 @@ export const NonEmptyString64k = S
23
23
  .NonEmptyString
24
24
  .pipe(
25
25
  S.check(S.isMaxLength(64 * 1024)),
26
- fromBrand(nominal<NonEmptyString64k>(), {
26
+ fromBrand<NonEmptyString64k>(nominal<NonEmptyString64k>(), {
27
27
  identifier: "NonEmptyString64k",
28
28
  title: "NonEmptyString64k",
29
29
  jsonSchema: {}
@@ -37,7 +37,7 @@ export const NonEmptyString2k = S
37
37
  .NonEmptyString
38
38
  .pipe(
39
39
  S.check(S.isMaxLength(2 * 1024)),
40
- fromBrand(nominal<NonEmptyString2k>(), {
40
+ fromBrand<NonEmptyString2k>(nominal<NonEmptyString2k>(), {
41
41
  identifier: "NonEmptyString2k",
42
42
  title: "NonEmptyString2k",
43
43
  jsonSchema: {}
@@ -51,7 +51,7 @@ export const NonEmptyString255 = S
51
51
  .NonEmptyString
52
52
  .pipe(
53
53
  S.check(S.isMaxLength(255)),
54
- fromBrand(nominal<NonEmptyString255>(), {
54
+ fromBrand<NonEmptyString255>(nominal<NonEmptyString255>(), {
55
55
  identifier: "NonEmptyString255",
56
56
  title: "NonEmptyString255",
57
57
  jsonSchema: {}
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
- import { Email as EmailT } from "./Schema/email.js"
6
- import { withDefaultMake } from "./Schema/ext.js"
7
- import { PhoneNumber as PhoneNumberT } from "./Schema/phoneNumber.js"
8
- import type { AST } from "./Schema/schema.js"
5
+ import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
6
+ import { concurrencyUnbounded, withDefaultMake } from "./Schema/ext.js"
7
+ import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
9
8
  import { extendM } 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
13
  export { Class, TaggedClass } 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,6 +22,8 @@ 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
 
@@ -33,6 +32,58 @@ export * as SchemaParser from "effect/SchemaParser"
33
32
 
34
33
  export { Void as Void_ } from "effect/Schema"
35
34
 
35
+ // ---------------------------------------------------------------------------
36
+ // Struct / NonEmptyArray / Record — with concurrency: "unbounded"
37
+ // ---------------------------------------------------------------------------
38
+
39
+ export function Struct<const Fields extends S.Struct.Fields>(fields: Fields): S.Struct<Fields> {
40
+ const result = S.Struct(fields).annotate(concurrencyUnbounded)
41
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
42
+ const origMapFields: any = result.mapFields
43
+ ;(result as any).mapFields = function(this: any, f: any, options?: any) {
44
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
45
+ return origMapFields.call(this, f, options).annotate(concurrencyUnbounded)
46
+ }
47
+ return result
48
+ }
49
+ export interface Struct<Fields extends S.Struct.Fields> extends S.Struct<Fields> {}
50
+ export declare namespace Struct {
51
+ export type Fields = S.Struct.Fields
52
+ export type Type<F extends S.Struct.Fields> = S.Struct.Type<F>
53
+ export type Encoded<F extends S.Struct.Fields> = S.Struct.Encoded<F>
54
+ export type DecodingServices<F extends S.Struct.Fields> = S.Struct.DecodingServices<F>
55
+ export type EncodingServices<F extends S.Struct.Fields> = S.Struct.EncodingServices<F>
56
+ export type MakeIn<F extends S.Struct.Fields> = S.Struct.MakeIn<F>
57
+ export type Iso<F extends S.Struct.Fields> = S.Struct.Iso<F>
58
+ }
59
+
60
+ export function NonEmptyArray<Value extends S.Top>(value: Value): S.NonEmptyArray<Value> {
61
+ return S.NonEmptyArray(value).annotate(concurrencyUnbounded)
62
+ }
63
+
64
+ export function TaggedStruct<const Tag extends SchemaAST.LiteralValue, const Fields extends S.Struct.Fields>(
65
+ value: Tag,
66
+ fields: Fields
67
+ ): S.TaggedStruct<Tag, Fields> {
68
+ return Struct({ _tag: S.tag(value), ...fields })
69
+ }
70
+ export type TaggedStruct<Tag extends SchemaAST.LiteralValue, Fields extends S.Struct.Fields> = S.TaggedStruct<
71
+ Tag,
72
+ Fields
73
+ >
74
+
75
+ export function Record<Key extends S.Record.Key, Value extends S.Top>(
76
+ key: Key,
77
+ value: Value
78
+ ): S.$Record<Key, Value> {
79
+ return S.Record(key, value).annotate(concurrencyUnbounded)
80
+ }
81
+ export declare namespace Record {
82
+ export type Key = S.Record.Key
83
+ export type Type<K extends S.Record.Key, V extends S.Top> = S.Record.Type<K, V>
84
+ export type Encoded<K extends S.Record.Key, V extends S.Top> = S.Record.Encoded<K, V>
85
+ }
86
+
36
87
  export const SpanId = Symbol()
37
88
  export type SpanId = typeof SpanId
38
89
 
@@ -40,127 +91,167 @@ export interface WithOptionalSpan {
40
91
  [SpanId]?: Tracer.Span
41
92
  }
42
93
 
94
+ const makeEmail = S.decodeSync(EmailT as any) as (value: string) => EmailType
95
+ const makePhoneNumber = S.decodeSync(PhoneNumberT as any) as (value: string) => PhoneNumberType
96
+
43
97
  export const Email = EmailT
44
98
  .pipe(
45
99
  S.annotate({
46
100
  // eslint-disable-next-line @typescript-eslint/unbound-method
47
- arbitrary: (): any => (fc: any) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(Email)
101
+ toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail)
48
102
  }),
49
103
  withDefaultMake
50
104
  )
51
105
 
52
- export type Email = EmailT
106
+ export type Email = EmailType
53
107
 
54
108
  export const PhoneNumber = PhoneNumberT
55
109
  .pipe(
56
110
  S.annotate({
57
- arbitrary: (): any => (fc: any) =>
111
+ toArbitrary: () => (fc) =>
58
112
  // eslint-disable-next-line @typescript-eslint/unbound-method
59
- fakerArb((faker) => faker.phone.number)(fc).map(PhoneNumber)
113
+ fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber)
60
114
  }),
61
115
  withDefaultMake
62
116
  )
63
117
 
64
- export const makeIs = <A extends { _tag: string }, I, R>(
65
- schema: S.Codec<A, I, R>
66
- ) => {
67
- // In v4, transformations are stored as encoding on nodes, not as wrapper nodes.
68
- // Union member ASTs are directly Objects (TypeLiteral equivalent).
69
- if (SchemaAST.isUnion(schema.ast)) {
70
- return schema.ast.types.reduce((acc: any, t: AST.AST) => {
71
- if (!SchemaAST.isObjects(t)) return acc
72
- const tag = Array.findFirst(t.propertySignatures, (_: any) => {
73
- if (_.name === "_tag" && SchemaAST.isLiteral(_.type)) {
74
- return Option.some(_.type)
75
- }
76
- return Option.none()
77
- })
78
- const ast = Option.getOrUndefined(tag)
79
- if (!ast) {
80
- return acc
81
- }
82
- return {
83
- ...acc,
84
- [String((ast as SchemaAST.Literal).literal)]: (x: { _tag: string }) =>
85
- x._tag === (ast as SchemaAST.Literal).literal
86
- }
87
- }, {} as Is<A>)
88
- }
89
- throw new Error("Unsupported")
90
- }
118
+ export type PhoneNumber = PhoneNumberType
91
119
 
92
- export const makeIsAnyOf = <A extends { _tag: string }, I, R>(
93
- schema: S.Codec<A, I, R>
94
- ): IsAny<A> => {
95
- if (SchemaAST.isUnion(schema.ast)) {
96
- return <Keys extends A["_tag"][]>(...keys: Keys) => (a: A): a is ExtractUnion<A, ElemType<Keys>> =>
97
- keys.includes(a._tag)
98
- }
99
- throw new Error("Unsupported")
120
+ // Copied from SchemaAST.collectSentinels (marked @internal in effect).
121
+ // Returns all { key, literal } pairs that can discriminate a union member.
122
+ const getTagFromAST = (schema: S.Top): string => {
123
+ const sentinels = collectSentinelsFromAST(schema.ast)
124
+ const sentinel = sentinels.find((s) => s.key === "_tag")
125
+ if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
126
+ throw new Error("No _tag literal found on schema member")
100
127
  }
101
128
 
102
- export type ExtractUnion<A extends { _tag: string }, Tags extends A["_tag"]> = Extract<A, Record<"_tag", Tags>>
103
- export type Is<A extends { _tag: string }> = { [K in A as K["_tag"]]: (a: A) => a is K }
104
- export type ElemType<A> = A extends Array<infer E> ? E : never
105
- export interface IsAny<A extends { _tag: string }> {
106
- <Keys extends A["_tag"][]>(...keys: Keys): (a: A) => a is ExtractUnion<A, ElemType<Keys>>
129
+ function collectSentinelsFromAST(
130
+ ast: SchemaAST.AST
131
+ ): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
132
+ switch (ast._tag) {
133
+ case "Declaration": {
134
+ const s = ast.annotations?.["~sentinels"]
135
+ return Array.isArray(s) ? s : []
136
+ }
137
+ case "Objects":
138
+ return ast.propertySignatures.flatMap(
139
+ (ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
140
+ const type = ps.type
141
+ if (!SchemaAST.isOptional(type)) {
142
+ if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
143
+ if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
144
+ }
145
+ return []
146
+ }
147
+ )
148
+ case "Suspend":
149
+ return collectSentinelsFromAST(ast.thunk())
150
+ default:
151
+ return []
152
+ }
107
153
  }
108
154
 
109
- export const taggedUnionMap = <
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]
112
- >(
113
- self: Members
114
- ) =>
115
- self.reduce((acc, key) => {
116
- // TODO: v4 migration — PropertySignatureDeclaration removed, need v4 AST traversal
117
- const ast = key.fields._tag.ast as any
118
- const tag = ((ast.type ?? ast) as SchemaAST.Literal).literal as string // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
119
- acc[tag] = key as any
120
- return acc
121
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- }, {} as any)
123
-
124
155
  export const tags = <
125
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
- Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
156
+ Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
127
157
  >(
128
158
  self: Members
129
159
  ) =>
130
- S.Literals(self.map((key) => {
131
- // TODO: v4 migration — PropertySignatureDeclaration removed, need v4 AST traversal
132
- const ast = key.fields._tag.ast as any
133
- const tag = ((ast.type ?? ast) as SchemaAST.Literal).literal
134
- return tag
135
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
- })) as any
137
-
138
- export const ExtendTaggedUnion = <A extends { _tag: string }, I, R>(
139
- schema: S.Codec<A, I, R>
140
- ) =>
141
- extendM(
142
- schema,
143
- (_) => ({
144
- is: S.is(schema as any),
145
- isA: makeIs(_ as any),
146
- isAnyOf: makeIsAnyOf(_ as any) /*, map: taggedUnionMap(a) */
147
- })
148
- )
160
+ S.Literals(
161
+ self.map(getTagFromAST) as {
162
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
163
+ }
164
+ ) as S.Literals<
165
+ {
166
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
167
+ }
168
+ >
149
169
 
150
- export const TaggedUnion = <
151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
- Members extends readonly (S.Top & { fields: { _tag: S.tag<any> } })[]
153
- >(...a: Members) =>
154
- pipe(
155
- S.Union(a),
156
- (_) =>
157
- extendM(_, (_) => ({
158
- is: S.is(_ as any),
159
- isA: makeIs(_ as any),
160
- isAnyOf: makeIsAnyOf(_ as any),
161
- tagMap: taggedUnionMap(a),
162
- tags: tags(a as any)
163
- }))
164
- )
170
+ type TaggedUnionMembers = NonEmptyReadonlyArray<
171
+ S.Top & { readonly Type: { readonly _tag: string } }
172
+ >
173
+
174
+ type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
175
+ {
176
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
177
+ }
178
+ >
179
+
180
+ type TaggedPropertyKeys<A, Members extends TaggedUnionMembers> = {
181
+ [K in keyof A & string]: A[K] extends Members[number]["Type"] ? K : never
182
+ }[keyof A & string]
183
+
184
+ type PropertyGuardsFor<
185
+ Members extends TaggedUnionMembers,
186
+ K extends string,
187
+ A
188
+ > =
189
+ & {
190
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: (
191
+ target: A
192
+ ) => target is A & { readonly [P in K]: M["Type"] }
193
+ }
194
+ & {
195
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
196
+ tags: Tags
197
+ ) => (
198
+ target: A
199
+ ) => target is A & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
200
+ }
201
+
202
+ type PropertyGuards<
203
+ Members extends TaggedUnionMembers,
204
+ K extends string
205
+ > =
206
+ & {
207
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: <
208
+ T extends { readonly [P in K]: Members[number]["Type"] }
209
+ >(target: T) => target is T & { readonly [P in K]: M["Type"] }
210
+ }
211
+ & {
212
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
213
+ tags: Tags
214
+ ) => <T extends { readonly [P in K]: Members[number]["Type"] }>(
215
+ target: T
216
+ ) => target is T & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
217
+ }
218
+
219
+ type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
220
+ readonly tags: TaggedUnionTags<Members>
221
+ readonly generateGuards: <K extends string>(property: K) => PropertyGuards<Members, K>
222
+ readonly generateGuardsFor: <A>() => <K extends TaggedPropertyKeys<A, Members>>(
223
+ property: K
224
+ ) => PropertyGuardsFor<Members, K, A>
225
+ }
165
226
 
166
- export type PhoneNumber = PhoneNumberT
227
+ const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
228
+ schema: S.Union<Members>
229
+ ): TaggedUnionWithTags<Members> =>
230
+ extendM(schema.pipe(S.toTaggedUnion("_tag")), (tagged) => {
231
+ const makeGuards = (property: string) => {
232
+ const result: any = {}
233
+ const guards: Record<string, (u: unknown) => boolean> = tagged.guards
234
+ for (const tag of Object.keys(guards)) {
235
+ const guard = guards[tag]!
236
+ result[`is${tag}`] = (target: any) => guard(target[property])
237
+ }
238
+ result.isAnyOf = (memberTags: Array<string>) => {
239
+ const check = tagged.isAnyOf(memberTags)
240
+ return (target: any) => check(target[property])
241
+ }
242
+ return result
243
+ }
244
+ return {
245
+ tags: tags(schema.members),
246
+ generateGuards: makeGuards,
247
+ generateGuardsFor: () => makeGuards
248
+ }
249
+ })
250
+
251
+ export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
252
+ schema: S.Union<Members>
253
+ ): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
254
+
255
+ export const TaggedUnion = <
256
+ Members extends TaggedUnionMembers
257
+ >(...a: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(a))