effect-app 4.0.0-beta.31 → 4.0.0-beta.310

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 +1486 -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 +280 -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 +5 -8
  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 +537 -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 +63 -0
  49. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  50. package/dist/Model/Repository/makeRepo.js +27 -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 +58 -0
  55. package/dist/Model/Repository/validation.d.ts.map +1 -0
  56. package/dist/Model/Repository/validation.js +32 -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 +614 -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 +361 -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 +188 -19
  121. package/dist/Schema/Class.d.ts.map +1 -1
  122. package/dist/Schema/Class.js +260 -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 +118 -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 +4 -2
  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 +7 -4
  138. package/dist/Schema/ext.d.ts +374 -56
  139. package/dist/Schema/ext.d.ts.map +1 -1
  140. package/dist/Schema/ext.js +393 -54
  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 +49 -42
  144. package/dist/Schema/numbers.d.ts +34 -21
  145. package/dist/Schema/numbers.d.ts.map +1 -1
  146. package/dist/Schema/numbers.js +55 -12
  147. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  148. package/dist/Schema/phoneNumber.js +6 -3
  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 +222 -8
  156. package/dist/Schema.d.ts.map +1 -1
  157. package/dist/Schema.js +191 -12
  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 +163 -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 +155 -35
  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 -36
  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 +42 -14
  202. package/dist/ids.d.ts.map +1 -1
  203. package/dist/ids.js +30 -5
  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 +2276 -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 +14 -16
  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 +905 -0
  273. package/src/Model/Repository/legacy.ts +29 -0
  274. package/src/Model/Repository/makeRepo.ts +156 -0
  275. package/src/Model/Repository/service.ts +676 -0
  276. package/src/Model/Repository/validation.ts +31 -0
  277. package/src/Model/Repository.ts +7 -0
  278. package/src/Model/dsl.ts +129 -0
  279. package/src/Model/filter/filterApi.ts +60 -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 +2911 -0
  289. package/src/Model/query/new-kid-interpreter.ts +526 -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 +633 -59
  299. package/src/Schema/SchemaParser.ts +12 -0
  300. package/src/Schema/SpecialJsonSchema.ts +139 -0
  301. package/src/Schema/SpecialOpenApi.ts +130 -0
  302. package/src/Schema/brand.ts +22 -2
  303. package/src/Schema/email.ts +9 -4
  304. package/src/Schema/ext.ts +517 -93
  305. package/src/Schema/moreStrings.ts +147 -68
  306. package/src/Schema/numbers.ts +97 -28
  307. package/src/Schema/phoneNumber.ts +9 -5
  308. package/src/Schema/schema.ts +1 -1
  309. package/src/Schema/strings.ts +23 -14
  310. package/src/Schema.ts +397 -26
  311. package/src/SchemaAST.ts +1 -0
  312. package/src/Set.ts +6 -2
  313. package/src/Store.ts +273 -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 +299 -134
  321. package/src/client/clientFor.ts +107 -34
  322. package/src/client/errors.ts +100 -29
  323. package/src/client/makeClient.ts +593 -92
  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 +33 -6
  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 +56 -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 +1 -1
  352. package/test/rpc.test.ts +42 -13
  353. package/test/rpcMiddlewareContext.test.ts +63 -0
  354. package/test/schema.test.ts +504 -30
  355. package/test/secretURL.test.ts +160 -0
  356. package/test/special.test.ts +1258 -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"
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"
39
+ import { isDateValid } from "effect/Schema"
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,131 +53,429 @@ 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)
56
+ const concurrencySetting = Effect.runSync(
57
+ Config
58
+ .literal("unbounded", "SCHEMA_CONCURRENCY")
59
+ .pipe(Config.orElse(() => Config.number("SCHEMA_CONCURRENCY")), Config.option)
60
+ )
61
+
62
+ export const DefaultParseOptions: SchemaAST.ParseOptions = {
63
+ concurrency: Option.getOrElse(concurrencySetting, () => "unbounded" as const)
28
64
  }
29
65
 
