effect-app 4.0.0-beta.33 → 4.0.0-beta.331

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 (369) hide show
  1. package/CHANGELOG.md +1628 -0
  2. package/dist/Array.d.ts +2 -1
  3. package/dist/Array.d.ts.map +1 -1
  4. package/dist/Array.js +4 -4
  5. package/dist/Chunk.d.ts.map +1 -1
  6. package/dist/Config/SecretURL.d.ts +3 -1
  7. package/dist/Config/SecretURL.d.ts.map +1 -1
  8. package/dist/Config/SecretURL.js +3 -6
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config/internal/configSecretURL.js +2 -2
  11. package/dist/Config.d.ts +2 -0
  12. package/dist/Config.d.ts.map +1 -0
  13. package/dist/Config.js +8 -0
  14. package/dist/ConfigProvider.d.ts +2 -0
  15. package/dist/ConfigProvider.d.ts.map +1 -0
  16. package/dist/ConfigProvider.js +6 -0
  17. package/dist/{ServiceMap.d.ts → Context.d.ts} +18 -20
  18. package/dist/Context.d.ts.map +1 -0
  19. package/dist/Context.js +67 -0
  20. package/dist/DataDependencies.d.ts +136 -0
  21. package/dist/DataDependencies.d.ts.map +1 -0
  22. package/dist/DataDependencies.js +112 -0
  23. package/dist/Effect.d.ts +10 -9
  24. package/dist/Effect.d.ts.map +1 -1
  25. package/dist/Effect.js +6 -9
  26. package/dist/Emailer.d.ts +51 -0
  27. package/dist/Emailer.d.ts.map +1 -0
  28. package/dist/Emailer.js +7 -0
  29. package/dist/Function.d.ts.map +1 -1
  30. package/dist/Layer.d.ts +10 -6
  31. package/dist/Layer.d.ts.map +1 -1
  32. package/dist/Layer.js +3 -2
  33. package/dist/Model/Repository/Registry.d.ts +22 -0
  34. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  35. package/dist/Model/Repository/Registry.js +18 -0
  36. package/dist/Model/Repository/dependency.d.ts +6 -0
  37. package/dist/Model/Repository/dependency.d.ts.map +1 -0
  38. package/dist/Model/Repository/dependency.js +29 -0
  39. package/dist/Model/Repository/ext.d.ts +60 -0
  40. package/dist/Model/Repository/ext.d.ts.map +1 -0
  41. package/dist/Model/Repository/ext.js +124 -0
  42. package/dist/Model/Repository/internal/internal.d.ts +68 -0
  43. package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
  44. package/dist/Model/Repository/internal/internal.js +611 -0
  45. package/dist/Model/Repository/legacy.d.ts +21 -0
  46. package/dist/Model/Repository/legacy.d.ts.map +1 -0
  47. package/dist/Model/Repository/legacy.js +2 -0
  48. package/dist/Model/Repository/makeRepo.d.ts +71 -0
  49. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  50. package/dist/Model/Repository/makeRepo.js +30 -0
  51. package/dist/Model/Repository/service.d.ts +121 -0
  52. package/dist/Model/Repository/service.d.ts.map +1 -0
  53. package/dist/Model/Repository/service.js +2 -0
  54. package/dist/Model/Repository/validation.d.ts +72 -0
  55. package/dist/Model/Repository/validation.d.ts.map +1 -0
  56. package/dist/Model/Repository/validation.js +39 -0
  57. package/dist/Model/Repository.d.ts +8 -0
  58. package/dist/Model/Repository.d.ts.map +1 -0
  59. package/dist/Model/Repository.js +8 -0
  60. package/dist/Model/dsl.d.ts +33 -0
  61. package/dist/Model/dsl.d.ts.map +1 -0
  62. package/dist/Model/dsl.js +43 -0
  63. package/dist/Model/filter/filterApi.d.ts +30 -0
  64. package/dist/Model/filter/filterApi.d.ts.map +1 -0
  65. package/dist/Model/filter/filterApi.js +2 -0
  66. package/dist/Model/filter/types/errors.d.ts +29 -0
  67. package/dist/Model/filter/types/errors.d.ts.map +1 -0
  68. package/dist/Model/filter/types/errors.js +2 -0
  69. package/dist/Model/filter/types/fields.d.ts +15 -0
  70. package/dist/Model/filter/types/fields.d.ts.map +1 -0
  71. package/dist/Model/filter/types/fields.js +2 -0
  72. package/dist/Model/filter/types/path/common.d.ts +316 -0
  73. package/dist/Model/filter/types/path/common.d.ts.map +1 -0
  74. package/dist/Model/filter/types/path/common.js +2 -0
  75. package/dist/Model/filter/types/path/eager.d.ts +94 -0
  76. package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
  77. package/dist/Model/filter/types/path/eager.js +36 -0
  78. package/dist/Model/filter/types/path/index.d.ts +4 -0
  79. package/dist/Model/filter/types/path/index.d.ts.map +1 -0
  80. package/dist/Model/filter/types/path/index.js +3 -0
  81. package/dist/Model/filter/types/utils.d.ts +79 -0
  82. package/dist/Model/filter/types/utils.d.ts.map +1 -0
  83. package/dist/Model/filter/types/utils.js +2 -0
  84. package/dist/Model/filter/types/validator.d.ts +30 -0
  85. package/dist/Model/filter/types/validator.d.ts.map +1 -0
  86. package/dist/Model/filter/types/validator.js +2 -0
  87. package/dist/Model/filter/types.d.ts +5 -0
  88. package/dist/Model/filter/types.d.ts.map +1 -0
  89. package/dist/Model/filter/types.js +7 -0
  90. package/dist/Model/query/dsl.d.ts +630 -0
  91. package/dist/Model/query/dsl.d.ts.map +1 -0
  92. package/dist/Model/query/dsl.js +376 -0
  93. package/dist/Model/query/new-kid-interpreter.d.ts +132 -0
  94. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
  95. package/dist/Model/query/new-kid-interpreter.js +370 -0
  96. package/dist/Model/query.d.ts +15 -0
  97. package/dist/Model/query.d.ts.map +1 -0
  98. package/dist/Model/query.js +3 -0
  99. package/dist/Model.d.ts +6 -0
  100. package/dist/Model.d.ts.map +1 -0
  101. package/dist/Model.js +6 -0
  102. package/dist/NonEmptySet.d.ts +3 -1
  103. package/dist/NonEmptySet.d.ts.map +1 -1
  104. package/dist/NonEmptySet.js +2 -2
  105. package/dist/Option.d.ts +1 -0
  106. package/dist/Option.d.ts.map +1 -1
  107. package/dist/Option.js +3 -1
  108. package/dist/Pure.d.ts +7 -5
  109. package/dist/Pure.d.ts.map +1 -1
  110. package/dist/Pure.js +7 -4
  111. package/dist/QueueMaker.d.ts +11 -0
  112. package/dist/QueueMaker.d.ts.map +1 -0
  113. package/dist/QueueMaker.js +11 -0
  114. package/dist/RequestContext.d.ts +87 -0
  115. package/dist/RequestContext.d.ts.map +1 -0
  116. package/dist/RequestContext.js +45 -0
  117. package/dist/RequestScopedDependencies.d.ts +21 -0
  118. package/dist/RequestScopedDependencies.d.ts.map +1 -0
  119. package/dist/RequestScopedDependencies.js +16 -0
  120. package/dist/Schema/Class.d.ts +200 -20
  121. package/dist/Schema/Class.d.ts.map +1 -1
  122. package/dist/Schema/Class.js +268 -17
  123. package/dist/Schema/FastCheck.d.ts.map +1 -1
  124. package/dist/Schema/SchemaParser.d.ts +5 -0
  125. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  126. package/dist/Schema/SchemaParser.js +6 -0
  127. package/dist/Schema/SpecialJsonSchema.d.ts +34 -0
  128. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  129. package/dist/Schema/SpecialJsonSchema.js +117 -0
  130. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  131. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  132. package/dist/Schema/SpecialOpenApi.js +123 -0
  133. package/dist/Schema/brand.d.ts +5 -5
  134. package/dist/Schema/brand.d.ts.map +1 -1
  135. package/dist/Schema/brand.js +3 -1
  136. package/dist/Schema/email.d.ts.map +1 -1
  137. package/dist/Schema/email.js +12 -6
  138. package/dist/Schema/ext.d.ts +335 -61
  139. package/dist/Schema/ext.d.ts.map +1 -1
  140. package/dist/Schema/ext.js +388 -63
  141. package/dist/Schema/moreStrings.d.ts +82 -36
  142. package/dist/Schema/moreStrings.d.ts.map +1 -1
  143. package/dist/Schema/moreStrings.js +90 -53
  144. package/dist/Schema/numbers.d.ts +33 -21
  145. package/dist/Schema/numbers.d.ts.map +1 -1
  146. package/dist/Schema/numbers.js +65 -12
  147. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  148. package/dist/Schema/phoneNumber.js +11 -7
  149. package/dist/Schema/schema.d.ts +1 -1
  150. package/dist/Schema/schema.d.ts.map +1 -1
  151. package/dist/Schema/schema.js +2 -2
  152. package/dist/Schema/strings.d.ts +18 -4
  153. package/dist/Schema/strings.d.ts.map +1 -1
  154. package/dist/Schema/strings.js +1 -5
  155. package/dist/Schema.d.ts +225 -7
  156. package/dist/Schema.d.ts.map +1 -1
  157. package/dist/Schema.js +193 -26
  158. package/dist/SchemaAST.d.ts +2 -0
  159. package/dist/SchemaAST.d.ts.map +1 -0
  160. package/dist/SchemaAST.js +2 -0
  161. package/dist/Set.d.ts +4 -1
  162. package/dist/Set.d.ts.map +1 -1
  163. package/dist/Set.js +3 -2
  164. package/dist/Store.d.ts +215 -0
  165. package/dist/Store.d.ts.map +1 -0
  166. package/dist/Store.js +113 -0
  167. package/dist/_ext/Array.d.ts +1 -1
  168. package/dist/_ext/Array.d.ts.map +1 -1
  169. package/dist/_ext/Array.js +4 -2
  170. package/dist/_ext/misc.d.ts +4 -1
  171. package/dist/_ext/misc.d.ts.map +1 -1
  172. package/dist/_ext/misc.js +4 -2
  173. package/dist/_ext/ord.ext.d.ts +2 -1
  174. package/dist/_ext/ord.ext.d.ts.map +1 -1
  175. package/dist/_ext/ord.ext.js +2 -2
  176. package/dist/builtin.d.ts +2 -14
  177. package/dist/builtin.d.ts.map +1 -1
  178. package/dist/builtin.js +1 -1
  179. package/dist/client/InvalidationKeys.d.ts +30 -0
  180. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  181. package/dist/client/InvalidationKeys.js +37 -0
  182. package/dist/client/apiClientFactory.d.ts +19 -31
  183. package/dist/client/apiClientFactory.d.ts.map +1 -1
  184. package/dist/client/apiClientFactory.js +157 -39
  185. package/dist/client/clientFor.d.ts +54 -18
  186. package/dist/client/clientFor.d.ts.map +1 -1
  187. package/dist/client/clientFor.js +9 -1
  188. package/dist/client/errors.d.ts +82 -27
  189. package/dist/client/errors.d.ts.map +1 -1
  190. package/dist/client/errors.js +75 -19
  191. package/dist/client/makeClient.d.ts +490 -34
  192. package/dist/client/makeClient.d.ts.map +1 -1
  193. package/dist/client/makeClient.js +62 -30
  194. package/dist/client.d.ts +2 -0
  195. package/dist/client.d.ts.map +1 -1
  196. package/dist/client.js +3 -1
  197. package/dist/faker.d.ts.map +1 -1
  198. package/dist/http/Request.d.ts +1 -1
  199. package/dist/http/Request.d.ts.map +1 -1
  200. package/dist/http/Request.js +2 -2
  201. package/dist/ids.d.ts +32 -14
  202. package/dist/ids.d.ts.map +1 -1
  203. package/dist/ids.js +38 -6
  204. package/dist/index.d.ts +6 -7
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js +8 -8
  207. package/dist/middleware.d.ts +13 -7
  208. package/dist/middleware.d.ts.map +1 -1
  209. package/dist/middleware.js +14 -8
  210. package/dist/rpc/Invalidation.d.ts +1124 -0
  211. package/dist/rpc/Invalidation.d.ts.map +1 -0
  212. package/dist/rpc/Invalidation.js +178 -0
  213. package/dist/rpc/MiddlewareMaker.d.ts +11 -7
  214. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  215. package/dist/rpc/MiddlewareMaker.js +59 -38
  216. package/dist/rpc/RpcContextMap.d.ts +3 -3
  217. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  218. package/dist/rpc/RpcContextMap.js +4 -4
  219. package/dist/rpc/RpcMiddleware.d.ts +14 -10
  220. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  221. package/dist/rpc/RpcMiddleware.js +1 -1
  222. package/dist/rpc.d.ts +1 -1
  223. package/dist/rpc.d.ts.map +1 -1
  224. package/dist/rpc.js +2 -2
  225. package/dist/runtime.d.ts +19 -0
  226. package/dist/runtime.d.ts.map +1 -0
  227. package/dist/runtime.js +40 -0
  228. package/dist/setupRequest.d.ts +20 -0
  229. package/dist/setupRequest.d.ts.map +1 -0
  230. package/dist/setupRequest.js +72 -0
  231. package/dist/toast.d.ts +51 -0
  232. package/dist/toast.d.ts.map +1 -0
  233. package/dist/toast.js +34 -0
  234. package/dist/transform.d.ts +1 -1
  235. package/dist/transform.d.ts.map +1 -1
  236. package/dist/transform.js +4 -5
  237. package/dist/utils/effectify.d.ts +1 -1
  238. package/dist/utils/effectify.d.ts.map +1 -1
  239. package/dist/utils/effectify.js +2 -2
  240. package/dist/utils/extend.d.ts.map +1 -1
  241. package/dist/utils/gen.d.ts +4 -4
  242. package/dist/utils/gen.d.ts.map +1 -1
  243. package/dist/utils/logLevel.d.ts +2 -2
  244. package/dist/utils/logLevel.d.ts.map +1 -1
  245. package/dist/utils/logger.d.ts +4 -3
  246. package/dist/utils/logger.d.ts.map +1 -1
  247. package/dist/utils/logger.js +4 -4
  248. package/dist/utils.d.ts +34 -39
  249. package/dist/utils.d.ts.map +1 -1
  250. package/dist/utils.js +33 -37
  251. package/dist/validation/validators.d.ts.map +1 -1
  252. package/dist/validation.d.ts.map +1 -1
  253. package/dist/withToast.d.ts +30 -0
  254. package/dist/withToast.d.ts.map +1 -0
  255. package/dist/withToast.js +64 -0
  256. package/package.json +27 -251
  257. package/src/Array.ts +5 -5
  258. package/src/Chunk.ts +3 -3
  259. package/src/Config/SecretURL.ts +6 -3
  260. package/src/Config/internal/configSecretURL.ts +2 -2
  261. package/src/Config.ts +7 -0
  262. package/src/ConfigProvider.ts +5 -0
  263. package/src/{ServiceMap.ts → Context.ts} +56 -63
  264. package/src/DataDependencies.ts +171 -0
  265. package/src/Effect.ts +15 -17
  266. package/src/Emailer.ts +51 -0
  267. package/src/Inputify.type.ts +1 -1
  268. package/src/Layer.ts +11 -7
  269. package/src/Model/Repository/Registry.ts +35 -0
  270. package/src/Model/Repository/dependency.ts +32 -0
  271. package/src/Model/Repository/ext.ts +374 -0
  272. package/src/Model/Repository/internal/internal.ts +988 -0
  273. package/src/Model/Repository/legacy.ts +29 -0
  274. package/src/Model/Repository/makeRepo.ts +168 -0
  275. package/src/Model/Repository/service.ts +676 -0
  276. package/src/Model/Repository/validation.ts +38 -0
  277. package/src/Model/Repository.ts +7 -0
  278. package/src/Model/dsl.ts +129 -0
  279. package/src/Model/filter/filterApi.ts +78 -0
  280. package/src/Model/filter/types/errors.ts +47 -0
  281. package/src/Model/filter/types/fields.ts +50 -0
  282. package/src/Model/filter/types/path/common.ts +404 -0
  283. package/src/Model/filter/types/path/eager.ts +329 -0
  284. package/src/Model/filter/types/path/index.ts +4 -0
  285. package/src/Model/filter/types/utils.ts +128 -0
  286. package/src/Model/filter/types/validator.ts +46 -0
  287. package/src/Model/filter/types.ts +6 -0
  288. package/src/Model/query/dsl.ts +3092 -0
  289. package/src/Model/query/new-kid-interpreter.ts +532 -0
  290. package/src/Model/query.ts +13 -0
  291. package/src/Model.ts +5 -0
  292. package/src/NonEmptySet.ts +6 -4
  293. package/src/Option.ts +2 -0
  294. package/src/Pure.ts +10 -7
  295. package/src/QueueMaker.ts +11 -0
  296. package/src/RequestContext.ts +48 -0
  297. package/src/RequestScopedDependencies.ts +45 -0
  298. package/src/Schema/Class.ts +648 -60
  299. package/src/Schema/SchemaParser.ts +12 -0
  300. package/src/Schema/SpecialJsonSchema.ts +137 -0
  301. package/src/Schema/SpecialOpenApi.ts +130 -0
  302. package/src/Schema/brand.ts +24 -9
  303. package/src/Schema/email.ts +14 -7
  304. package/src/Schema/ext.ts +508 -102
  305. package/src/Schema/moreStrings.ts +217 -86
  306. package/src/Schema/numbers.ts +107 -28
  307. package/src/Schema/phoneNumber.ts +13 -9
  308. package/src/Schema/schema.ts +1 -1
  309. package/src/Schema/strings.ts +23 -14
  310. package/src/Schema.ts +403 -46
  311. package/src/SchemaAST.ts +1 -0
  312. package/src/Set.ts +6 -2
  313. package/src/Store.ts +325 -0
  314. package/src/_ext/Array.ts +4 -2
  315. package/src/_ext/misc.ts +4 -1
  316. package/src/_ext/ord.ext.ts +2 -1
  317. package/src/builtin-json.d.ts +24 -0
  318. package/src/builtin.ts +16 -21
  319. package/src/client/InvalidationKeys.ts +55 -0
  320. package/src/client/apiClientFactory.ts +301 -141
  321. package/src/client/clientFor.ts +107 -34
  322. package/src/client/errors.ts +100 -29
  323. package/src/client/makeClient.ts +594 -80
  324. package/src/client.ts +6 -4
  325. package/src/http/Request.ts +3 -3
  326. package/src/http.ts +1 -1
  327. package/src/ids.ts +45 -7
  328. package/src/index.ts +20 -23
  329. package/src/middleware.ts +13 -9
  330. package/src/rpc/Invalidation.ts +278 -0
  331. package/src/rpc/MiddlewareMaker.ts +88 -80
  332. package/src/rpc/README.md +2 -2
  333. package/src/rpc/RpcContextMap.ts +7 -6
  334. package/src/rpc/RpcMiddleware.ts +19 -13
  335. package/src/rpc.ts +4 -4
  336. package/src/runtime.ts +57 -0
  337. package/src/setupRequest.ts +138 -0
  338. package/src/toast.ts +54 -0
  339. package/src/transform.ts +4 -4
  340. package/src/utils/effectify.ts +1 -1
  341. package/src/utils/gen.ts +8 -8
  342. package/src/utils/logLevel.ts +1 -1
  343. package/src/utils/logger.ts +4 -3
  344. package/src/utils.ts +85 -158
  345. package/src/validation.ts +2 -2
  346. package/src/withToast.ts +133 -0
  347. package/test/dist/rpc.test.d.ts.map +1 -1
  348. package/test/dist/rpcMiddlewareContext.test.d.ts.map +1 -0
  349. package/test/dist/secretURL.test.d.ts.map +1 -0
  350. package/test/dist/special.test.d.ts.map +1 -0
  351. package/test/moreStrings.test.ts +55 -10
  352. package/test/rpc.test.ts +38 -10
  353. package/test/rpcMiddlewareContext.test.ts +63 -0
  354. package/test/schema.test.ts +505 -26
  355. package/test/secretURL.test.ts +160 -0
  356. package/test/special.test.ts +1261 -0
  357. package/test/utils.test.ts +7 -7
  358. package/tsconfig.base.json +6 -5
  359. package/tsconfig.json +9 -2
  360. package/tsconfig.src.json +29 -29
  361. package/tsconfig.test.json +2 -2
  362. package/dist/Operations.d.ts +0 -123
  363. package/dist/Operations.d.ts.map +0 -1
  364. package/dist/Operations.js +0 -29
  365. package/dist/ServiceMap.d.ts.map +0 -1
  366. package/dist/ServiceMap.js +0 -91
  367. package/eslint.config.mjs +0 -26
  368. package/src/Operations.ts +0 -55
  369. package/tsconfig.json.bak +0 -12
