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
@@ -12,11 +12,16 @@ export type Email = string & EmailBrand
12
12
  export const Email = S
13
13
  .String
14
14
  .pipe(
15
+ S.annotate({
16
+ title: "Email",
17
+ description: "an email according to RFC 5322",
18
+ format: "email"
19
+ }),
20
+ S.check(S.isMinLength(3), /* a@b */ S.isMaxLength(998)),
15
21
  S.refine(isValidEmail as Refinement<string, Email>, {
16
22
  identifier: "Email",
17
- title: "Email",
18
23
  description: "an email according to RFC 5322",
19
- jsonSchema: { format: "email", minLength: 3, /* a@b */ maxLength: 998 }
24
+ jsonSchema: { format: "email", minLength: 3, maxLength: 998 }
20
25
  }),
21
26
  S.annotate({
22
27
  toArbitrary: () => (fc) => fc.emailAddress().map((_) => _ as Email)
package/src/Schema/ext.ts CHANGED
@@ -1,8 +1,40 @@
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 { Config, Effect, Function, Option, pipe, type SchemaAST, SchemaIssue, SchemaTransformation } from "effect"
4
34
  import * as S from "effect/Schema"
35
+ import { isDateValid } from "effect/Schema"
5
36
  import { type NonEmptyReadonlyArray } from "../Array.js"
37
+ import * as Context from "../Context.js"
6
38
  import { extendM, typedKeysOf } from "../utils.js"
7
39
  import { type AST } from "./schema.js"
8
40
 
@@ -13,131 +45,418 @@ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
13
45
  Exclude<Self["EncodingServices"], R>
14
46
  >
15
47
 
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)
48
+ const concurrencySetting = Effect.runSync(
49
+ Config
50
+ .literal("unbounded", "SCHEMA_CONCURRENCY")
51
+ .pipe(Config.orElse(() => Config.number("SCHEMA_CONCURRENCY")), Config.option)
52
+ .asEffect()
53
+ )
54
+
55
+ export const DefaultParseOptions: SchemaAST.ParseOptions = {
56
+ concurrency: Option.getOrElse(concurrencySetting, () => "unbounded" as const)
28
57
  }
29
58
 
59
+ /**
60
+ * Parse-options annotation used on schema constructors for decode paths where callers
61
+ * cannot currently pass parse options (notably some RPC / HttpApi integration paths).
62
+ *
63
+ * Keep this annotation in place so those framework-managed decodes still run with
64
+ * unbounded concurrency by default.
65
+ */
66
+ export const concurrencyUnbounded = { parseOptions: DefaultParseOptions } as const
67
+
68
+ type DecodeLike = (schema: any) => (input: any, options?: SchemaAST.ParseOptions) => any
69
+
70
+ export const withDefaultParseOptions = <Decode extends DecodeLike>(
71
+ decode: Decode,
72
+ defaultParseOptions: SchemaAST.ParseOptions = DefaultParseOptions
73
+ ): Decode =>
74
+ ((schema: any) => {
75
+ const run = decode(schema)
76
+ return (input: any, options?: SchemaAST.ParseOptions) => run(input, { ...defaultParseOptions, ...options })
77
+ }) as Decode
78
+
30
79
  // 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
- )
80
+
81
+ const DateString = S.String.annotate({
82
+ identifier: "Date",
83
+ description: "a string in ISO 8601 format that will be decoded as a Date",
84
+ format: "date-time"
85
+ })
37
86
 
38
87
  /**
39
- * Like the default Schema `Date` but from String with `withDefault` => now
88
+ * Schema type for {@link DateFromString}.
89
+ *
90
+ * @category Schemas
91
+ * @since 4.0.0
40
92
  */
41
- export const Date = Object.assign(DateFromString, {
42
- withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
43
- })
93
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
44
94
 
45
95
  /**
46
- * Like the default Schema `Boolean` but with `withDefault` => false
96
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
97
+ *
98
+ * Decoding:
99
+ * - A `string` is decoded as a `Date`.
100
+ *
101
+ * Encoding:
102
+ * - A `Date` is encoded as a `string`.
103
+ *
104
+ * @since 4.0.0
47
105
  */
106
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
107
+
108
+ /** Like the default Schema `Date` but from String, with default helpers. */
109
+ export const Date = Object.assign(DateFromString, {
110
+ /**
111
+ * Construction-only default `new Date()`. Applied only when the field is
112
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
113
+ * used to JIT-migrate database fields. See file-level note.
114
+ */
115
+ withConstructorDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
116
+ /**
117
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
118
+ * missing field may be data corruption, not an old-shape document; silently
119
+ * substituting `new Date()` hides the problem. Prefer an explicit,
120
+ * preferably versioned migration over a decode-time fallback. See
121
+ * file-level note.
122
+ */
123
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
124
+ })
125
+
126
+ /** Like the default Schema `DateValid` but from String, with default helpers. */
127
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
128
+ /**
129
+ * Construction-only default `new Date()`. Applied only when the field is
130
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
131
+ * used to JIT-migrate database fields. See file-level note.
132
+ */
133
+ withConstructorDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
134
+ /**
135
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
136
+ * missing field may be data corruption, not an old-shape document; silently
137
+ * substituting `new Date()` hides the problem. Prefer an explicit,
138
+ * preferably versioned migration over a decode-time fallback. See
139
+ * file-level note.
140
+ */
141
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
142
+ })
143
+
144
+ /** Like the default Schema `Boolean` but with default helpers. */
48
145
  export const Boolean = Object.assign(S.Boolean, {
49
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
146
+ /**
147
+ * Construction-only default `false`. Applied only when the field is
148
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
149
+ * used to JIT-migrate database fields. See file-level note.
150
+ */
151
+ withConstructorDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false))),
152
+ /**
153
+ * Decode-time default `false`. **Discouraged for persisted data:** a
154
+ * missing field may be data corruption, not an old-shape document; silently
155
+ * substituting `false` hides the problem. Prefer an explicit, preferably
156
+ * versioned migration over a decode-time fallback. See file-level note.
157
+ */
158
+ withDecodingDefaultType: S.Boolean.pipe(S.withDecodingDefaultType(Effect.succeed(false)))
50
159
  })
