effect-app 4.0.0-beta.23 → 4.0.0-beta.230

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 (241) hide show
  1. package/CHANGELOG.md +1004 -0
  2. package/dist/Array.d.ts +3 -2
  3. package/dist/Array.d.ts.map +1 -1
  4. package/dist/Array.js +4 -4
  5. package/dist/Chunk.d.ts +1 -1
  6. package/dist/Chunk.d.ts.map +1 -1
  7. package/dist/Config/SecretURL.d.ts +4 -2
  8. package/dist/Config/SecretURL.d.ts.map +1 -1
  9. package/dist/Config/SecretURL.js +3 -6
  10. package/dist/Config/internal/configSecretURL.d.ts +1 -1
  11. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  12. package/dist/Config/internal/configSecretURL.js +2 -2
  13. package/dist/Config.d.ts +7 -0
  14. package/dist/Config.d.ts.map +1 -0
  15. package/dist/Config.js +6 -0
  16. package/dist/ConfigProvider.d.ts +39 -0
  17. package/dist/ConfigProvider.d.ts.map +1 -0
  18. package/dist/ConfigProvider.js +42 -0
  19. package/dist/Context.d.ts +43 -0
  20. package/dist/Context.d.ts.map +1 -0
  21. package/dist/Context.js +67 -0
  22. package/dist/Effect.d.ts +11 -10
  23. package/dist/Effect.d.ts.map +1 -1
  24. package/dist/Effect.js +6 -7
  25. package/dist/Function.d.ts +1 -1
  26. package/dist/Function.d.ts.map +1 -1
  27. package/dist/Inputify.type.d.ts +1 -1
  28. package/dist/Layer.d.ts +11 -6
  29. package/dist/Layer.d.ts.map +1 -1
  30. package/dist/Layer.js +3 -2
  31. package/dist/NonEmptySet.d.ts +4 -2
  32. package/dist/NonEmptySet.d.ts.map +1 -1
  33. package/dist/NonEmptySet.js +2 -2
  34. package/dist/Option.d.ts +2 -1
  35. package/dist/Option.d.ts.map +1 -1
  36. package/dist/Option.js +3 -1
  37. package/dist/Pure.d.ts +8 -6
  38. package/dist/Pure.d.ts.map +1 -1
  39. package/dist/Pure.js +17 -14
  40. package/dist/Schema/Class.d.ts +66 -20
  41. package/dist/Schema/Class.d.ts.map +1 -1
  42. package/dist/Schema/Class.js +192 -23
  43. package/dist/Schema/FastCheck.d.ts +1 -1
  44. package/dist/Schema/FastCheck.d.ts.map +1 -1
  45. package/dist/Schema/Methods.d.ts +1 -1
  46. package/dist/Schema/SchemaParser.d.ts +5 -0
  47. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  48. package/dist/Schema/SchemaParser.js +6 -0
  49. package/dist/Schema/SpecialJsonSchema.d.ts +34 -0
  50. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  51. package/dist/Schema/SpecialJsonSchema.js +118 -0
  52. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  53. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  54. package/dist/Schema/SpecialOpenApi.js +123 -0
  55. package/dist/Schema/brand.d.ts +5 -3
  56. package/dist/Schema/brand.d.ts.map +1 -1
  57. package/dist/Schema/brand.js +3 -1
  58. package/dist/Schema/email.d.ts +1 -1
  59. package/dist/Schema/email.d.ts.map +1 -1
  60. package/dist/Schema/email.js +7 -4
  61. package/dist/Schema/ext.d.ts +339 -56
  62. package/dist/Schema/ext.d.ts.map +1 -1
  63. package/dist/Schema/ext.js +351 -53
  64. package/dist/Schema/moreStrings.d.ts +108 -26
  65. package/dist/Schema/moreStrings.d.ts.map +1 -1
  66. package/dist/Schema/moreStrings.js +45 -16
  67. package/dist/Schema/numbers.d.ts +55 -15
  68. package/dist/Schema/numbers.d.ts.map +1 -1
  69. package/dist/Schema/numbers.js +60 -12
  70. package/dist/Schema/phoneNumber.d.ts +1 -1
  71. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  72. package/dist/Schema/phoneNumber.js +6 -3
  73. package/dist/Schema/schema.d.ts +1 -1
  74. package/dist/Schema/strings.d.ts +5 -5
  75. package/dist/Schema/strings.d.ts.map +1 -1
  76. package/dist/Schema/strings.js +1 -5
  77. package/dist/Schema.d.ts +214 -8
  78. package/dist/Schema.d.ts.map +1 -1
  79. package/dist/Schema.js +219 -21
  80. package/dist/Set.d.ts +5 -2
  81. package/dist/Set.d.ts.map +1 -1
  82. package/dist/Set.js +3 -2
  83. package/dist/TypeTest.d.ts +1 -1
  84. package/dist/Types.d.ts +1 -1
  85. package/dist/Widen.type.d.ts +1 -1
  86. package/dist/_ext/Array.d.ts +2 -2
  87. package/dist/_ext/Array.d.ts.map +1 -1
  88. package/dist/_ext/Array.js +4 -2
  89. package/dist/_ext/date.d.ts +1 -1
  90. package/dist/_ext/misc.d.ts +5 -2
  91. package/dist/_ext/misc.d.ts.map +1 -1
  92. package/dist/_ext/misc.js +4 -2
  93. package/dist/_ext/ord.ext.d.ts +3 -2
  94. package/dist/_ext/ord.ext.d.ts.map +1 -1
  95. package/dist/_ext/ord.ext.js +2 -2
  96. package/dist/builtin.d.ts +1 -1
  97. package/dist/builtin.d.ts.map +1 -1
  98. package/dist/client/InvalidationKeys.d.ts +29 -0
  99. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  100. package/dist/client/InvalidationKeys.js +33 -0
  101. package/dist/client/apiClientFactory.d.ts +20 -32
  102. package/dist/client/apiClientFactory.d.ts.map +1 -1
  103. package/dist/client/apiClientFactory.js +96 -33
  104. package/dist/client/clientFor.d.ts +51 -17
  105. package/dist/client/clientFor.d.ts.map +1 -1
  106. package/dist/client/clientFor.js +9 -1
  107. package/dist/client/errors.d.ts +49 -25
  108. package/dist/client/errors.d.ts.map +1 -1
  109. package/dist/client/errors.js +43 -17
  110. package/dist/client/makeClient.d.ts +481 -33
  111. package/dist/client/makeClient.d.ts.map +1 -1
  112. package/dist/client/makeClient.js +66 -24
  113. package/dist/client.d.ts +2 -1
  114. package/dist/client.d.ts.map +1 -1
  115. package/dist/client.js +2 -1
  116. package/dist/faker.d.ts +1 -1
  117. package/dist/faker.d.ts.map +1 -1
  118. package/dist/http/Request.d.ts +2 -2
  119. package/dist/http/Request.d.ts.map +1 -1
  120. package/dist/http/Request.js +2 -2
  121. package/dist/http/internal/lib.d.ts +1 -1
  122. package/dist/http.d.ts +1 -1
  123. package/dist/ids.d.ts +40 -12
  124. package/dist/ids.d.ts.map +1 -1
  125. package/dist/ids.js +25 -3
  126. package/dist/index.d.ts +5 -8
  127. package/dist/index.d.ts.map +1 -1
  128. package/dist/index.js +6 -8
  129. package/dist/logger.d.ts +1 -1
  130. package/dist/middleware.d.ts +14 -8
  131. package/dist/middleware.d.ts.map +1 -1
  132. package/dist/middleware.js +14 -8
  133. package/dist/rpc/Invalidation.d.ts +402 -0
  134. package/dist/rpc/Invalidation.d.ts.map +1 -0
  135. package/dist/rpc/Invalidation.js +150 -0
  136. package/dist/rpc/MiddlewareMaker.d.ts +11 -7
  137. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  138. package/dist/rpc/MiddlewareMaker.js +59 -38
  139. package/dist/rpc/RpcContextMap.d.ts +4 -4
  140. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  141. package/dist/rpc/RpcContextMap.js +4 -4
  142. package/dist/rpc/RpcMiddleware.d.ts +14 -10
  143. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  144. package/dist/rpc/RpcMiddleware.js +1 -1
  145. package/dist/rpc.d.ts +2 -2
  146. package/dist/rpc.d.ts.map +1 -1
  147. package/dist/rpc.js +2 -2
  148. package/dist/transform.d.ts +2 -2
  149. package/dist/transform.d.ts.map +1 -1
  150. package/dist/transform.js +4 -5
  151. package/dist/utils/effectify.d.ts +2 -2
  152. package/dist/utils/effectify.d.ts.map +1 -1
  153. package/dist/utils/effectify.js +2 -2
  154. package/dist/utils/extend.d.ts +1 -1
  155. package/dist/utils/extend.d.ts.map +1 -1
  156. package/dist/utils/gen.d.ts +2 -2
  157. package/dist/utils/gen.d.ts.map +1 -1
  158. package/dist/utils/logLevel.d.ts +3 -3
  159. package/dist/utils/logLevel.d.ts.map +1 -1
  160. package/dist/utils/logger.d.ts +5 -4
  161. package/dist/utils/logger.d.ts.map +1 -1
  162. package/dist/utils/logger.js +4 -4
  163. package/dist/utils.d.ts +35 -40
  164. package/dist/utils.d.ts.map +1 -1
  165. package/dist/utils.js +19 -27
  166. package/dist/validation/validators.d.ts +1 -1
  167. package/dist/validation/validators.d.ts.map +1 -1
  168. package/dist/validation.d.ts +1 -1
  169. package/dist/validation.d.ts.map +1 -1
  170. package/package.json +46 -24
  171. package/src/Array.ts +3 -3
  172. package/src/Config/SecretURL.ts +5 -2
  173. package/src/Config/internal/configSecretURL.ts +1 -1
  174. package/src/Config.ts +14 -0
  175. package/src/ConfigProvider.ts +48 -0
  176. package/src/{ServiceMap.ts → Context.ts} +56 -60
  177. package/src/Effect.ts +14 -14
  178. package/src/Layer.ts +10 -5
  179. package/src/NonEmptySet.ts +3 -1
  180. package/src/Option.ts +2 -0
  181. package/src/Pure.ts +21 -19
  182. package/src/Schema/Class.ts +274 -64
  183. package/src/Schema/SchemaParser.ts +12 -0
  184. package/src/Schema/SpecialJsonSchema.ts +139 -0
  185. package/src/Schema/SpecialOpenApi.ts +130 -0
  186. package/src/Schema/brand.ts +22 -2
  187. package/src/Schema/email.ts +7 -2
  188. package/src/Schema/ext.ts +432 -88
  189. package/src/Schema/moreStrings.ts +93 -37
  190. package/src/Schema/numbers.ts +64 -16
  191. package/src/Schema/phoneNumber.ts +5 -1
  192. package/src/Schema/strings.ts +4 -8
  193. package/src/Schema.ts +404 -18
  194. package/src/Set.ts +5 -1
  195. package/src/_ext/Array.ts +3 -1
  196. package/src/_ext/misc.ts +4 -1
  197. package/src/_ext/ord.ext.ts +2 -1
  198. package/src/client/InvalidationKeys.ts +50 -0
  199. package/src/client/apiClientFactory.ts +224 -130
  200. package/src/client/clientFor.ts +95 -29
  201. package/src/client/errors.ts +52 -26
  202. package/src/client/makeClient.ts +572 -71
  203. package/src/client.ts +1 -0
  204. package/src/http/Request.ts +1 -1
  205. package/src/ids.ts +25 -3
  206. package/src/index.ts +5 -10
  207. package/src/middleware.ts +13 -9
  208. package/src/rpc/Invalidation.ts +226 -0
  209. package/src/rpc/MiddlewareMaker.ts +82 -74
  210. package/src/rpc/README.md +2 -2
  211. package/src/rpc/RpcContextMap.ts +6 -5
  212. package/src/rpc/RpcMiddleware.ts +14 -11
  213. package/src/rpc.ts +1 -1
  214. package/src/transform.ts +3 -3
  215. package/src/utils/effectify.ts +1 -1
  216. package/src/utils/gen.ts +1 -1
  217. package/src/utils/logLevel.ts +1 -1
  218. package/src/utils/logger.ts +4 -3
  219. package/src/utils.ts +57 -134
  220. package/test/dist/rpc.test.d.ts.map +1 -1
  221. package/test/dist/secretURL.test.d.ts.map +1 -0
  222. package/test/dist/special.test.d.ts.map +1 -0
  223. package/test/moreStrings.test.ts +1 -1
  224. package/test/rpc.test.ts +46 -6
  225. package/test/schema.test.ts +585 -10
  226. package/test/secretURL.test.ts +160 -0
  227. package/test/special.test.ts +1026 -0
  228. package/test/utils.test.ts +7 -7
  229. package/tsconfig.base.json +3 -4
  230. package/tsconfig.json +0 -1
  231. package/tsconfig.json.bak +2 -2
  232. package/tsconfig.src.json +29 -29
  233. package/tsconfig.test.json +2 -2
  234. package/dist/Operations.d.ts +0 -123
  235. package/dist/Operations.d.ts.map +0 -1
  236. package/dist/Operations.js +0 -29
  237. package/dist/ServiceMap.d.ts +0 -44
  238. package/dist/ServiceMap.d.ts.map +0 -1
  239. package/dist/ServiceMap.js +0 -91
  240. package/eslint.config.mjs +0 -26
  241. package/src/Operations.ts +0 -55