package/src/Schema/ext.ts CHANGED
@@ -1,10 +1,50 @@
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"
38
+ import * as Order from "effect/Order"
4
39
  import * as S from "effect/Schema"
5
- import { type NonEmptyReadonlyArray } from "../Array.js"
6
- import { extendM, typedKeysOf } from "../utils.js"
7
- import { type AST } from "./schema.js"
40
+ import * as SchemaIssue from "effect/SchemaIssue"
41
+ import * as SchemaTransformation from "effect/SchemaTransformation"
42
+ import * as Struct from "effect/Struct"
43
+ import { type NonEmptyReadonlyArray } from "../Array.ts"
44
+ import * as Context from "../Context.ts"
45
+ import * as SchemaAST from "../SchemaAST.ts"
46
+ import { extendM, typedKeysOf } from "../utils.ts"
47
+ import { type AST } from "./schema.ts"
8
48
 
9
49
  type ProvidedCodec<Self extends S.Top, R> = S.Codec<
10
50
  Self["Type"],
@@ -13,137 +53,417 @@ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
13
53
  Exclude<Self["EncodingServices"], R>
14
54
  >
15
55
 
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)
28
- }
29
-
30
- // 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
- })
56
+ const concurrencySetting = Effect.runSync(
57
+ Config
58
+ .Literal("unbounded", "SCHEMA_CONCURRENCY")
59
+ .pipe(Config.orElse(() => Config.Number("SCHEMA_CONCURRENCY")), Config.option)
36
60
  )