51
160
 
52
161
  /**
53
- * Like the default Schema `Number` but with `withDefault` => 0
162
+ * You probably want to use `Finite` instead of this. Like the default Schema
163
+ * `Number` but with default helpers.
54
164
  */
55
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
165
+ export const Number = Object.assign(S.Number, {
166
+ /**
167
+ * Construction-only default `0`. Applied only when the field is omitted
168
+ * from `.make(...)` input. NOT applied during decode — cannot be used to
169
+ * JIT-migrate database fields. See file-level note.
170
+ */
171
+ withConstructorDefault: S.Number.pipe(S.withConstructorDefault(Effect.succeed(0))),
172
+ /**
173
+ * Decode-time default `0`. **Discouraged for persisted data:** a missing
174
+ * field may be data corruption, not an old-shape document; silently
175
+ * substituting `0` hides the problem. Prefer an explicit, preferably
176
+ * versioned migration over a decode-time fallback. See file-level note.
177
+ */
178
+ withDecodingDefaultType: S.Number.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
179
+ })
56
180
 
57
- /**
58
- * Like the default Schema `Literal` but with `withDefault` => literals[0]
59
- */
60
- export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(...literals: Literals) =>
181
+ /** Like the default Schema `Finite` but with default helpers. */
182
+ export const Finite = Object.assign(S.Finite, {
183
+ /**
184
+ * Construction-only default `0`. Applied only when the field is omitted
185
+ * from `.make(...)` input. NOT applied during decode — cannot be used to
186
+ * JIT-migrate database fields. See file-level note.
187
+ */
188
+ withConstructorDefault: S.Finite.pipe(S.withConstructorDefault(Effect.succeed(0))),
189
+ /**
190
+ * Decode-time default `0`. **Discouraged for persisted data:** a missing
191
+ * field may be data corruption, not an old-shape document; silently
192
+ * substituting `0` hides the problem. Prefer an explicit, preferably
193
+ * versioned migration over a decode-time fallback. See file-level note.
194
+ */
195
+ withDecodingDefaultType: S.Finite.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
196
+ })
197
+
198
+ /** Like the default Schema `Literals` but with default helpers. Default value is `literals[0]`. */
199
+ export const Literals = <const Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(literals: Literals) =>
61
200
  pipe(
62
201
  S.Literals(literals),
63
202
  (s) =>
64
203
  Object.assign(s, {
204
+ /** Override the default literal value used by `withConstructorDefault` / `withDecodingDefaultType`. */
65
205
  changeDefault: <A extends Literals[number]>(a: A) => {
66
206
  return Object.assign(S.Literals(literals), {
67
207
  Default: a,
68
- withDefault: s.pipe(withDefaultConstructor(() => a))
208
+ /**
209
+ * Construction-only default. Applied only when the field is
210
+ * omitted from `.make(...)` input. NOT applied during decode —
211
+ * cannot be used to JIT-migrate database fields. See file-level
212
+ * note.
213
+ */
214
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a))),
215
+ /**
216
+ * Decode-time default. **Discouraged for persisted data:** a
217
+ * missing field may be data corruption, not an old-shape
218
+ * document; silently substituting hides the problem. Prefer an
219
+ * explicit, preferably versioned migration over a decode-time
220
+ * fallback. See file-level note.
221
+ */
222
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(a)))
69
223
  }) // todo: copy annotations from original?