66
+ /**
67
+ * Parse-options annotation used on schema constructors for decode paths where callers
68
+ * cannot currently pass parse options (notably some RPC / HttpApi integration paths).
69
+ *
70
+ * Keep this annotation in place so those framework-managed decodes still run with
71
+ * unbounded concurrency by default.
72
+ */
73
+ export const concurrencyUnbounded = { parseOptions: DefaultParseOptions } as const
74
+
75
+ type DecodeLike = (schema: any) => (input: any, options?: SchemaAST.ParseOptions) => any
76
+
77
+ export const withDefaultParseOptions = <Decode extends DecodeLike>(
78
+ decode: Decode,
79
+ defaultParseOptions: SchemaAST.ParseOptions = DefaultParseOptions
80
+ ): Decode =>
81
+ ((schema: any) => {
82
+ const run = decode(schema)
83
+ return (input: any, options?: SchemaAST.ParseOptions) => run(input, { ...defaultParseOptions, ...options })
84
+ }) as Decode
85
+
30
86
  // TODO: v4 migration - Date is no longer by default encoded to string.
31
- const DateFromString = Schema.Date.pipe(
32
- Schema.encodeTo(Schema.String, {
33
- decode: SchemaGetter.Date(),
34
- encode: SchemaGetter.transform((_) => _.toISOString())
35
- })
36
- )
87
+
88
+ const DateString = S.String.annotate({
89
+ identifier: "DateOrInvalid",
90
+ description: "an ISO 8601 date string that will be decoded as a Date (may be invalid)",
91
+ format: "date-time"
92
+ })
37
93
 
38
94
  /**
39
- * Like the default Schema `Date` but from String with `withDefault` => now
95
+ * Schema type for {@link DateFromString}.
96
+ *
97
+ * @category Schemas
98
+ * @since 4.0.0
40
99
  */
41
- export const Date = Object.assign(DateFromString, {
42
- withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
43
- })
100
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
44
101
 
45
102
  /**
46
- * Like the default Schema `Boolean` but with `withDefault` => false
103
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
104
+ *
105
+ * Decoding:
106
+ * - A `string` is decoded as a `Date`.
107
+ *
108
+ * Encoding:
109
+ * - A `Date` is encoded as a `string`.
110
+ *
111
+ * @since 4.0.0
47
112
  */
113
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
114
+
115
+ /** Like the default Schema `Date` but from String, with default helpers. */
116
+ export const Date = extendM(DateFromString, (s) => ({
117
+ /**
118
+ * Construction-only default `new Date()`. Applied only when the field is
119
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
120
+ * used to JIT-migrate database fields. See file-level note.
121
+ */
122
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
123
+ /**
124
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
125
+ * missing field may be data corruption, not an old-shape document; silently
126
+ * substituting `new Date()` hides the problem. Prefer an explicit,
127
+ * preferably versioned migration over a decode-time fallback. See
128
+ * file-level note.
129
+ */
130
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
131
+ }))
132
+
133
+ const DateValidString = S.String.annotate({
134
+ identifier: "Date",
135
+ description: "a valid ISO 8601 date string that will be decoded as a Date",
136
+ format: "date-time"
137
+ })
138
+
139
+ const DateValidFromString = DateValidString
140
+ .pipe(
141
+ S.decodeTo(S.Date, SchemaTransformation.dateFromString)
142
+ )
143
+ .check(isDateValid())
144
+
145
+ /** Like the default Schema `DateValid` but from String, with default helpers. */
146
+ export const DateValid = extendM(DateValidFromString, (s) => ({
147
+ /**
148
+ * Construction-only default `new Date()`. Applied only when the field is
149
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
150
+ * used to JIT-migrate database fields. See file-level note.
151
+ */
152
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
153
+ /**
154
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
155
+ * missing field may be data corruption, not an old-shape document; silently
156
+ * substituting `new Date()` hides the problem. Prefer an explicit,
157
+ * preferably versioned migration over a decode-time fallback. See
158
+ * file-level note.
159
+ */
160
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
161
+ }))
162
+
163
+ /** Like the default Schema `Boolean` but with default helpers. */
48
164
  export const Boolean = Object.assign(S.Boolean, {
49
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
165
+ /**
166
+ * Construction-only default `false`. Applied only when the field is
167
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
168
+ * used to JIT-migrate database fields. See file-level note.
169
+ */
170
+ withConstructorDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false))),
171
+ /**
172
+ * Decode-time default `false`. **Discouraged for persisted data:** a
173
+ * missing field may be data corruption, not an old-shape document; silently
174
+ * substituting `false` hides the problem. Prefer an explicit, preferably
175
+ * versioned migration over a decode-time fallback. See file-level note.
176
+ */
177
+ withDecodingDefaultType: S.Boolean.pipe(S.withDecodingDefaultType(Effect.succeed(false)))
50
178
  })