37
61
 
62
+ export const DefaultParseOptions: SchemaAST.ParseOptions = {
63
+ concurrency: Option.getOrElse(concurrencySetting, () => "unbounded" as const)
64
+ }
65
+
38
66
  /**
39
- * Like the default Schema `Date` but from String with `withDefault` => now
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.
40
72
  */
41
- export const Date = Object.assign(DateFromString, {
42
- withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
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
+
86
+ const DateString = S.String.annotate({
87
+ identifier: "DateOrInvalid",
88
+ description: "an ISO 8601 date string that will be decoded as a Date (may be invalid)",
89
+ format: "date-time"
43
90
  })
44
91
 
45
92
  /**
46
- * Like the default Schema `Boolean` but with `withDefault` => false
93
+ * Schema type for {@link DateFromString}.
94
+ *
95
+ * @category Schemas
96
+ * @since 4.0.0
47
97
  */
48
- export const Boolean = Object.assign(S.Boolean, {
49
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
50
- })
98
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
51
99
 
52
100
  /**
53
- * You probably want to use `Finite` instead of this.
54
- * Like the default Schema `Number` but with `withDefault` => 0
101
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
102
+ *
103
+ * Decoding:
104
+ * - A `string` is decoded as a `Date`.
105
+ *
106
+ * Encoding:
107
+ * - A `Date` is encoded as a `string`.
108
+ *
109
+ * Use this when the Encoded form must be JSON (`string`). Domain models should
110
+ * prefer {@link Date}, whose Encoded form is `Date`; JSON stores convert via
111
+ * `Schema.toCodecJson`.
112
+ *
113
+ * @since 4.0.0
55
114
  */
56
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
115
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
116
+
117
+ const dateHelpers = (s: S.Date) => ({
118
+ /**
119
+ * Construction-only default `new Date()`. Applied only when the field is
120
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
121
+ * used to JIT-migrate database fields. See file-level note.
122
+ */
123
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))),
124
+ /**
125
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
126
+ * missing field may be data corruption, not an old-shape document; silently
127
+ * substituting `new Date()` hides the problem. Prefer an explicit,
128
+ * preferably versioned migration over a decode-time fallback. See
129
+ * file-level note.
130
+ */
131
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new globalThis.Date())))
132
+ })
133
+
134
+ /** Like the default Schema `Date` (Encoded is `Date`) with default helpers. */
135
+ export const Date = extendM(S.Date, dateHelpers)
57
136
 