package/src/Schema/ext.ts CHANGED
@@ -1,8 +1,47 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-return */
3
- import { Effect, Option, pipe, Schema, type SchemaAST, SchemaGetter, SchemaIssue, SchemaTransformation, ServiceMap } from "effect"
3
+ /**
4
+ * # `withConstructorDefault` policy
5
+ *
6
+ * The `withConstructorDefault` properties exported throughout this module
7
+ * (and from `numbers.ts`, `moreStrings.ts`, `ids.ts`) attach a default value
8
+ * that is **only** applied during construction — i.e. when the field is
9
+ * omitted from the input to a Schema constructor / `.make(...)` call.
10
+ *
11
+ * They are **NOT** applied during `decode` (JSON, database rows, RPC payloads,
12
+ * etc.). Decoding a payload with a missing field will still fail with a parse
13
+ * error, exactly as if the default were not present.
14
+ *
15
+ * Concretely this means `withConstructorDefault` MUST NOT be relied on as a
16
+ * just-in-time migration mechanism for database fields. If a stored record is
17
+ * missing a newly added field, the constructor default will not fill it in on
18
+ * read — decoding will fail.
19
+ *
20
+ * ## Don't reach for `withDecodingDefault*` either
21
+ *
22
+ * The sibling `withDecodingDefaultType` (and `withDecodingDefault`) extensions
23
+ * exist, but they are discouraged for migrating persisted data. A missing
24
+ * field in a stored record is just as likely to be data corruption as it is
25
+ * an old-shape document; silently substituting a default hides the problem
26
+ * and can poison downstream aggregates.
27
+ *
28
+ * Prefer an **explicit, preferably versioned** migration of database data
29
+ * (a schema-version field, a one-shot backfill, or a transform on read that
30
+ * is gated on an explicit version marker) over shoving missing fields under
31
+ * the rug with a decode-time default.
32
+ */
33
+ import * as Config from "effect/Config"
34
+ import * as Effect from "effect/Effect"
35
+ import { pipe } from "effect/Function"
36
+ import * as Function from "effect/Function"
37
+ import * as Option from "effect/Option"
4
38
  import * as S from "effect/Schema"