70
224
  },
71
- Default: literals[0] as typeof literals[0],
72
- withDefault: s.pipe(withDefaultConstructor(() => literals[0]))
225
+ // 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
226
+ Default: literals[0] as Literals[0],
227
+ /**
228
+ * Construction-only default `literals[0]`. Applied only when the
229
+ * field is omitted from `.make(...)` input. NOT applied during
230
+ * decode — cannot be used to JIT-migrate database fields. See
231
+ * file-level note.
232
+ */
233
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(literals[0]))),
234
+ /**
235
+ * Decode-time default `literals[0]`. **Discouraged for persisted
236
+ * data:** a missing field may be data corruption, not an old-shape
237
+ * document; silently substituting hides the problem. Prefer an
238
+ * explicit, preferably versioned migration over a decode-time
239
+ * fallback. See file-level note.
240
+ */
241
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(literals[0])))
73
242
  })
74
243
  )
75
244
 
76
- /**
77
- * Like the default Schema `Array` but with `withDefault` => []
78
- */
245
+ /** Like the default Schema `Array` but with default helpers. */
79
246
  export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
80
247
  return pipe(
81
- S.Array(value),
82
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
248
+ S.Array(value).annotate(concurrencyUnbounded),
249
+ (s) =>
250
+ Object.assign(s, {
251
+ /**
252
+ * Construction-only default `[]`. Applied only when the field is
253
+ * omitted from `.make(...)` input. NOT applied during decode —
254
+ * cannot be used to JIT-migrate database fields. See file-level
255
+ * note.
256
+ */
257
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => []))),
258
+ /**
259
+ * Decode-time default `[]`. **Discouraged for persisted data:** a
260
+ * missing field may be data corruption, not an old-shape document;
261
+ * silently substituting `[]` hides the problem. Prefer an explicit,
262
+ * preferably versioned migration over a decode-time fallback. See
263
+ * file-level note.
264
+ */
265
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => [])))
266
+ })
83
267
  )
84
268
  }
85
269
 
86
270
  /**
87
- * Like the default Schema `Map` but with `withDefault` => []
271
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
88
272
  */
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())) })
273
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
274
+ const from = S
275
+ .Array(value)
276
+ .annotate({ ...concurrencyUnbounded, expected: "an array of unique items that will be decoded as a ReadonlySet" })
277
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
278
+ const schema = from.pipe(
279
+ S.decodeTo(
280
+ to,
281
+ SchemaTransformation.transform({
282
+ decode: (arr) => new Set(arr) as ReadonlySet<S.Schema.Type<ValueSchema>>,
283
+ encode: (set) => [...set]
284
+ })
285
+ )
93
286
  )
287
+ return schema
94
288
  }
95
289
 
96
- export { Map_ as Map }
97
-
98
290
  /**
99
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
291
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
100
292
  */
293
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
294
+ readonly key: KeySchema
295
+ readonly value: ValueSchema
296
+ }) => {
297
+ const from = S
298
+ .Array(S.Tuple([pair.key, pair.value]))
299
+ .annotate({
300
+ ...concurrencyUnbounded,
301
+ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap"
302
+ })
303
+ const to = S.instanceOf(Map) as S.instanceOf<
304
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
305
+ >
306
+ const schema = from.pipe(
307
+ S.decodeTo(
308
+ to,
309
+ SchemaTransformation.transform({
310
+ decode: (
311
+ arr
312
+ ) => new Map(arr) as ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>,
313
+ encode: (
314
+ map
315
+ ) => [...map.entries()] as any // fu
316
+ })
317
+ )
318
+ )
319
+ return schema
320
+ }
321
+
322
+ /** Like the default Schema `ReadonlySet` but from Array, with default helpers. */
101
323
  export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