58
137
  /**
59
- * Like the default Schema `Finite` but with `withDefault` => 0
138
+ * Alias of {@link Date}. Core `Schema.Date` already rejects invalid Dates.
139
+ *
140
+ * @deprecated Use {@link Date}.
60
141
  */
61
- export const Finite = Object.assign(S.Finite, { withDefault: S.Finite.pipe(withDefaultConstructor(() => 0)) })
142
+ export const DateValid = Date
143
+
144
+ /** Like the default Schema `Boolean` but with default helpers. */
145
+ export const Boolean = Object.assign(S.Boolean, {
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)))
159
+ })
62
160
 
63
161
  /**
64
- * Like the default Schema `Literal` but with `withDefault` => literals[0]
162
+ * You probably want to use `Finite` instead of this. Like the default Schema
163
+ * `Number` but with default helpers.
65
164
  */
66
- export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(...literals: Literals) =>
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
+ })
180
+
181
+ // Generation-only: native Arbitrary otherwise samples up to ±Number.MAX_VALUE, so
182
+ // derived totals overflow to Infinity. Validation and JSON Schema are unaffected.
183
+ const realisticFiniteSamples = S.makeFilter<number>(() => undefined, {
184
+ arbitraryConstraint: { order: Order.Number, minimum: -1_000_000, maximum: 1_000_000 }
185
+ })
186
+ const FiniteSchema = S.Finite.pipe(S.check(realisticFiniteSamples))
187
+
188
+ /** Like the default Schema `Finite` but with default helpers. */
189
+ export const Finite = Object.assign(FiniteSchema, {
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: FiniteSchema.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: FiniteSchema.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) =>
67
207
  pipe(
68
208
  S.Literals(literals),
69
209
  (s) =>
70
210
  Object.assign(s, {
211
+ /** Override the default literal value used by `withConstructorDefault` / `withDecodingDefaultType`. */
71
212
  changeDefault: <A extends Literals[number]>(a: A) => {
72
213
  return Object.assign(S.Literals(literals), {
73
214
  Default: a,
74
- 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)))
75
230
  }) // todo: copy annotations from original?
76
231
  },
77
- Default: literals[0] as typeof literals[0],
78
- 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])))
79
249
  })
