effect-app 4.0.0-beta.3 → 4.0.0-beta.300

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