39
+ import { isDateValid } from "effect/Schema"
40
+ import type * as SchemaAST from "effect/SchemaAST"
41
+ import * as SchemaIssue from "effect/SchemaIssue"
42
+ import * as SchemaTransformation from "effect/SchemaTransformation"
5
43
  import { type NonEmptyReadonlyArray } from "../Array.js"
44
+ import * as Context from "../Context.js"
6
45
  import { extendM, typedKeysOf } from "../utils.js"
7
46
  import { type AST } from "./schema.js"
8
47
 
@@ -13,131 +52,418 @@ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
13
52
  Exclude<Self["EncodingServices"], R>
14
53
  >
15
54
 
16
- // TODO: v4 migration — withConstructorDefault signature changed, propertySignature removed
17
- // Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
18
- // because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
19
- // The narrowing assertions below are safe — we're asserting "this schema hasn't had a default applied yet".
20
- export const withDefaultConstructor = <A>(
21
- makeDefault: () => NoInfer<A>
22
- ) =>
23
- <Self extends S.Top>(self: Self): S.withConstructorDefault<Self & S.WithoutConstructorDefault> => {
24
- type Narrowed = Self & S.WithoutConstructorDefault
25
- return S.withConstructorDefault<Narrowed>(
26
- () => Option.some(makeDefault() as Narrowed["~type.make.in"])
27
- )(self as Narrowed)
55
+ const concurrencySetting = Effect.runSync(
56
+ Config
57
+ .literal("unbounded", "SCHEMA_CONCURRENCY")
58
+ .pipe(Config.orElse(() => Config.number("SCHEMA_CONCURRENCY")), Config.option)
59
+ .asEffect()
60
+ )
61
+
62
+ export const DefaultParseOptions: SchemaAST.ParseOptions = {
63
+ concurrency: Option.getOrElse(concurrencySetting, () => "unbounded" as const)
28
64
  }
29
65
 
66
+ /**
67
+ * Parse-options annotation used on schema constructors for decode paths where callers
68
+ * cannot currently pass parse options (notably some RPC / HttpApi integration paths).
69
+ *
70
+ * Keep this annotation in place so those framework-managed decodes still run with
71
+ * unbounded concurrency by default.
72
+ */
73
+ export const concurrencyUnbounded = { parseOptions: DefaultParseOptions } as const
74
+
75
+ type DecodeLike = (schema: any) => (input: any, options?: SchemaAST.ParseOptions) => any
76
+
77
+ export const withDefaultParseOptions = <Decode extends DecodeLike>(
78
+ decode: Decode,
79
+ defaultParseOptions: SchemaAST.ParseOptions = DefaultParseOptions
80
+ ): Decode =>
81
+ ((schema: any) => {
82
+ const run = decode(schema)
83
+ return (input: any, options?: SchemaAST.ParseOptions) => run(input, { ...defaultParseOptions, ...options })
84
+ }) as Decode
85
+
30
86
  // TODO: v4 migration - Date is no longer by default encoded to string.
31
- const DateFromString = Schema.Date.pipe(
32
- Schema.encodeTo(Schema.String, {
33
- decode: SchemaGetter.Date(),
34
- encode: SchemaGetter.transform((_) => _.toISOString())
35
- })
36
- )
87
+
88
+ const DateString = S.String.annotate({
89
+ identifier: "Date",
90
+ description: "a string in ISO 8601 format that will be decoded as a Date",
91
+ format: "date-time"
92
+ })
37
93
 
38
94
  /**
39
- * Like the default Schema `Date` but from String with `withDefault` => now
95
+ * Schema type for {@link DateFromString}.
96
+ *
97
+ * @category Schemas
98
+ * @since 4.0.0
40
99
  */
41
- export const Date = Object.assign(DateFromString, {
42
- withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
43
- })
100
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
44
101
 
45
102
  /**
46
- * Like the default Schema `Boolean` but with `withDefault` => false
103
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
104
+ *
105
+ * Decoding:
106
+ * - A `string` is decoded as a `Date`.
107
+ *
108
+ * Encoding:
109
+ * - A `Date` is encoded as a `string`.
110
+ *
111
+ * @since 4.0.0
47
112
  */
113
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
114
+
115
+ /** Like the default Schema `Date` but from String, with default helpers. */
116
+ export const Date = Object.assign(DateFromString, {
117
+ /**
118
+ * Construction-only default `new Date()`. Applied only when the field is
119
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
120
+ * used to JIT-migrate database fields. See file-level note.
121
+ */
122
+ withConstructorDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
123
+ /**
124
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
125
+ * missing field may be data corruption, not an old-shape document; silently
126
+ * substituting `new Date()` hides the problem. Prefer an explicit,
127
+ * preferably versioned migration over a decode-time fallback. See
128
+ * file-level note.
129
+ */
130
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
131
+ })
132
+
133
+ /** Like the default Schema `DateValid` but from String, with default helpers. */
134
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
135
+ /**
136
+ * Construction-only default `new Date()`. Applied only when the field is
137
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
138
+ * used to JIT-migrate database fields. See file-level note.
139
+ */
140
+ withConstructorDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
141
+ /**
142
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
143
+ * missing field may be data corruption, not an old-shape document; silently
144
+ * substituting `new Date()` hides the problem. Prefer an explicit,
145
+ * preferably versioned migration over a decode-time fallback. See
146
+ * file-level note.
147
+ */
148
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
149
+ })
150
+
151
+ /** Like the default Schema `Boolean` but with default helpers. */
48
152
  export const Boolean = Object.assign(S.Boolean, {
49
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
153
+ /**
154
+ * Construction-only default `false`. Applied only when the field is
155
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
156
+ * used to JIT-migrate database fields. See file-level note.
157
+ */
158
+ withConstructorDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false))),
159
+ /**
160
+ * Decode-time default `false`. **Discouraged for persisted data:** a
161
+ * missing field may be data corruption, not an old-shape document; silently
162
+ * substituting `false` hides the problem. Prefer an explicit, preferably
163
+ * versioned migration over a decode-time fallback. See file-level note.
164
+ */
165
+ withDecodingDefaultType: S.Boolean.pipe(S.withDecodingDefaultType(Effect.succeed(false)))
50
166
  })