80
250
  )
81
251
 
82
- /**
83
- * Like the default Schema `Array` but with `withDefault` => []
84
- */
252
+ /** Like the default Schema `Array` but with default helpers. */
85
253
  export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
86
254
  return pipe(
87
- S.Array(value),
88
- (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
+ })
89
274
  )
90
275
  }
91
276
 
92
277
  /**
93
- * Like the default Schema `Map` but with `withDefault` => []
278
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
94
279
  */
95
- function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
96
- return pipe(
97
- S.ReadonlyMap(input.key, input.value),
98
- (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<ValueSchema["Type"]>>
285
+ const schema = from.pipe(
286
+ S.decodeTo(
287
+ to,
288
+ SchemaTransformation.transform({
289
+ decode: (arr) => new Set(arr) as ReadonlySet<ValueSchema["Type"]>,
290
+ encode: (set) => [...set]
291
+ })
292
+ )
99
293
  )
294
+ return schema
100
295
  }
101
296
 
102
- export { Map_ as Map }
103
-
104
297
  /**
105
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
298
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
106
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<KeySchema["Type"], ValueSchema["Type"]>
312
+ >
313
+ const schema = from.pipe(
314
+ S.decodeTo(
315
+ to,
316
+ SchemaTransformation.transform({
317
+ decode: (
318
+ arr
319
+ ) => new Map(arr) as ReadonlyMap<KeySchema["Type"], ValueSchema["Type"]>,
320
+ encode: (
321
+ map
322
+ ) => [...map.entries()] as any // fu
323
+ })
324
+ )
325
+ )
326
+ return schema
327
+ }
328
+
329
+ /** Like the default Schema `ReadonlySet` (Encoded is `Set`) with default helpers. */
107
330
  export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
108
331
  pipe(
109
332
  S.ReadonlySet(value),
110
333
  (s) =>
111
- 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<ValueSchema["Type"]>()))
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<ValueSchema["Type"]>()))
353
+ )
354
+ })
112
355
  )