102
324
  pipe(
103
- S.ReadonlySet(value),
325
+ ReadonlySetFromArray(value),
104
326
  (s) =>
105
- Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
327
+ Object.assign(s, {
328
+ /**
329
+ * Construction-only default `new Set()`. Applied only when the field
330
+ * is omitted from `.make(...)` input. NOT applied during decode —
331
+ * cannot be used to JIT-migrate database fields. See file-level
332
+ * note.
333
+ */
334
+ withConstructorDefault: s.pipe(
335
+ S.withConstructorDefault(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))
336
+ ),
337
+ /**
338
+ * Decode-time default `new Set()`. **Discouraged for persisted
339
+ * data:** a missing field may be data corruption, not an old-shape
340
+ * document; silently substituting an empty set hides the problem.
341
+ * Prefer an explicit, preferably versioned migration over a
342
+ * decode-time fallback. See file-level note.
343
+ */
344
+ withDecodingDefaultType: s.pipe(
345
+ S.withDecodingDefaultType(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))
346
+ )
347
+ })
106
348
  )
107
349
 
108
- /**
109
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
110
- */
350
+ /** Like the default Schema `ReadonlyMap` but from Array, with default helpers. */
111
351
  export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
112
352
  readonly key: KeySchema
113
353
  readonly value: ValueSchema
114
354
  }) =>
115
355
  pipe(
116
- S.ReadonlyMap(pair.key, pair.value),
117
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
356
+ ReadonlyMapFromArray(pair),
357
+ (s) =>
358
+ Object.assign(s, {
359
+ /**
360
+ * Construction-only default `new Map()`. Applied only when the field
361
+ * is omitted from `.make(...)` input. NOT applied during decode —
362
+ * cannot be used to JIT-migrate database fields. See file-level
363
+ * note.
364
+ */
365
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Map()))),
366
+ /**
367
+ * Decode-time default `new Map()`. **Discouraged for persisted
368
+ * data:** a missing field may be data corruption, not an old-shape
369
+ * document; silently substituting an empty map hides the problem.
370
+ * Prefer an explicit, preferably versioned migration over a
371
+ * decode-time fallback. See file-level note.
372
+ */
373
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new Map())))
374
+ })
118
375
  )
119
376
 
120
- /**
121
- * Like the default Schema `NullOr` but with `withDefault` => null
122
- */
377
+ /** Like the default Schema `NullOr` but with default helpers. */
123
378
  export const NullOr = <Schema extends S.Top>(self: Schema) =>
124
379
  pipe(
125
380
  S.NullOr(self),
126
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
381
+ (s) =>
382
+ Object.assign(s, {
383
+ /**
384
+ * Construction-only default `null`. Applied only when the field is
385
+ * omitted from `.make(...)` input. NOT applied during decode —
386
+ * cannot be used to JIT-migrate database fields. See file-level
387
+ * note.
388
+ */
389
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(null))),
390
+ /**
391
+ * Decode-time default `null`. **Discouraged for persisted data:** a
392
+ * missing field may be data corruption, not an old-shape document;
393
+ * silently substituting `null` hides the problem. Prefer an
394
+ * explicit, preferably versioned migration over a decode-time
395
+ * fallback. See file-level note.
396
+ */
397
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(null)))
398
+ })
127
399
  )
128
400
 
129
- export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
130
- schema.pipe(withDefaultConstructor(() => new global.Date()))
401
+ /**
402
+ * Attach a `withConstructorDefault` of `new Date()` to any schema.
403
+ *
404
+ * **Construction-only.** Applied only when the field is omitted from
405
+ * `.make(...)` input. NOT applied during decode — cannot be used to
406
+ * JIT-migrate database fields. See file-level note.
407
+ */
408
+ export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
409
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
131
410
 
132
- export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
411
+ /**
412
+ * Attach a `withConstructorDefault` of `false` to any schema.
413
+ *
414
+ * **Construction-only.** Applied only when the field is omitted from
415
+ * `.make(...)` input. NOT applied during decode — cannot be used to
416
+ * JIT-migrate database fields. See file-level note.
417
+ */
418
+ export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
419
+ schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
133
420
 
134
- export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
421
+ /**
422
+ * Attach a `withConstructorDefault` of `null` to any schema.
423
+ *
424
+ * **Construction-only.** Applied only when the field is omitted from
425
+ * `.make(...)` input. NOT applied during decode — cannot be used to
426
+ * JIT-migrate database fields. See file-level note.
427
+ */
428
+ export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
429
+ schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
135
430
 