51
179
 
52
180
  /**
53
- * Like the default Schema `Number` but with `withDefault` => 0
181
+ * You probably want to use `Finite` instead of this. Like the default Schema
182
+ * `Number` but with default helpers.
54
183
  */
55
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
184
+ export const Number = Object.assign(S.Number, {
185
+ /**
186
+ * Construction-only default `0`. Applied only when the field is omitted
187
+ * from `.make(...)` input. NOT applied during decode — cannot be used to
188
+ * JIT-migrate database fields. See file-level note.
189
+ */
190
+ withConstructorDefault: S.Number.pipe(S.withConstructorDefault(Effect.succeed(0))),
191
+ /**
192
+ * Decode-time default `0`. **Discouraged for persisted data:** a missing
193
+ * field may be data corruption, not an old-shape document; silently
194
+ * substituting `0` hides the problem. Prefer an explicit, preferably
195
+ * versioned migration over a decode-time fallback. See file-level note.
196
+ */
197
+ withDecodingDefaultType: S.Number.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
198
+ })
56
199
 
57
- /**
58
- * Like the default Schema `Literal` but with `withDefault` => literals[0]
59
- */
60
- export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(...literals: Literals) =>
200
+ /** Like the default Schema `Finite` but with default helpers. */
201
+ export const Finite = Object.assign(S.Finite, {
202
+ /**
203
+ * Construction-only default `0`. Applied only when the field is omitted
204
+ * from `.make(...)` input. NOT applied during decode — cannot be used to
205
+ * JIT-migrate database fields. See file-level note.
206
+ */
207
+ withConstructorDefault: S.Finite.pipe(S.withConstructorDefault(Effect.succeed(0))),
208
+ /**
209
+ * Decode-time default `0`. **Discouraged for persisted data:** a missing
210
+ * field may be data corruption, not an old-shape document; silently
211
+ * substituting `0` hides the problem. Prefer an explicit, preferably
212
+ * versioned migration over a decode-time fallback. See file-level note.
213
+ */
214
+ withDecodingDefaultType: S.Finite.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
215
+ })
216
+
217
+ /** Like the default Schema `Literals` but with default helpers. Default value is `literals[0]`. */
218
+ export const Literals = <const Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(literals: Literals) =>
61
219
  pipe(
62
220
  S.Literals(literals),
63
221
  (s) =>
64
222
  Object.assign(s, {
223
+ /** Override the default literal value used by `withConstructorDefault` / `withDecodingDefaultType`. */
65
224
  changeDefault: <A extends Literals[number]>(a: A) => {
66
225
  return Object.assign(S.Literals(literals), {
67
226
  Default: a,
68
- withDefault: s.pipe(withDefaultConstructor(() => a))
227
+ /**
228
+ * Construction-only default. Applied only when the field is
229
+ * omitted from `.make(...)` input. NOT applied during decode —
230
+ * cannot be used to JIT-migrate database fields. See file-level
231
+ * note.
232
+ */
233
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a))),
234
+ /**
235
+ * Decode-time default. **Discouraged for persisted data:** a
236
+ * missing field may be data corruption, not an old-shape
237
+ * document; silently substituting hides the problem. Prefer an
238
+ * explicit, preferably versioned migration over a decode-time
239
+ * fallback. See file-level note.
240
+ */
241
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(a)))
69
242
  }) // todo: copy annotations from original?
70
243
  },
71
- Default: literals[0] as typeof literals[0],
72
- withDefault: s.pipe(withDefaultConstructor(() => literals[0]))
244
+ // 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
245
+ Default: literals[0] as Literals[0],
246
+ /**
247
+ * Construction-only default `literals[0]`. Applied only when the
248
+ * field is omitted from `.make(...)` input. NOT applied during
249
+ * decode — cannot be used to JIT-migrate database fields. See
250
+ * file-level note.
251
+ */
252
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(literals[0]))),
253
+ /**
254
+ * Decode-time default `literals[0]`. **Discouraged for persisted
255
+ * data:** a missing field may be data corruption, not an old-shape
256
+ * document; silently substituting hides the problem. Prefer an
257
+ * explicit, preferably versioned migration over a decode-time
258
+ * fallback. See file-level note.
259
+ */
260
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(literals[0])))
73
261
  })