113
356
 
114
- /**
115
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
116
- */
357
+ /** Like the default Schema `ReadonlyMap` (Encoded is `Map`) with default helpers. */
117
358
  export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
118
359
  readonly key: KeySchema
119
360
  readonly value: ValueSchema
120
361
  }) =>
121
362
  pipe(
122
363
  S.ReadonlyMap(pair.key, pair.value),
123
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
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
+ })
124
382
  )
125
383
 
126
- /**
127
- * Like the default Schema `NullOr` but with `withDefault` => null
128
- */
384
+ /** Like the default Schema `NullOr` but with default helpers. */
129
385
  export const NullOr = <Schema extends S.Top>(self: Schema) =>
130
386
  pipe(
131
387
  S.NullOr(self),
132
- (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
+ })
133
406
  )
134
407
 
135
- export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
136
- 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())))
137
417
 
138
- 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)))
139
427
 
140
- 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)))
141
437
 
142
- 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(() => [])))
143
447
 
144
- 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())))
145
457
 
146
- 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())))
147
467
 
148
468
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
149
469
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -172,9 +492,25 @@ export type WithDefaults<Self extends S.Top> = (
172
492
  // export type UnionToIntersection3<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I
173
493
  // : never
174
494
 
495
+ /** Union of core `Schema.Date` (Date objects) and string-encoded `DateFromString`, with default helpers. */
175
496
  export const inputDate = extendM(
176
- S.Union([S.DateValid, S.Date]),
177
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
497
+ S.Union([S.Date, DateFromString]),
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
+ })
178
514
  )
