effect-app 4.0.0-beta.22 → 4.0.0-beta.220

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 (219) hide show
  1. package/CHANGELOG.md +986 -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 +7 -6
  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/Option.d.ts +1 -1
  31. package/dist/Option.d.ts.map +1 -1
  32. package/dist/Pure.d.ts +5 -5
  33. package/dist/Pure.d.ts.map +1 -1
  34. package/dist/Pure.js +13 -13
  35. package/dist/Schema/Class.d.ts +66 -20
  36. package/dist/Schema/Class.d.ts.map +1 -1
  37. package/dist/Schema/Class.js +189 -22
  38. package/dist/Schema/FastCheck.d.ts +1 -1
  39. package/dist/Schema/FastCheck.d.ts.map +1 -1
  40. package/dist/Schema/Methods.d.ts +1 -1
  41. package/dist/Schema/SchemaParser.d.ts +5 -0
  42. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  43. package/dist/Schema/SchemaParser.js +6 -0
  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 +4 -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 +367 -56
  57. package/dist/Schema/ext.d.ts.map +1 -1
  58. package/dist/Schema/ext.js +345 -53
  59. package/dist/Schema/moreStrings.d.ts +108 -26
  60. package/dist/Schema/moreStrings.d.ts.map +1 -1
  61. package/dist/Schema/moreStrings.js +54 -16
  62. package/dist/Schema/numbers.d.ts +55 -15
  63. package/dist/Schema/numbers.d.ts.map +1 -1
  64. package/dist/Schema/numbers.js +60 -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 +5 -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 +217 -15
  73. package/dist/Schema.d.ts.map +1 -1
  74. package/dist/Schema.js +221 -16
  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/InvalidationKeys.d.ts +29 -0
  89. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  90. package/dist/client/InvalidationKeys.js +33 -0
  91. package/dist/client/apiClientFactory.d.ts +20 -32
  92. package/dist/client/apiClientFactory.d.ts.map +1 -1
  93. package/dist/client/apiClientFactory.js +96 -33
  94. package/dist/client/clientFor.d.ts +51 -17
  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 +49 -25
  98. package/dist/client/errors.d.ts.map +1 -1
  99. package/dist/client/errors.js +43 -17
  100. package/dist/client/makeClient.d.ts +481 -33
  101. package/dist/client/makeClient.d.ts.map +1 -1
  102. package/dist/client/makeClient.js +66 -24
  103. package/dist/client.d.ts +2 -1
  104. package/dist/client.d.ts.map +1 -1
  105. package/dist/client.js +2 -1
  106. package/dist/faker.d.ts +1 -1
  107. package/dist/faker.d.ts.map +1 -1
  108. package/dist/http/Request.d.ts +2 -2
  109. package/dist/http/Request.d.ts.map +1 -1
  110. package/dist/http/internal/lib.d.ts +1 -1
  111. package/dist/http.d.ts +1 -1
  112. package/dist/ids.d.ts +40 -12
  113. package/dist/ids.d.ts.map +1 -1
  114. package/dist/ids.js +25 -3
  115. package/dist/index.d.ts +5 -8
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +6 -8
  118. package/dist/logger.d.ts +1 -1
  119. package/dist/middleware.d.ts +14 -8
  120. package/dist/middleware.d.ts.map +1 -1
  121. package/dist/middleware.js +14 -8
  122. package/dist/rpc/Invalidation.d.ts +402 -0
  123. package/dist/rpc/Invalidation.d.ts.map +1 -0
  124. package/dist/rpc/Invalidation.js +150 -0
  125. package/dist/rpc/MiddlewareMaker.d.ts +5 -4
  126. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  127. package/dist/rpc/MiddlewareMaker.js +57 -37
  128. package/dist/rpc/RpcContextMap.d.ts +3 -3
  129. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  130. package/dist/rpc/RpcContextMap.js +4 -4
  131. package/dist/rpc/RpcMiddleware.d.ts +5 -4
  132. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  133. package/dist/rpc/RpcMiddleware.js +1 -1
  134. package/dist/rpc.d.ts +2 -2
  135. package/dist/rpc.d.ts.map +1 -1
  136. package/dist/rpc.js +2 -2
  137. package/dist/transform.d.ts +1 -1
  138. package/dist/transform.d.ts.map +1 -1
  139. package/dist/transform.js +3 -3
  140. package/dist/utils/effectify.d.ts +1 -1
  141. package/dist/utils/extend.d.ts +1 -1
  142. package/dist/utils/extend.d.ts.map +1 -1
  143. package/dist/utils/gen.d.ts +2 -2
  144. package/dist/utils/gen.d.ts.map +1 -1
  145. package/dist/utils/logLevel.d.ts +2 -2
  146. package/dist/utils/logLevel.d.ts.map +1 -1
  147. package/dist/utils/logger.d.ts +3 -3
  148. package/dist/utils/logger.d.ts.map +1 -1
  149. package/dist/utils/logger.js +3 -3
  150. package/dist/utils.d.ts +31 -38
  151. package/dist/utils.d.ts.map +1 -1
  152. package/dist/utils.js +12 -25
  153. package/dist/validation/validators.d.ts +1 -1
  154. package/dist/validation/validators.d.ts.map +1 -1
  155. package/dist/validation.d.ts +1 -1
  156. package/dist/validation.d.ts.map +1 -1
  157. package/package.json +46 -24
  158. package/src/Config/SecretURL.ts +2 -1
  159. package/src/Config.ts +14 -0
  160. package/src/ConfigProvider.ts +48 -0
  161. package/src/{ServiceMap.ts → Context.ts} +52 -59
  162. package/src/Effect.ts +12 -14
  163. package/src/Layer.ts +6 -5
  164. package/src/Pure.ts +17 -18
  165. package/src/Schema/Class.ts +270 -64
  166. package/src/Schema/SchemaParser.ts +12 -0
  167. package/src/Schema/SpecialJsonSchema.ts +137 -0
  168. package/src/Schema/SpecialOpenApi.ts +130 -0
  169. package/src/Schema/brand.ts +21 -1
  170. package/src/Schema/email.ts +7 -2
  171. package/src/Schema/ext.ts +425 -88
  172. package/src/Schema/moreStrings.ts +92 -37
  173. package/src/Schema/numbers.ts +64 -16
  174. package/src/Schema/phoneNumber.ts +5 -1
  175. package/src/Schema/strings.ts +4 -8
  176. package/src/Schema.ts +410 -20
  177. package/src/client/InvalidationKeys.ts +50 -0
  178. package/src/client/apiClientFactory.ts +224 -130
  179. package/src/client/clientFor.ts +95 -29
  180. package/src/client/errors.ts +52 -26
  181. package/src/client/makeClient.ts +572 -71
  182. package/src/client.ts +1 -0
  183. package/src/ids.ts +25 -3
  184. package/src/index.ts +5 -10
  185. package/src/middleware.ts +13 -9
  186. package/src/rpc/Invalidation.ts +226 -0
  187. package/src/rpc/MiddlewareMaker.ts +65 -60
  188. package/src/rpc/README.md +2 -2
  189. package/src/rpc/RpcContextMap.ts +6 -5
  190. package/src/rpc/RpcMiddleware.ts +5 -4
  191. package/src/rpc.ts +1 -1
  192. package/src/transform.ts +2 -2
  193. package/src/utils/gen.ts +1 -1
  194. package/src/utils/logger.ts +2 -2
  195. package/src/utils.ts +50 -132
  196. package/test/dist/rpc.test.d.ts.map +1 -1
  197. package/test/dist/secretURL.test.d.ts.map +1 -0
  198. package/test/dist/special.test.d.ts.map +1 -0
  199. package/test/dist/stream-error.types.d.ts +2 -0
  200. package/test/dist/stream-error.types.d.ts.map +1 -0
  201. package/test/dist/stream-error.types.js +27 -0
  202. package/test/rpc.test.ts +45 -6
  203. package/test/schema.test.ts +583 -9
  204. package/test/secretURL.test.ts +157 -0
  205. package/test/special.test.ts +1023 -0
  206. package/test/utils.test.ts +6 -6
  207. package/tsconfig.base.json +3 -4
  208. package/tsconfig.json +0 -1
  209. package/tsconfig.json.bak +2 -2
  210. package/tsconfig.src.json +29 -29
  211. package/tsconfig.test.json +2 -2
  212. package/dist/Operations.d.ts +0 -123
  213. package/dist/Operations.d.ts.map +0 -1
  214. package/dist/Operations.js +0 -29
  215. package/dist/ServiceMap.d.ts +0 -44
  216. package/dist/ServiceMap.d.ts.map +0 -1
  217. package/dist/ServiceMap.js +0 -91
  218. package/eslint.config.mjs +0 -26
  219. package/src/Operations.ts +0 -55