136
- export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
431
+ /**
432
+ * Attach a `withConstructorDefault` of `[]` to any schema.
433
+ *
434
+ * **Construction-only.** Applied only when the field is omitted from
435
+ * `.make(...)` input. NOT applied during decode — cannot be used to
436
+ * JIT-migrate database fields. See file-level note.
437
+ */
438
+ export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
439
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
137
440
 
138
- export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
441
+ /**
442
+ * Attach a `withConstructorDefault` of `new Map()` to any schema.
443
+ *
444
+ * **Construction-only.** Applied only when the field is omitted from
445
+ * `.make(...)` input. NOT applied during decode — cannot be used to
446
+ * JIT-migrate database fields. See file-level note.
447
+ */
448
+ export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
449
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
139
450
 
140
- export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
451
+ /**
452
+ * Attach a `withConstructorDefault` of `new Set()` to any schema.
453
+ *
454
+ * **Construction-only.** Applied only when the field is omitted from
455
+ * `.make(...)` input. NOT applied during decode — cannot be used to
456
+ * JIT-migrate database fields. See file-level note.
457
+ */
458
+ export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
459
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
141
460
 
142
461
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
143
462
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -166,9 +485,25 @@ export type WithDefaults<Self extends S.Top> = (
166
485
  // export type UnionToIntersection3<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I
167
486
  // : never
168
487
 
488
+ /** Union of `DateValid` and `Date`, with default helpers. */
169
489
  export const inputDate = extendM(
170
- S.Union([S.DateValid, S.Date]),
171
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
490
+ S.Union([S.DateValid, Date]),
491
+ (s) => ({
492
+ /**
493
+ * Construction-only default `new Date()`. Applied only when the field is
494
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
495
+ * used to JIT-migrate database fields. See file-level note.
496
+ */
497
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))),
498
+ /**
499
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
500
+ * missing field may be data corruption, not an old-shape document;
501
+ * silently substituting `new Date()` hides the problem. Prefer an
502
+ * explicit, preferably versioned migration over a decode-time fallback.
503
+ * See file-level note.
504
+ */
505
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new globalThis.Date())))
506
+ })
172
507
  )
173
508
 
174
509
  export interface UnionBrand {}
@@ -218,7 +553,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
218
553
  { message: "One way schema transformation, encoding is not allowed" }
219
554
  )
220
555
  )
221
- }) as any
556
+ })
222
557
  )
223
558
  )
224
559
 
@@ -235,7 +570,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
235
570
  S.decodeTo(
236
571
  to,
237
572
  SchemaTransformation.transformOrFail({
238
- decode: decode as any,
573
+ decode,
239
574
  encode: (i: any) =>
240
575
  Effect.fail(
241
576
  new SchemaIssue.Forbidden(
@@ -243,34 +578,36 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
243
578
  { message: "One way schema transformation, encoding is not allowed" }
244
579
  )
245
580
  )
246
- }) as any
581
+ })
247
582
  )
248
583
  )
249
584
 
250
- export const provide = <Self extends S.Top, R>(
251
- self: Self,
252
- context: ServiceMap.ServiceMap<R>
253
- ): ProvidedCodec<Self, R> => {
585
+ export const provide: {
586
+ <R>(context: Context.Context<R>): <Self extends S.Top>(self: Self) => ProvidedCodec<Self, R>
587
+ <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R>
588
+ } = Function.dual(2, <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R> => {
254
589
  const prov = Effect.provide(context)
255
590
  return self.pipe(
256
591
  S.middlewareDecoding((effect) => prov(effect)),
257
592
  S.middlewareEncoding((effect) => prov(effect))
258
- ) as ProvidedCodec<Self, R>
259
- }
260
- export const contextFromServices = <
593
+ )
594
+ })
595
+ export const contextFromServices = Effect.fnUntraced(function*<
261
596
  Self extends S.Top,
262
- Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
597
+ Tags extends ReadonlyArray<Context.Key<any, any>>
598
+ >(self: Self, ...services: Tags) {
599
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
600
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
601
+ )
602
+ return provide(self, context)
603
+ }) as <
604
+ Self extends S.Top,
605
+ Tags extends ReadonlyArray<Context.Key<any, any>>
263
606
  >(
264
607
  self: Self,
265
608
  ...services: Tags
266
- ): Effect.Effect<
267
- ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
609
+ ) => Effect.Effect<
610
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
268
611
  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
- })
612
+ Context.Service.Identifier<Tags[number]>
613
+ >