179
515
 
180
516
  export interface UnionBrand {}
@@ -192,13 +528,81 @@ export function makeOptional<NER extends S.Struct.Fields>(
192
528
  }, {} as any)
193
529
  }
194
530
 
531
+ // `Omit` erases the polymorphic `this` of "this-returning" methods (`annotate`, `annotateKey`,
532
+ // `check`, `rebuild`): extracting them through a mapped type binds their `this["Rebuild"]` back to
533
+ // the original `T`, not to this intersection. So overriding just `Rebuild` above is not enough, those
534
+ // four methods have to be redeclared here with a concrete return type instead of `this["Rebuild"]`.
535
+ export type DropConstructorDefault<T extends S.Top> =
536
+ & Omit<
537
+ T,
538
+ "~type.constructor.default" | "Rebuild" | "annotate" | "annotateKey" | "check" | "rebuild"
539
+ >
540
+ & {
541
+ readonly "~type.constructor.default": "no-default"
542
+ readonly "Rebuild": DropConstructorDefault<T>
543
+ annotate(annotations: S.Annotations.Bottom<T["Type"], T["~type.parameters"]>): DropConstructorDefault<T>
544
+ annotateKey(annotations: S.Annotations.Key<T["Type"]>): DropConstructorDefault<T>
545
+ check(
546
+ ...checks: readonly [SchemaAST.Check<T["Type"]>, ...Array<SchemaAST.Check<T["Type"]>>]
547
+ ): DropConstructorDefault<T>
548
+ rebuild(ast: T["ast"]): DropConstructorDefault<T>
549
+ }
550
+
551
+ // `replaceContext` is `@internal` in `effect/SchemaAST` (stripped from its public types, though the
552
+ // export itself still ships in the compiled JS). Effect's own `withConstructorDefault` goes through the
553
+ // same function internally, so this mirrors an existing code path rather than reaching for something
554
+ // novel, but it's still an internal API that could move or disappear without a signal.
555
+ const SchemaASTInternal = SchemaAST as unknown as {
556
+ readonly replaceContext: <A extends SchemaAST.AST>(ast: A, context: SchemaAST.Context | undefined) => A
557
+ }
558
+
559
+ export interface DropConstructorDefaultLambda extends Struct.Lambda {
560
+ <T extends S.Top>(self: T): DropConstructorDefault<T>
561
+ readonly "~lambda.out": DropConstructorDefault<this["~lambda.in"] & S.Top>
562
+ }
563
+
564
+ /**
565
+ * A `Struct.Lambda` that removes a field's `withConstructorDefault`, if any.
566
+ * Compose with `Struct.pick`/`Struct.omit` via `Struct.map` to derive a
567
+ * partial-update schema from a "create" schema without inheriting defaults
568
+ * that would resurrect omitted fields in `.make(...)` output:
569
+ *
570
+ * ```ts
571
+ * import { flow } from "effect/Function"
572
+ * import * as Struct from "effect/Struct"
573
+ * import * as S from "effect-app/Schema"
574
+ *
575
+ * const Person = S.Struct({
576
+ * id: S.String,
577
+ * name: S.String.pipe(S.optionalKey, S.withConstructorDefault(() => ""))
578
+ * })
579
+ *
580
+ * const UpdatePerson = Person.mapFields(
581
+ * flow(Struct.omit(["id"]), Struct.map(S.dropConstructorDefault))
582
+ * )
583
+ * ```
584
+ */
585
+ export const dropConstructorDefault: DropConstructorDefaultLambda = Struct.lambda<DropConstructorDefaultLambda>(
586
+ (schema) => {
587
+ const context = schema.ast.context
588
+ if (!context?.constructorDefault) return schema
589
+ return schema.rebuild(
590
+ SchemaASTInternal.replaceContext(
591
+ schema.ast,
592
+ new SchemaAST.Context(context.isOptional, context.isMutable, undefined, context.annotations)
593
+ )
594
+ )
595
+ }
596
+ )
597
+
195
598
  export function makeExactOptional<NER extends S.Struct.Fields>(
196
599
  t: NER
197
600
  ): {
198
- [K in keyof NER]: NER[K] extends S.Top ? ReturnType<typeof S.optionalKey<NER[K] & S.Top>> : any
601
+ [K in keyof NER]: NER[K] extends S.Top ? DropConstructorDefault<ReturnType<typeof S.optionalKey<NER[K] & S.Top>>>
602
+ : any
199
603
  } {
200
604
  return typedKeysOf(t).reduce((prev, cur) => {
201
- prev[cur] = S.optionalKey(t[cur] as any)
605
+ prev[cur] = dropConstructorDefault(S.optionalKey(t[cur] as any))
202
606
  return prev
203
607
  }, {} as any)
204
608
  }