package/src/Schema.ts CHANGED
@@ -1,21 +1,118 @@
1
1
  import { SchemaAST, type Tracer } from "effect"
2
2
  import * as S from "effect/Schema"
3
+ import { type Simplify } from "effect/Struct"
4
+ import type { RequiredKeys } from "effect/Types"
3
5
  import type { NonEmptyReadonlyArray } from "./Array.js"
4
6
  import { fakerArb } from "./faker.js"
5
7
  import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
6
- import { withDefaultMake } from "./Schema/ext.js"
8
+ import { concurrencyUnbounded, withDefaultMake, withDefaultParseOptions } from "./Schema/ext.js"
7
9
  import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
8
- import { extendM } from "./utils.js"
10
+ import { type AST } from "./Schema/schema.js"
11
+ import { copy, extendM, type StructuralCopyOrigin } from "./utils.js"
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Default helpers — re-exported from effect/Schema
15
+ //
16
+ // The five helpers below are surfaced explicitly so the (important) policy
17
+ // around them lives next to the export. See also the file-level note in
18
+ // `./Schema/ext.ts` and the documented wrappers in
19
+ // `./Schema/ext.ts`, `./Schema/numbers.ts`, `./Schema/moreStrings.ts`,
20
+ // and `./ids.ts`.
21
+ //
22
+ // **Construction-only**: `withConstructorDefault` fills the field when it
23
+ // is omitted from `.make(...)` input. It is NOT applied during decode, so
24
+ // it CANNOT be used to just-in-time migrate database fields. A stored
25
+ // record missing the field will still fail to decode.
26
+ //
27
+ // **`withDecodingDefault*` is discouraged**: a missing field in persisted
28
+ // data is just as likely to be data corruption as it is an old-shape
29
+ // document; silently substituting a default hides the problem and can
30
+ // poison downstream aggregates. Prefer an explicit, preferably versioned
31
+ // migration of database data over shoving missing fields under the rug.
32
+ // ---------------------------------------------------------------------------
33
+
34
+ /**
35
+ * Attach a default value used **only** when constructing a value (e.g. via
36
+ * `.make(...)` or struct constructors) and the field is omitted from input.
37
+ *
38
+ * **Not applied during decode.** Decoding a payload that is missing the
39
+ * field will still raise a parse error. Do **not** rely on this to migrate
40
+ * database fields just-in-time — see the section header above.
41
+ *
42
+ * @see {@link withDecodingDefault} / {@link withDecodingDefaultType} —
43
+ * decode-time variants (discouraged for persisted data; use explicit,
44
+ * versioned migrations instead).
45
+ */
46
+ export { withConstructorDefault } from "effect/Schema"
47
+
48
+ /**
49
+ * Attach a default value used during decode when the field's `Encoded` value
50
+ * is missing **or** `undefined`. The default is specified as an `Encoded`
51
+ * value and threaded through the schema's decode step.
52
+ *
53
+ * **Discouraged for persisted data.** A missing field in a stored record is
54
+ * just as likely to be data corruption as it is an old-shape document;
55
+ * silently substituting a default hides the problem. Prefer an explicit,
56
+ * preferably versioned migration of database data — see the section header
57
+ * above.
58
+ *
59
+ * @see {@link withDecodingDefaultKey} — key-absent-only variant
60
+ * @see {@link withDecodingDefaultType} — `Type`-side variant
61
+ * @see {@link withConstructorDefault} — for `.make(...)`-time defaults
62
+ */
63
+ export { withDecodingDefault } from "effect/Schema"
64
+
65
+ /**
66
+ * Attach a default value used during decode when the field **key is absent**
67
+ * (note: not when present and `undefined`). The default is an `Encoded`
68
+ * value.
69
+ *
70
+ * **Discouraged for persisted data** — same reasoning as
71
+ * {@link withDecodingDefault}. Use explicit, preferably versioned migrations
72
+ * over decode-time fallbacks.
73
+ *
74
+ * @see {@link withDecodingDefault} — value-absent-or-undefined variant
75
+ * @see {@link withDecodingDefaultTypeKey} — `Type`-side variant
76
+ * @see {@link withConstructorDefault} — for `.make(...)`-time defaults
77
+ */
78
+ export { withDecodingDefaultKey } from "effect/Schema"
79
+
80
+ /**
81
+ * Attach a default value used during decode when the field is missing **or**
82
+ * `undefined`. The default is specified as a `Type` value (i.e. on the
83
+ * decoded side).
84
+ *
85
+ * **Discouraged for persisted data** — same reasoning as
86
+ * {@link withDecodingDefault}. Use explicit, preferably versioned migrations
87
+ * over decode-time fallbacks.
88
+ *
89
+ * @see {@link withDecodingDefault} — `Encoded`-side variant
90
+ * @see {@link withDecodingDefaultTypeKey} — key-absent-only variant
91
+ * @see {@link withConstructorDefault} — for `.make(...)`-time defaults
92
+ */
93
+ export { withDecodingDefaultType } from "effect/Schema"
94
+
95
+ /**
96
+ * Attach a default value used during decode when the field **key is absent**
97
+ * (note: not when present and `undefined`). The default is a `Type` value.
98
+ *
99
+ * **Discouraged for persisted data** — same reasoning as
100
+ * {@link withDecodingDefault}. Use explicit, preferably versioned migrations
101
+ * over decode-time fallbacks.
102
+ *
103
+ * @see {@link withDecodingDefaultKey} — `Encoded`-side variant
104
+ * @see {@link withDecodingDefaultType} — value-absent-or-undefined variant
105
+ * @see {@link withConstructorDefault} — for `.make(...)`-time defaults
106
+ */
107
+ export { withDecodingDefaultTypeKey } from "effect/Schema"
9
108
 