51
167
 
52
168
  /**
53
- * Like the default Schema `Number` but with `withDefault` => 0
169
+ * You probably want to use `Finite` instead of this. Like the default Schema
170
+ * `Number` but with default helpers.
54
171
  */
55
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
172
+ export const Number = Object.assign(S.Number, {
173
+ /**
174
+ * Construction-only default `0`. Applied only when the field is omitted
175
+ * from `.make(...)` input. NOT applied during decode — cannot be used to
176
+ * JIT-migrate database fields. See file-level note.
177
+ */
178
+ withConstructorDefault: S.Number.pipe(S.withConstructorDefault(Effect.succeed(0))),
179
+ /**
180
+ * Decode-time default `0`. **Discouraged for persisted data:** a missing
181
+ * field may be data corruption, not an old-shape document; silently
182
+ * substituting `0` hides the problem. Prefer an explicit, preferably
183
+ * versioned migration over a decode-time fallback. See file-level note.
184
+ */
185
+ withDecodingDefaultType: S.Number.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
186
+ })
56
187
 
57
- /**
58
- * Like the default Schema `Literal` but with `withDefault` => literals[0]
59
- */
60
- export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(...literals: Literals) =>
188
+ /** Like the default Schema `Finite` but with default helpers. */
189
+ export const Finite = Object.assign(S.Finite, {
190
+ /**
191
+ * Construction-only default `0`. Applied only when the field is omitted
192
+ * from `.make(...)` input. NOT applied during decode — cannot be used to
193
+ * JIT-migrate database fields. See file-level note.
194
+ */
195
+ withConstructorDefault: S.Finite.pipe(S.withConstructorDefault(Effect.succeed(0))),
196
+ /**
197
+ * Decode-time default `0`. **Discouraged for persisted data:** a missing
198
+ * field may be data corruption, not an old-shape document; silently
199
+ * substituting `0` hides the problem. Prefer an explicit, preferably
200
+ * versioned migration over a decode-time fallback. See file-level note.
201
+ */
202
+ withDecodingDefaultType: S.Finite.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
203
+ })
204
+
205
+ /** Like the default Schema `Literals` but with default helpers. Default value is `literals[0]`. */
206
+ export const Literals = <const Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(literals: Literals) =>
61
207
  pipe(
62
208
  S.Literals(literals),
63
209
  (s) =>
64
210
  Object.assign(s, {
211
+ /** Override the default literal value used by `withConstructorDefault` / `withDecodingDefaultType`. */
65
212
  changeDefault: <A extends Literals[number]>(a: A) => {
66
213
  return Object.assign(S.Literals(literals), {
67
214
  Default: a,
68
- withDefault: s.pipe(withDefaultConstructor(() => a))
215
+ /**
216
+ * Construction-only default. Applied only when the field is
217
+ * omitted from `.make(...)` input. NOT applied during decode —
218
+ * cannot be used to JIT-migrate database fields. See file-level
219
+ * note.
220
+ */
221
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a))),
222
+ /**
223
+ * Decode-time default. **Discouraged for persisted data:** a
224
+ * missing field may be data corruption, not an old-shape
225
+ * document; silently substituting hides the problem. Prefer an
226
+ * explicit, preferably versioned migration over a decode-time
227
+ * fallback. See file-level note.
228
+ */
229
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(a)))
69
230
  }) // todo: copy annotations from original?