74
262
  )
75
263
 
76
- /**
77
- * Like the default Schema `Array` but with `withDefault` => []
78
- */
264
+ /** Like the default Schema `Array` but with default helpers. */
79
265
  export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
80
266
  return pipe(
81
- S.Array(value),
82
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
267
+ S.Array(value).annotate(concurrencyUnbounded),
268
+ (s) =>
269
+ Object.assign(s, {
270
+ /**
271
+ * Construction-only default `[]`. Applied only when the field is
272
+ * omitted from `.make(...)` input. NOT applied during decode —
273
+ * cannot be used to JIT-migrate database fields. See file-level
274
+ * note.
275
+ */
276
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => []))),
277
+ /**
278
+ * Decode-time default `[]`. **Discouraged for persisted data:** a
279
+ * missing field may be data corruption, not an old-shape document;
280
+ * silently substituting `[]` hides the problem. Prefer an explicit,
281
+ * preferably versioned migration over a decode-time fallback. See
282
+ * file-level note.
283
+ */
284
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => [])))
285
+ })
83
286
  )
84
287
  }
85
288
 
86
289
  /**
87
- * Like the default Schema `Map` but with `withDefault` => []
290
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
88
291
  */
89
- function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
90
- return pipe(
91
- S.ReadonlyMap(input.key, input.value),
92
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
292
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
293
+ const from = S
294
+ .Array(value)
295
+ .annotate({ ...concurrencyUnbounded, expected: "an array of unique items that will be decoded as a ReadonlySet" })
296
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<ValueSchema["Type"]>>
297
+ const schema = from.pipe(
298
+ S.decodeTo(
299
+ to,
300
+ SchemaTransformation.transform({
301
+ decode: (arr) => new Set(arr) as ReadonlySet<ValueSchema["Type"]>,
302
+ encode: (set) => [...set]
303
+ })
304
+ )
93
305
  )
306
+ return schema
94
307
  }
95
308
 
96
- export { Map_ as Map }
97
-
98
309
  /**
99
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
310
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
100
311
  */
312
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
313
+ readonly key: KeySchema
314
+ readonly value: ValueSchema
315
+ }) => {
316
+ const from = S
317
+ .Array(S.Tuple([pair.key, pair.value]))
318
+ .annotate({
319
+ ...concurrencyUnbounded,
320
+ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap"
321
+ })
322
+ const to = S.instanceOf(Map) as S.instanceOf<
323
+ ReadonlyMap<KeySchema["Type"], ValueSchema["Type"]>
324
+ >
325
+ const schema = from.pipe(
326
+ S.decodeTo(
327
+ to,
328
+ SchemaTransformation.transform({
329
+ decode: (
330
+ arr
331
+ ) => new Map(arr) as ReadonlyMap<KeySchema["Type"], ValueSchema["Type"]>,
332
+ encode: (
333
+ map
334
+ ) => [...map.entries()] as any // fu
335
+ })
336
+ )
337
+ )
338
+ return schema
339
+ }
340
+
341
+ /** Like the default Schema `ReadonlySet` but from Array, with default helpers. */
101
342
  export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
102
343
  pipe(
103
- S.ReadonlySet(value),
344
+ ReadonlySetFromArray(value),
104
345
  (s) =>
105
- Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
346
+ Object.assign(s, {
347
+ /**
348
+ * Construction-only default `new Set()`. Applied only when the field
349
+ * is omitted from `.make(...)` input. NOT applied during decode —
350
+ * cannot be used to JIT-migrate database fields. See file-level
351
+ * note.
352
+ */
353
+ withConstructorDefault: s.pipe(
354
+ S.withConstructorDefault(Effect.sync(() => new Set<ValueSchema["Type"]>()))
355
+ ),
356
+ /**
357
+ * Decode-time default `new Set()`. **Discouraged for persisted
358
+ * data:** a missing field may be data corruption, not an old-shape
359
+ * document; silently substituting an empty set hides the problem.
360
+ * Prefer an explicit, preferably versioned migration over a
361
+ * decode-time fallback. See file-level note.
362
+ */
363
+ withDecodingDefaultType: s.pipe(
364
+ S.withDecodingDefaultType(Effect.sync(() => new Set<ValueSchema["Type"]>()))
365
+ )
366
+ })
106
367
  )