@@ -215,20 +619,20 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
215
619
  from.pipe(
216
620
  S.decodeTo(
217
621
  to,
218
- SchemaTransformation.transformOrFail({
622
+ SchemaTransformation.transformEffect({
219
623
  decode: (input: any, options: any) => Effect.sync(() => decode(input, options)),
220
624
  encode: (i: any) =>
221
625
  Effect.fail(
222
626
  new SchemaIssue.Forbidden(
223
- Option.some(i),
224
- { message: "One way schema transformation, encoding is not allowed" }
627
+ { message: "One way schema transformation, encoding is not allowed" },
628
+ i
225
629
  )
226
630
  )
227
- }) as any
631
+ })
228
632
  )
229
633
  )
230
634
 
231
- /** A version of transformOrFail which is only a one way mapping of From->To */
635
+ /** A version of transformEffect which is only a one way mapping of From->To */
232
636
  export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
233
637
  from: From,
234
638
  to: To,
@@ -240,43 +644,45 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
240
644
  from.pipe(
241
645
  S.decodeTo(
242
646
  to,
243
- SchemaTransformation.transformOrFail({
244
- decode: decode as any,
647
+ SchemaTransformation.transformEffect({
648
+ decode,
245
649
  encode: (i: any) =>
246
650
  Effect.fail(
247
651
  new SchemaIssue.Forbidden(
248
- Option.some(i),
249
- { message: "One way schema transformation, encoding is not allowed" }
652
+ { message: "One way schema transformation, encoding is not allowed" },
653
+ i
250
654
  )
251
655
  )
252
- }) as any
656
+ })
253
657
  )
254
658
  )
255
659
 
256
- export const provide = <Self extends S.Top, R>(
257
- self: Self,
258
- context: ServiceMap.ServiceMap<R>
259
- ): ProvidedCodec<Self, R> => {
660
+ export const provide: {
661
+ <R>(context: Context.Context<R>): <Self extends S.Top>(self: Self) => ProvidedCodec<Self, R>
662
+ <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R>
663
+ } = Function.dual(2, <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R> => {
260
664
  const prov = Effect.provide(context)
261
665
  return self.pipe(
262
666
  S.middlewareDecoding((effect) => prov(effect)),
263
667
  S.middlewareEncoding((effect) => prov(effect))
264
- ) as ProvidedCodec<Self, R>
265
- }
266
- export const contextFromServices = <
668
+ )
669
+ })
670
+ export const contextFromServices = Effect.fnUntraced(function*<
267
671
  Self extends S.Top,
268
- Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
672
+ Tags extends ReadonlyArray<Context.Key<any, any>>
673
+ >(self: Self, ...services: Tags) {
674
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
675
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
676
+ )
677
+ return provide(self, context)
678
+ }) as <
679
+ Self extends S.Top,
680
+ Tags extends ReadonlyArray<Context.Key<any, any>>
269
681
  >(
270
682
  self: Self,
271
683
  ...services: Tags
272
- ): Effect.Effect<
273
- ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
684
+ ) => Effect.Effect<
685
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
274
686
  never,
275
- ServiceMap.Service.Identifier<Tags[number]>
276
- > =>
277
- Effect.gen(function*() {
278
- const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
279
- yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
280
- )
281
- return provide(self, context)
282
- })
687
+ Context.Service.Identifier<Tags[number]>
688
+ >