70
231
  },
71
- Default: literals[0] as typeof literals[0],
72
- withDefault: s.pipe(withDefaultConstructor(() => literals[0]))
232
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- load-bearing: Object.assign widens the field type without it, breaking `expectTypeOf(l.Default).toEqualTypeOf<"a">()` in tests
233
+ Default: literals[0] as Literals[0],
234
+ /**
235
+ * Construction-only default `literals[0]`. Applied only when the
236
+ * field is omitted from `.make(...)` input. NOT applied during
237
+ * decode — cannot be used to JIT-migrate database fields. See
238
+ * file-level note.
239
+ */
240
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(literals[0]))),
241
+ /**
242
+ * Decode-time default `literals[0]`. **Discouraged for persisted
243
+ * data:** a missing field may be data corruption, not an old-shape
244
+ * document; silently substituting hides the problem. Prefer an
245
+ * explicit, preferably versioned migration over a decode-time
246
+ * fallback. See file-level note.
247
+ */
248
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(literals[0])))
73
249
  })
74
250
  )
75
251
 
76
- /**
77
- * Like the default Schema `Array` but with `withDefault` => []
78
- */
252
+ /** Like the default Schema `Array` but with default helpers. */
79
253
  export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
80
254
  return pipe(
81
- S.Array(value),
82
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
255
+ S.Array(value).annotate(concurrencyUnbounded),
256
+ (s) =>
257
+ Object.assign(s, {
258
+ /**
259
+ * Construction-only default `[]`. Applied only when the field is
260
+ * omitted from `.make(...)` input. NOT applied during decode —
261
+ * cannot be used to JIT-migrate database fields. See file-level
262
+ * note.
263
+ */
264
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => []))),
265
+ /**
266
+ * Decode-time default `[]`. **Discouraged for persisted data:** a
267
+ * missing field may be data corruption, not an old-shape document;
268
+ * silently substituting `[]` hides the problem. Prefer an explicit,
269
+ * preferably versioned migration over a decode-time fallback. See
270
+ * file-level note.
271
+ */
272
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => [])))
273
+ })
83
274
  )