107
368
 
108
- /**
109
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
110
- */
369
+ /** Like the default Schema `ReadonlyMap` but from Array, with default helpers. */
111
370
  export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
112
371
  readonly key: KeySchema
113
372
  readonly value: ValueSchema
114
373
  }) =>
115
374
  pipe(
116
- S.ReadonlyMap(pair.key, pair.value),
117
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
375
+ ReadonlyMapFromArray(pair),
376
+ (s) =>
377
+ Object.assign(s, {
378
+ /**
379
+ * Construction-only default `new Map()`. Applied only when the field
380
+ * is omitted from `.make(...)` input. NOT applied during decode —
381
+ * cannot be used to JIT-migrate database fields. See file-level
382
+ * note.
383
+ */
384
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Map()))),
385
+ /**
386
+ * Decode-time default `new Map()`. **Discouraged for persisted
387
+ * data:** a missing field may be data corruption, not an old-shape
388
+ * document; silently substituting an empty map hides the problem.
389
+ * Prefer an explicit, preferably versioned migration over a
390
+ * decode-time fallback. See file-level note.
391
+ */
392
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new Map())))
393
+ })
118
394
  )
119
395
 
120
- /**
121
- * Like the default Schema `NullOr` but with `withDefault` => null
122
- */
396
+ /** Like the default Schema `NullOr` but with default helpers. */
123
397
  export const NullOr = <Schema extends S.Top>(self: Schema) =>
124
398
  pipe(
125
399
  S.NullOr(self),
126
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
400
+ (s) =>
401
+ Object.assign(s, {
402
+ /**
403
+ * Construction-only default `null`. Applied only when the field is
404
+ * omitted from `.make(...)` input. NOT applied during decode —
405
+ * cannot be used to JIT-migrate database fields. See file-level
406
+ * note.
407
+ */
408
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.succeed(null))),
409
+ /**
410
+ * Decode-time default `null`. **Discouraged for persisted data:** a
411
+ * missing field may be data corruption, not an old-shape document;
412
+ * silently substituting `null` hides the problem. Prefer an
413
+ * explicit, preferably versioned migration over a decode-time
414
+ * fallback. See file-level note.
415
+ */
416
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(null)))
417
+ })
127
418
  )
128
419
 
129
- export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
130
- schema.pipe(withDefaultConstructor(() => new global.Date()))
420
+ /**
421
+ * Attach a `withConstructorDefault` of `new Date()` to any schema.
422
+ *
423
+ * **Construction-only.** Applied only when the field is omitted from
424
+ * `.make(...)` input. NOT applied during decode — cannot be used to
425
+ * JIT-migrate database fields. See file-level note.
426
+ */
427
+ export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
428
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
131
429
 
132
- export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
430
+ /**
431
+ * Attach a `withConstructorDefault` of `false` to any schema.
432
+ *
433
+ * **Construction-only.** Applied only when the field is omitted from
434
+ * `.make(...)` input. NOT applied during decode — cannot be used to
435
+ * JIT-migrate database fields. See file-level note.
436
+ */
437
+ export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
438
+ schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
133
439
 
134
- export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
440
+ /**
441
+ * Attach a `withConstructorDefault` of `null` to any schema.
442
+ *
443
+ * **Construction-only.** Applied only when the field is omitted from
444
+ * `.make(...)` input. NOT applied during decode — cannot be used to
445
+ * JIT-migrate database fields. See file-level note.
446
+ */
447
+ export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
448
+ schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
135
449
 
136
- export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
450
+ /**
451
+ * Attach a `withConstructorDefault` of `[]` to any schema.
452
+ *
453
+ * **Construction-only.** Applied only when the field is omitted from
454
+ * `.make(...)` input. NOT applied during decode — cannot be used to
455
+ * JIT-migrate database fields. See file-level note.
456
+ */
457
+ export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
458
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
137
459
 
138
- export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
460
+ /**
461
+ * Attach a `withConstructorDefault` of `new Map()` to any schema.
462
+ *
463
+ * **Construction-only.** Applied only when the field is omitted from
464
+ * `.make(...)` input. NOT applied during decode — cannot be used to
465
+ * JIT-migrate database fields. See file-level note.
466
+ */
467
+ export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
468
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
139
469
 
