effect-app 4.0.0-beta.15 → 4.0.0-beta.151

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