84
275
  }
85
276
 
86
277
  /**
87
- * Like the default Schema `Map` but with `withDefault` => []
278
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
88
279
  */
89
- function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
90
- return pipe(
91
- S.ReadonlyMap(input.key, input.value),
92
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
280
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
281
+ const from = S
282
+ .Array(value)
283
+ .annotate({ ...concurrencyUnbounded, expected: "an array of unique items that will be decoded as a ReadonlySet" })
284
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
285
+ const schema = from.pipe(
286
+ S.decodeTo(
287
+ to,
288
+ SchemaTransformation.transform({
289
+ decode: (arr) => new Set(arr) as ReadonlySet<S.Schema.Type<ValueSchema>>,
290
+ encode: (set) => [...set]
291
+ })
292
+ )
93
293
  )
294
+ return schema
94
295
  }
95
296
 
96
- export { Map_ as Map }
97
-
98
297
  /**
99
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
298
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
100
299
  */
300
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
301
+ readonly key: KeySchema
302
+ readonly value: ValueSchema
303
+ }) => {
304
+ const from = S
305
+ .Array(S.Tuple([pair.key, pair.value]))
306
+ .annotate({
307
+ ...concurrencyUnbounded,
308
+ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap"
309
+ })
310
+ const to = S.instanceOf(Map) as S.instanceOf<
311
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
312
+ >
313
+ const schema = from.pipe(
314
+ S.decodeTo(
315
+ to,
316
+ SchemaTransformation.transform({
317
+ decode: (
318
+ arr
319
+ ) => new Map(arr) as ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>,
320
+ encode: (
321
+ map
322
+ ) => [...map.entries()] as any // fu
323
+ })
324
+ )
325
+ )
326
+ return schema
327
+ }
328
+
329
+ /** Like the default Schema `ReadonlySet` but from Array, with default helpers. */
101
330
  export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