10
109
  export * from "effect/Schema"
11
- // v4: TaggedError renamed to TaggedErrorClass
12
- export { TaggedErrorClass as TaggedError } from "effect/Schema"
13
110
 
14
111
  export * from "./Schema/Class.js"
15
- export { Class, TaggedClass } from "./Schema/Class.js"
112
+ export { Class, ErrorClass, Opaque, TaggedClass, TaggedErrorClass } from "./Schema/Class.js"
16
113
 
17
114
  export { fromBrand, nominal } from "./Schema/brand.js"
18
- export { Array, Boolean, Date, Literal, Map, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
115
+ export { Array, Boolean, Date, DateFromString, DateValid, Finite, Literals, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
19
116
  export { Int, NonNegativeInt } from "./Schema/numbers.js"
20
117
 
21
118
  export * from "./Schema/email.js"
@@ -24,14 +121,217 @@ export * from "./Schema/moreStrings.js"
24
121
  export * from "./Schema/numbers.js"
25
122
  export * from "./Schema/phoneNumber.js"
26
123
  export * from "./Schema/schema.js"
124
+ export * from "./Schema/SpecialJsonSchema.js"
125
+ export * from "./Schema/SpecialOpenApi.js"
27
126
  export * from "./Schema/strings.js"
28
127
  export { NonEmptyString } from "./Schema/strings.js"
29
128
 
30
129
  export * as SchemaIssue from "effect/SchemaIssue"
31
- export * as SchemaParser from "effect/SchemaParser"
130
+
131
+ export const decodeEffectConcurrently: typeof S.decodeEffect = withDefaultParseOptions(S.decodeEffect)
132
+ export const decodeUnknownEffectConcurrently: typeof S.decodeUnknownEffect = withDefaultParseOptions(
133
+ S.decodeUnknownEffect
134
+ )
135
+ export * as SchemaParser from "./Schema/SchemaParser.js"
32
136
 
33
137
  export { Void as Void_ } from "effect/Schema"
34
138
 
139
+ // ---------------------------------------------------------------------------
140
+ // Struct / NonEmptyArray / Record
141
+ // ---------------------------------------------------------------------------
142
+
143
+ export function Struct<const Fields extends S.Struct.Fields>(
144
+ fields: Fields
145
+ ): Struct<Fields> {
146
+ const result = S.Struct(fields).annotate(concurrencyUnbounded)
147
+ const allowVoidMake = (schema: any): any => {
148
+ // Normalize omitted input to an empty object so optional/default-only structs can be constructed with make().
149
+ const origMake: any = schema.make
150
+ const origMakeOption: any = schema.makeOption
151
+ const origMakeEffect: any = schema.makeEffect
152
+ schema.make = function(this: any, input: any, options?: any) {
153
+ return origMake.call(this, input === undefined ? {} : input, options)
154
+ }
155
+ schema.makeOption = function(this: any, input: any, options?: any) {
156
+ return origMakeOption.call(this, input === undefined ? {} : input, options)
157
+ }
158
+ schema.makeEffect = function(this: any, input: any, options?: any) {
159
+ return origMakeEffect.call(this, input === undefined ? {} : input, options)
160
+ }
161
+ return schema
162
+ }
163
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
164
+ const origMapFields: any = result.mapFields
165
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
166
+ const origAnnotate: any = result.annotate
167
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
168
+ const origAnnotateKey: any = result.annotateKey
169
+
170
+ const preserveCopyAndMethods = (schema: any): any => {
171
+ schema.copy = copy
172
+ schema.mapFields = function(this: any, f: any, options?: any) {
173
+ return (result as any).mapFields.call(this, f, options)
174
+ }
175
+ schema.annotate = function(this: any, annotations?: any) {
176
+ return (result as any).annotate.call(this, annotations)
177
+ }
178
+ schema.annotateKey = function(this: any, annotations?: any) {
179
+ return (result as any).annotateKey.call(this, annotations)
180
+ }
181
+ return allowVoidMake(schema)
182
+ }
183
+ ;(result as any).mapFields = function(this: any, f: any, options?: any) {
184
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
185
+ const mapped = origMapFields.call(this, f, options).annotate(concurrencyUnbounded)
186
+ return preserveCopyAndMethods(mapped)
187
+ }
188
+ ;(result as any).annotate = function(this: any, annotations?: any) {
189
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
190
+ const annotated = origAnnotate.call(this, annotations)
191
+ return preserveCopyAndMethods(annotated)
192
+ }
193
+ ;(result as any).annotateKey = function(this: any, annotations?: any) {
194
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
195
+ const annotated = origAnnotateKey.call(this, annotations)
196
+ return preserveCopyAndMethods(annotated)
197
+ }
198
+ ;(result as any).copy = copy
199
+ allowVoidMake(result)
200
+ return result as Struct<Fields>
201
+ }
202
+
203
+ export interface Struct<Fields extends S.Struct.Fields> extends
204
+ S.Bottom<
205
+ Struct.Type<Fields>,
206
+ Struct.Encoded<Fields>,
207
+ Struct.DecodingServices<Fields>,
208
+ Struct.EncodingServices<Fields>,
209
+ AST.Objects,
210
+ // Rebuild is what's returned from annotate etc
211
+ Struct<Fields>,
212
+ Struct.MakeIn<Fields>,
213
+ Struct.Iso<Fields>
214
+ >
215
+ {
216
+ /**
217
+ * The field definitions of this struct. Spread them into a new struct to
218
+ * reuse fields across schemas.
219
+ *
220
+ * **Example** (Reusing fields across structs)
221
+ *
222
+ * ```ts
223
+ * import { Schema } from "effect"
224
+ *
225
+ * const Timestamped = Schema.Struct({
226
+ * createdAt: Schema.Date,
227
+ * updatedAt: Schema.Date
228
+ * })
229
+ *
230
+ * const User = Schema.Struct({
231
+ * ...Timestamped.fields,
232
+ * name: Schema.String,
233
+ * email: Schema.String
234
+ * })
235
+ * ```
236
+ */
237
+ readonly fields: Fields
238
+ /**
239
+ * Returns a new struct with the fields modified by the provided function.
240
+ *
241
+ * **Options**
242
+ *
243
+ * - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints
244
+ * that were attached to the original union. Defaults to `false`.
245
+ *
246
+ * **Warning**: This is an unsafe operation. Since `mapFields`
247
+ * transformations change the schema type, the original refinement functions
248
+ * may no longer be valid or safe to apply to the transformed schema. Only
249
+ * use this option if you have verified that your refinements remain correct
250
+ * after the transformation.
251
+ */
252
+ mapFields<To extends Struct.Fields>(
253
+ f: (fields: Fields) => To,
254
+ options?: {
255
+ readonly unsafePreserveChecks?: boolean | undefined
256
+ } | undefined
257
+ ): Struct<Simplify<Readonly<To>>>
258
+
259
+ // added copy
260
+ readonly copy: StructuralCopyOrigin<Struct.Type<Fields>>
261
+ }
262
+
263
+ export declare namespace Struct {
264
+ export type Fields = S.Struct.Fields
265
+ export type Type<F extends S.Struct.Fields> = S.Struct.Type<F>
266
+ export type Encoded<F extends S.Struct.Fields> = S.Struct.Encoded<F>
267
+ export type DecodingServices<F extends S.Struct.Fields> = S.Struct.DecodingServices<F>
268
+ export type EncodingServices<F extends S.Struct.Fields> = S.Struct.EncodingServices<F>
269
+ // changed; all optional allows void
270
+ export type MakeIn<F extends S.Struct.Fields> = RequiredKeys<S.Struct.MakeIn<F>> extends never
271
+ ? void | S.Struct.MakeIn<F>
272
+ : S.Struct.MakeIn<F>
273
+ export type Iso<F extends S.Struct.Fields> = S.Struct.Iso<F>
274
+ }
275
+
276
+ export type StructNestedEncodedError<T> = {
277
+ readonly _tag: "StructNestedEncodedError"
278
+ readonly message: "Expected a Struct schema or a schema with from.Encoded"
279
+ readonly schema: T
280
+ }
281
+
282
+ export type StructNestedEncoded<T> = T extends { fields: infer Fields extends S.Struct.Fields } ? Struct.Encoded<Fields>
283
+ : T extends { readonly from: { readonly Encoded: infer Encoded } } ? Encoded
284
+ : StructNestedEncodedError<T>
285
+
286
+ export function NonEmptyArray<Value extends S.Top>(value: Value): S.NonEmptyArray<Value> {
287
+ return S.NonEmptyArray(value).annotate(concurrencyUnbounded)
288
+ }
289
+
290
+ export function TaggedStruct<const Tag extends SchemaAST.LiteralValue, const Fields extends S.Struct.Fields>(
291
+ value: Tag,
292
+ fields: Fields
293
+ ): TaggedStruct<Tag, Fields> {
294
+ return Struct({ _tag: S.tag(value), ...fields }) as any
295
+ }
296
+ export interface TaggedStruct<Tag extends SchemaAST.LiteralValue, Fields extends S.Struct.Fields>
297
+ extends Struct<{ readonly _tag: S.tag<Tag> } & Fields>
298
+ {}
299
+ export declare namespace TaggedStruct {
300
+ export type Fields = S.Struct.Fields
301
+ export type Type<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.Type<
302
+ { readonly _tag: S.tag<Tag> } & F
303
+ >
304
+ export type Encoded<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.Encoded<
305
+ { readonly _tag: S.tag<Tag> } & F
306
+ >
307
+ export type DecodingServices<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> =
308
+ S.Struct.DecodingServices<
309
+ { readonly _tag: S.tag<Tag> } & F
310
+ >
311
+ export type EncodingServices<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> =
312
+ S.Struct.EncodingServices<
313
+ { readonly _tag: S.tag<Tag> } & F
314
+ >
315
+ export type MakeIn<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.MakeIn<
316
+ { readonly _tag: S.tag<Tag> } & F
317
+ >
318
+ export type Iso<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.Iso<
319
+ { readonly _tag: S.tag<Tag> } & F
320
+ >
321
+ }
322
+
323
+ export function Record<Key extends S.Record.Key, Value extends S.Top>(
324
+ key: Key,
325
+ value: Value
326
+ ): S.$Record<Key, Value> {
327
+ return S.Record(key, value).annotate(concurrencyUnbounded)
328
+ }
329
+ export declare namespace Record {
330
+ export type Key = S.Record.Key
331
+ export type Type<K extends S.Record.Key, V extends S.Top> = S.Record.Type<K, V>
332
+ export type Encoded<K extends S.Record.Key, V extends S.Top> = S.Record.Encoded<K, V>
333
+ }
334
+
35
335
  export const SpanId = Symbol()
36
336
  export type SpanId = typeof SpanId
37
337
 
@@ -65,46 +365,136 @@ export const PhoneNumber = PhoneNumberT
65
365
 
66
366
  export type PhoneNumber = PhoneNumberType
67
367
 
68
- const getTagLiteral = <Tag extends string>(schema: S.tag<Tag>): Tag => {
69
- if (!SchemaAST.isLiteral(schema.ast)) {
70
- throw new Error("Unsupported _tag schema: expected a literal AST")
368
+ // Copied from SchemaAST.collectSentinels (marked @internal in effect).
369
+ // Returns all { key, literal } pairs that can discriminate a union member.
370
+ const getTagFromAST = (schema: S.Top): string => {
371
+ const sentinels = collectSentinelsFromAST(schema.ast)
372
+ const sentinel = sentinels.find((s) => s.key === "_tag")
373
+ if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
374
+ throw new Error("No _tag literal found on schema member")
375
+ }
376
+
377
+ function collectSentinelsFromAST(
378
+ ast: SchemaAST.AST
379
+ ): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
380
+ switch (ast._tag) {
381
+ case "Declaration": {
382
+ const s = ast.annotations?.["~sentinels"]
383
+ return Array.isArray(s) ? s : []
384
+ }
385
+ case "Objects":
386
+ return ast.propertySignatures.flatMap(
387
+ (ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
388
+ const type = ps.type
389
+ if (!SchemaAST.isOptional(type)) {
390
+ if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
391
+ if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
392
+ }
393
+ return []
394
+ }
395
+ )
396
+ case "Suspend":
397
+ return collectSentinelsFromAST(ast.thunk())
398
+ default:
399
+ return []
71
400
  }
72
- return schema.ast.literal as Tag
73
401
  }
74
402
 
75
403
  export const tags = <
76
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
- Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
404
+ Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
78
405
  >(
79
406
  self: Members
80
407
  ) =>
81
408
  S.Literals(
82
- self.map((key) => getTagLiteral(key.fields._tag)) as {
83
- [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
409
+ self.map(getTagFromAST) as {
410
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
84
411
  }
85
412
  ) as S.Literals<
86
413
  {
87
- [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
414
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
88
415
  }
89
416
  >
90
417
 
91
418
  type TaggedUnionMembers = NonEmptyReadonlyArray<
92
- S.Top & { readonly Type: { readonly _tag: string }; fields: { _tag: S.tag<string> } }
419
+ S.Top & { readonly Type: { readonly _tag: string } }
93
420
  >
94
421
 
95
422
  type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
96
423
  {
97
- [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
424
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
98
425
  }
99
426
  >
100
427
 
428
+ type TaggedPropertyKeys<A, Members extends TaggedUnionMembers> = {
429
+ [K in keyof A & string]: A[K] extends Members[number]["Type"] ? K : never
430
+ }[keyof A & string]
431
+
432
+ type PropertyGuardsFor<
433
+ Members extends TaggedUnionMembers,
434
+ K extends string,
435
+ A
436
+ > =
437
+ & {
438
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: (
439
+ target: A
440
+ ) => target is A & { readonly [P in K]: M["Type"] }
441
+ }
442
+ & {
443
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
444
+ tags: Tags
445
+ ) => (
446
+ target: A
447
+ ) => target is A & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
448
+ }
449
+
450
+ type PropertyGuards<
451
+ Members extends TaggedUnionMembers,
452
+ K extends string
453
+ > =
454
+ & {
455
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: <
456
+ T extends { readonly [P in K]: Members[number]["Type"] }
457
+ >(target: T) => target is T & { readonly [P in K]: M["Type"] }
458
+ }
459
+ & {
460
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
461
+ tags: Tags
462
+ ) => <T extends { readonly [P in K]: Members[number]["Type"] }>(
463
+ target: T
464
+ ) => target is T & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
465
+ }
466
+
101
467
  type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
102
468
  readonly tags: TaggedUnionTags<Members>
469
+ readonly generateGuards: <K extends string>(property: K) => PropertyGuards<Members, K>
470
+ readonly generateGuardsFor: <A>() => <K extends TaggedPropertyKeys<A, Members>>(
471
+ property: K
472
+ ) => PropertyGuardsFor<Members, K, A>
103
473
  }
104
474
 
105
475
  const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
106
476
  schema: S.Union<Members>
107
- ): TaggedUnionWithTags<Members> => extendM(schema.pipe(S.toTaggedUnion("_tag")), () => ({ tags: tags(schema.members) }))
477
+ ): TaggedUnionWithTags<Members> =>
478
+ extendM(schema.pipe(S.toTaggedUnion("_tag")), (tagged) => {
479
+ const makeGuards = (property: string) => {
480
+ const result: any = {}
481
+ const guards: Record<string, (u: unknown) => boolean> = tagged.guards
482
+ for (const tag of Object.keys(guards)) {
483
+ const guard = guards[tag]!
484
+ result[`is${tag}`] = (target: any) => guard(target[property])
485
+ }
486
+ result.isAnyOf = (memberTags: Array<string>) => {
487
+ const check = tagged.isAnyOf(memberTags)
488
+ return (target: any) => check(target[property])
489
+ }
490
+ return result
491
+ }
492
+ return {
493
+ tags: tags(schema.members),
494
+ generateGuards: makeGuards,
495
+ generateGuardsFor: () => makeGuards
496
+ }
497
+ })
108
498
 
109
499
  export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
110
500
  schema: S.Union<Members>
@@ -112,4 +502,4 @@ export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
112
502
 
113
503
  export const TaggedUnion = <
114
504
  Members extends TaggedUnionMembers
115
- >(...a: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(a))
505
+ >(members: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(members))
@@ -0,0 +1,50 @@
1
+ import * as Ref from "effect/Ref"
2
+ import * as Context from "../Context.js"
3
+ import * as Effect from "../Effect.js"
4
+ import type { InvalidationKey } from "../rpc/Invalidation.js"
5
+
6
+ export type { InvalidationKey }
7
+ /** Shape of the per-mutation service that accumulates server-provided invalidation keys. */
8
+ export interface InvalidationKeysService {
9
+ readonly add: (key: InvalidationKey) => Effect.Effect<void>
10
+ readonly get: Effect.Effect<ReadonlyArray<InvalidationKey>>
11
+ }
12
+
13
+ /**
14
+ * Context.Reference that accumulates invalidation keys received from the server via the
15
+ * `x-invalidate` HTTP response header.
16
+ *
17
+ * The default is a no-op: when not explicitly provided (e.g. outside a mutation wrapper)
18
+ * all calls are ignored. The mutation wrapper in `@effect-app/vue` provides a real
19
+ * implementation backed by a `Ref`.
20
+ */
21
+ export const InvalidationKeysFromServer = Context.Reference<InvalidationKeysService>(
22
+ "effect-app/client/InvalidationKeysFromServer",
23
+ {
24
+ defaultValue: () => ({
25
+ add: (_key: InvalidationKey) => Effect.void,
26
+ get: Effect.succeed([] as ReadonlyArray<InvalidationKey>)
27
+ })
28
+ }
29
+ )
30
+ export type InvalidationKeysFromServer = typeof InvalidationKeysFromServer
31
+
32
+ /**
33
+ * Creates a fresh `InvalidationKeysService` implementation backed by a `Ref`.
34
+ *
35
+ * @param ref - The `Ref` that stores the accumulated keys.
36
+ * @param onAdded - V3: Optional Effect run after a key is added. Use to trigger mid-stream
37
+ * query invalidation without waiting for the stream to complete.
38
+ */
39
+ export const makeInvalidationKeysService = (
40
+ ref: Ref.Ref<ReadonlyArray<InvalidationKey>>,
41
+ onAdded?: (key: InvalidationKey) => Effect.Effect<void>
42
+ ): InvalidationKeysService => ({
43
+ // When onAdded is set, fire it immediately without accumulating in the ref —
44
+ // the key is handled on arrival and must not be re-processed at stream end.
45
+ add: (key) =>
46
+ onAdded
47
+ ? onAdded(key)
48
+ : Ref.update(ref, (keys) => [...keys, key]),
49
+ get: Ref.get(ref)
50
+ })