140
- export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
470
+ /**
471
+ * Attach a `withConstructorDefault` of `new Set()` to any schema.
472
+ *
473
+ * **Construction-only.** Applied only when the field is omitted from
474
+ * `.make(...)` input. NOT applied during decode — cannot be used to
475
+ * JIT-migrate database fields. See file-level note.
476
+ */
477
+ export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
478
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
141
479
 
142
480
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
143
481
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -166,9 +504,25 @@ export type WithDefaults<Self extends S.Top> = (
166
504
  // export type UnionToIntersection3<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I
167
505
  // : never
168
506
 
507
+ /** Union of `DateValid` and `Date`, with default helpers. */
169
508
  export const inputDate = extendM(
170
- S.Union([S.DateValid, S.Date]),
171
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
509
+ S.Union([S.DateValid, Date]),
510
+ (s) => ({
511
+ /**
512
+ * Construction-only default `new Date()`. Applied only when the field is
513
+ * omitted from `.make(...)` input. NOT applied during decode — cannot be
514
+ * used to JIT-migrate database fields. See file-level note.
515
+ */
516
+ withConstructorDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))),
517
+ /**
518
+ * Decode-time default `new Date()`. **Discouraged for persisted data:** a
519
+ * missing field may be data corruption, not an old-shape document;
520
+ * silently substituting `new Date()` hides the problem. Prefer an
521
+ * explicit, preferably versioned migration over a decode-time fallback.
522
+ * See file-level note.
523
+ */
524
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new globalThis.Date())))
525
+ })
172
526
  )
173
527
 
174
528
  export interface UnionBrand {}
@@ -186,13 +540,81 @@ export function makeOptional<NER extends S.Struct.Fields>(
186
540
  }, {} as any)
187
541
  }
188
542
 
543
+ // `Omit` erases the polymorphic `this` of "this-returning" methods (`annotate`, `annotateKey`,
544
+ // `check`, `rebuild`): extracting them through a mapped type binds their `this["Rebuild"]` back to
545
+ // the original `T`, not to this intersection. So overriding just `Rebuild` above is not enough, those
546
+ // four methods have to be redeclared here with a concrete return type instead of `this["Rebuild"]`.
547
+ export type DropConstructorDefault<T extends S.Top> =
548
+ & Omit<
549
+ T,
550
+ "~type.constructor.default" | "Rebuild" | "annotate" | "annotateKey" | "check" | "rebuild"
551
+ >
552
+ & {
553
+ readonly "~type.constructor.default": "no-default"
554
+ readonly "Rebuild": DropConstructorDefault<T>
555
+ annotate(annotations: S.Annotations.Bottom<T["Type"], T["~type.parameters"]>): DropConstructorDefault<T>
556
+ annotateKey(annotations: S.Annotations.Key<T["Type"]>): DropConstructorDefault<T>
557
+ check(
558
+ ...checks: readonly [SchemaAST.Check<T["Type"]>, ...Array<SchemaAST.Check<T["Type"]>>]
559
+ ): DropConstructorDefault<T>
560
+ rebuild(ast: T["ast"]): DropConstructorDefault<T>
561
+ }
562
+
563
+ // `replaceContext` is `@internal` in `effect/SchemaAST` (stripped from its public types, though the
564
+ // export itself still ships in the compiled JS). Effect's own `withConstructorDefault` goes through the
565
+ // same function internally, so this mirrors an existing code path rather than reaching for something
566
+ // novel, but it's still an internal API that could move or disappear without a signal.
567
+ const SchemaASTInternal = SchemaAST as unknown as {
568
+ readonly replaceContext: <A extends SchemaAST.AST>(ast: A, context: SchemaAST.Context | undefined) => A
569
+ }
570
+
571
+ export interface DropConstructorDefaultLambda extends Struct.Lambda {
572
+ <T extends S.Top>(self: T): DropConstructorDefault<T>
573
+ readonly "~lambda.out": DropConstructorDefault<this["~lambda.in"] & S.Top>
574
+ }
575
+
576
+ /**
577
+ * A `Struct.Lambda` that removes a field's `withConstructorDefault`, if any.
578
+ * Compose with `Struct.pick`/`Struct.omit` via `Struct.map` to derive a
579
+ * partial-update schema from a "create" schema without inheriting defaults
580
+ * that would resurrect omitted fields in `.make(...)` output:
581
+ *
582
+ * ```ts
583
+ * import { flow } from "effect/Function"
584
+ * import * as Struct from "effect/Struct"
585
+ * import * as S from "effect-app/Schema"
586
+ *
587
+ * const Person = S.Struct({
588
+ * id: S.String,
589
+ * name: S.String.pipe(S.optionalKey, S.withConstructorDefault(() => ""))
590
+ * })
591
+ *
592
+ * const UpdatePerson = Person.mapFields(
593
+ * flow(Struct.omit(["id"]), Struct.map(S.dropConstructorDefault))
594
+ * )
595
+ * ```
596
+ */
597
+ export const dropConstructorDefault: DropConstructorDefaultLambda = Struct.lambda<DropConstructorDefaultLambda>(
598
+ (schema) => {
599
+ const context = schema.ast.context
600
+ if (!context?.defaultValue) return schema
601
+ return schema.rebuild(
602
+ SchemaASTInternal.replaceContext(
603
+ schema.ast,
604
+ new SchemaAST.Context(context.isOptional, context.isMutable, undefined, context.annotations)
605
+ )
606
+ )
607
+ }
608
+ )
609
+
189
610
  export function makeExactOptional<NER extends S.Struct.Fields>(
190
611
  t: NER
191
612
  ): {
192
- [K in keyof NER]: NER[K] extends S.Top ? ReturnType<typeof S.optionalKey<NER[K] & S.Top>> : any
613
+ [K in keyof NER]: NER[K] extends S.Top ? DropConstructorDefault<ReturnType<typeof S.optionalKey<NER[K] & S.Top>>>
614
+ : any
193
615
  } {
194
616
  return typedKeysOf(t).reduce((prev, cur) => {
195
- prev[cur] = S.optionalKey(t[cur] as any)
617
+ prev[cur] = dropConstructorDefault(S.optionalKey(t[cur] as any))
196
618
  return prev
197
619
  }, {} as any)
198
620
  }