102
331
  pipe(
103
- S.ReadonlySet(value),
332
+ ReadonlySetFromArray(value),
104
333
  (s) =>
105
- Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
334
+ Object.assign(s, {
335
+ /**
336
+ * Construction-only default `new Set()`. Applied only when the field
337
+ * is omitted from `.make(...)` input. NOT applied during decode —
338
+ * cannot be used to JIT-migrate database fields. See file-level
339
+ * note.
340
+ */
341
+ withConstructorDefault: s.pipe(
342
+ S.withConstructorDefault(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))
343
+ ),
344
+ /**
345
+ * Decode-time default `new Set()`. **Discouraged for persisted
346
+ * data:** a missing field may be data corruption, not an old-shape
347
+ * document; silently substituting an empty set hides the problem.
348
+ * Prefer an explicit, preferably versioned migration over a
349
+ * decode-time fallback. See file-level note.
350
+ */
351
+ withDecodingDefaultType: s.pipe(
352
+ S.withDecodingDefaultType(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))
353
+ )
354
+ })
106
355
  )
107
356
 
108
- /**
109
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
110
- */
357
+ /** Like the default Schema `ReadonlyMap` but from Array, with default helpers. */
111
358
  export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
112
359
  readonly key: KeySchema
113
360
  readonly value: ValueSchema
114
361
  }) =>
115
362
  pipe(
116
- S.ReadonlyMap(pair.key, pair.value),
117
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
363
+ ReadonlyMapFromArray(pair),
364
+ (s) =>
365
+ Object.assign(s, {
366
+ /**
367
+ * Construction-only default `new Map()`. Applied only when the field
368
+ * is omitted from `.make(...)` input. NOT applied during decode —
369
+ * cannot be used to JIT-migrate database fields. See file-level
370
+ * note.
371
+ */
372
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Map()))),
373
+ /**
374
+ * Decode-time default `new Map()`. **Discouraged for persisted
375
+ * data:** a missing field may be data corruption, not an old-shape
376
+ * document; silently substituting an empty map hides the problem.
377
+ * Prefer an explicit, preferably versioned migration over a
378
+ * decode-time fallback. See file-level note.
379
+ */
380
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new Map())))
381
+ })
118
382
  )
119
383
 
120
- /**
121
- * Like the default Schema `NullOr` but with `withDefault` => null
122
- */
384
+ /** Like the default Schema `NullOr` but with default helpers. */
123
385
  export const NullOr = <Schema extends S.Top>(self: Schema) =>
124
386
  pipe(
125
387
  S.NullOr(self),
126
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
388
+ (s) =>
389
+ Object.assign(s, {
390
+ /**
391
+ * Construction-only default `null`. Applied only when the field is
392
+ * omitted from `.make(...)` input. NOT applied during decode —
393
+ * cannot be used to JIT-migrate database fields. See file-level
394
+ * note.
395
+ */
396
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(null))),
397
+ /**
398
+ * Decode-time default `null`. **Discouraged for persisted data:** a
399
+ * missing field may be data corruption, not an old-shape document;
400
+ * silently substituting `null` hides the problem. Prefer an
401
+ * explicit, preferably versioned migration over a decode-time
402
+ * fallback. See file-level note.
403
+ */
404
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(null)))
405
+ })
127
406
  )
128
407
 
129
- export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
130
- schema.pipe(withDefaultConstructor(() => new global.Date()))
408
+ /**
409
+ * Attach a `withConstructorDefault` of `new Date()` to any schema.
410
+ *
411
+ * **Construction-only.** Applied only when the field is omitted from
412
+ * `.make(...)` input. NOT applied during decode — cannot be used to
413
+ * JIT-migrate database fields. See file-level note.
414
+ */
415
+ export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
416
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
131
417
 
132
- export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
418
+ /**
419
+ * Attach a `withConstructorDefault` of `false` to any schema.
420
+ *
421
+ * **Construction-only.** Applied only when the field is omitted from
422
+ * `.make(...)` input. NOT applied during decode — cannot be used to
423
+ * JIT-migrate database fields. See file-level note.
424
+ */
425
+ export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
426
+ schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
133
427
 
134
- export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
428
+ /**
429
+ * Attach a `withConstructorDefault` of `null` to any schema.
430
+ *
431
+ * **Construction-only.** Applied only when the field is omitted from
432
+ * `.make(...)` input. NOT applied during decode — cannot be used to
433
+ * JIT-migrate database fields. See file-level note.
434
+ */
435
+ export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
436
+ schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
135
437
 