@@ -218,7 +640,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
218
640
  { message: "One way schema transformation, encoding is not allowed" }
219
641
  )
220
642
  )
221
- }) as any
643
+ })
222
644
  )
223
645
  )
224
646
 
@@ -235,7 +657,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
235
657
  S.decodeTo(
236
658
  to,
237
659
  SchemaTransformation.transformOrFail({
238
- decode: decode as any,
660
+ decode,
239
661
  encode: (i: any) =>
240
662
  Effect.fail(
241
663
  new SchemaIssue.Forbidden(
@@ -243,34 +665,36 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
243
665
  { message: "One way schema transformation, encoding is not allowed" }
244
666
  )
245
667
  )
246
- }) as any
668
+ })
247
669
  )
248
670
  )
249
671
 
250
- export const provide = <Self extends S.Top, R>(
251
- self: Self,
252
- context: ServiceMap.ServiceMap<R>
253
- ): ProvidedCodec<Self, R> => {
672
+ export const provide: {
673
+ <R>(context: Context.Context<R>): <Self extends S.Top>(self: Self) => ProvidedCodec<Self, R>
674
+ <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R>
675
+ } = Function.dual(2, <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R> => {
254
676
  const prov = Effect.provide(context)
255
677
  return self.pipe(
256
678
  S.middlewareDecoding((effect) => prov(effect)),
257
679
  S.middlewareEncoding((effect) => prov(effect))
258
- ) as ProvidedCodec<Self, R>
259
- }
260
- export const contextFromServices = <
680
+ )
681
+ })
682
+ export const contextFromServices = Effect.fnUntraced(function*<
261
683
  Self extends S.Top,
262
- Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
684
+ Tags extends ReadonlyArray<Context.Key<any, any>>
685
+ >(self: Self, ...services: Tags) {
686
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
687
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
688
+ )
689
+ return provide(self, context)
690
+ }) as <
691
+ Self extends S.Top,
692
+ Tags extends ReadonlyArray<Context.Key<any, any>>
263
693
  >(
264
694
  self: Self,
265
695
  ...services: Tags
266
- ): Effect.Effect<
267
- ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
696
+ ) => Effect.Effect<
697
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
268
698
  never,
269
- ServiceMap.Service.Identifier<Tags[number]>
270
- > =>
271
- Effect.gen(function*() {
272
- const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
273
- yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
274
- )
275
- return provide(self, context)
276
- })
699
+ Context.Service.Identifier<Tags[number]>
700
+ >