136
- export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
438
+ /**
439
+ * Attach a `withConstructorDefault` of `[]` to any schema.
440
+ *
441
+ * **Construction-only.** Applied only when the field is omitted from
442
+ * `.make(...)` input. NOT applied during decode — cannot be used to
443
+ * JIT-migrate database fields. See file-level note.
444
+ */
445
+ export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
446
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
137
447
 
138
- export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
448
+ /**
449
+ * Attach a `withConstructorDefault` of `new Map()` to any schema.
450
+ *
451
+ * **Construction-only.** Applied only when the field is omitted from
452
+ * `.make(...)` input. NOT applied during decode — cannot be used to
453
+ * JIT-migrate database fields. See file-level note.
454
+ */
455
+ export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
456
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
139
457
 
140
- export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
458
+ /**
459
+ * Attach a `withConstructorDefault` of `new Set()` to any schema.
460
+ *
461
+ * **Construction-only.** Applied only when the field is omitted from
462
+ * `.make(...)` input. NOT applied during decode — cannot be used to
463
+ * JIT-migrate database fields. See file-level note.
464
+ */
465
+ export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
466
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
141
467
 
142
468
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
143
469
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -166,9 +492,25 @@ export type WithDefaults<Self extends S.Top> = (
166
492
  // export type UnionToIntersection3<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I
167
493
  // : never
168
494
 
495
+ /** Union of `DateValid` and `Date`, with default helpers. */
169
496
  export const inputDate = extendM(
170
- S.Union([S.DateValid, S.Date]),
171
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
497
+ S.Union([S.DateValid, Date]),
498
+ (s) => ({
499
+ /**
500
+ * Construction-only default `new Date()`. Applied only when the field is
501
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
502
+ * used to JIT-migrate database fields. See file-level note.
503
+ */
504
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))),
505
+ /**
506
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
507
+ * missing field may be data corruption, not an old-shape document;
508
+ * silently substituting `new Date()` hides the problem. Prefer an
509
+ * explicit, preferably versioned migration over a decode-time fallback.
510
+ * See file-level note.
511
+ */
512
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new globalThis.Date())))
513
+ })
172
514
  )
173
515
 
174
516
  export interface UnionBrand {}
@@ -218,7 +560,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
218
560
  { message: "One way schema transformation, encoding is not allowed" }
219
561
  )
220
562
  )
221
- }) as any
563
+ })
222
564
  )
223
565
  )
224
566
 
@@ -235,7 +577,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
235
577
  S.decodeTo(
236
578
  to,
237
579
  SchemaTransformation.transformOrFail({
238
- decode: decode as any,
580
+ decode,
239
581
  encode: (i: any) =>
240
582
  Effect.fail(
241
583
  new SchemaIssue.Forbidden(
@@ -243,34 +585,36 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
243
585
  { message: "One way schema transformation, encoding is not allowed" }
244
586
  )
245
587
  )
246
- }) as any
588
+ })
247
589
  )
248
590
  )
249
591
 
250
- export const provide = <Self extends S.Top, R>(
251
- self: Self,
252
- context: ServiceMap.ServiceMap<R>
253
- ): ProvidedCodec<Self, R> => {
592
+ export const provide: {
593
+ <R>(context: Context.Context<R>): <Self extends S.Top>(self: Self) => ProvidedCodec<Self, R>
594
+ <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R>
595
+ } = Function.dual(2, <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R> => {
254
596
  const prov = Effect.provide(context)
255
597
  return self.pipe(
256
598
  S.middlewareDecoding((effect) => prov(effect)),
257
599
  S.middlewareEncoding((effect) => prov(effect))
258
- ) as ProvidedCodec<Self, R>
259
- }
260
- export const contextFromServices = <
600
+ )
601
+ })
602
+ export const contextFromServices = Effect.fnUntraced(function*<
261
603
  Self extends S.Top,
262
- Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
604
+ Tags extends ReadonlyArray<Context.Key<any, any>>
605
+ >(self: Self, ...services: Tags) {
606
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
607
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
608
+ )
609
+ return provide(self, context)
610
+ }) as <
611
+ Self extends S.Top,
612
+ Tags extends ReadonlyArray<Context.Key<any, any>>
263
613
  >(
264
614
  self: Self,
265
615
  ...services: Tags
266
- ): Effect.Effect<
267
- ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
616
+ ) => Effect.Effect<
617
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
268
618
  never,
269
- ServiceMap.Service.Identifier<Tags[number]>
270
- > =>
271
- Effect.gen(function*() {
272
- const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
273
- yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
274
- )
275
- return provide(self, context)
276
- })
619
+ Context.Service.Identifier<Tags[number]>
620
+ >