effect-app 4.0.0-beta.19 → 4.0.0-beta.190

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 (218) hide show
  1. package/CHANGELOG.md +796 -0
  2. package/dist/Array.d.ts +1 -1
  3. package/dist/Chunk.d.ts +1 -1
  4. package/dist/Chunk.d.ts.map +1 -1
  5. package/dist/Config/SecretURL.d.ts +1 -1
  6. package/dist/Config/SecretURL.d.ts.map +1 -1
  7. package/dist/Config/SecretURL.js +2 -2
  8. package/dist/Config/internal/configSecretURL.d.ts +1 -1
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config.d.ts +7 -0
  11. package/dist/Config.d.ts.map +1 -0
  12. package/dist/Config.js +6 -0
  13. package/dist/ConfigProvider.d.ts +39 -0
  14. package/dist/ConfigProvider.d.ts.map +1 -0
  15. package/dist/ConfigProvider.js +42 -0
  16. package/dist/Context.d.ts +40 -0
  17. package/dist/Context.d.ts.map +1 -0
  18. package/dist/Context.js +67 -0
  19. package/dist/Effect.d.ts +9 -10
  20. package/dist/Effect.d.ts.map +1 -1
  21. package/dist/Effect.js +3 -6
  22. package/dist/Function.d.ts +1 -1
  23. package/dist/Function.d.ts.map +1 -1
  24. package/dist/Inputify.type.d.ts +1 -1
  25. package/dist/Layer.d.ts +6 -5
  26. package/dist/Layer.d.ts.map +1 -1
  27. package/dist/Layer.js +1 -1
  28. package/dist/NonEmptySet.d.ts +1 -1
  29. package/dist/NonEmptySet.d.ts.map +1 -1
  30. package/dist/Operations.d.ts +369 -47
  31. package/dist/Operations.d.ts.map +1 -1
  32. package/dist/Operations.js +10 -10
  33. package/dist/Option.d.ts +1 -1
  34. package/dist/Option.d.ts.map +1 -1
  35. package/dist/Pure.d.ts +5 -5
  36. package/dist/Pure.d.ts.map +1 -1
  37. package/dist/Pure.js +13 -13
  38. package/dist/Schema/Class.d.ts +69 -20
  39. package/dist/Schema/Class.d.ts.map +1 -1
  40. package/dist/Schema/Class.js +190 -22
  41. package/dist/Schema/FastCheck.d.ts +1 -1
  42. package/dist/Schema/FastCheck.d.ts.map +1 -1
  43. package/dist/Schema/Methods.d.ts +1 -1
  44. package/dist/Schema/SchemaParser.d.ts +5 -0
  45. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  46. package/dist/Schema/SchemaParser.js +6 -0
  47. package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
  48. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  49. package/dist/Schema/SpecialJsonSchema.js +122 -0
  50. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  51. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  52. package/dist/Schema/SpecialOpenApi.js +123 -0
  53. package/dist/Schema/brand.d.ts +7 -2
  54. package/dist/Schema/brand.d.ts.map +1 -1
  55. package/dist/Schema/brand.js +1 -1
  56. package/dist/Schema/email.d.ts +1 -1
  57. package/dist/Schema/email.d.ts.map +1 -1
  58. package/dist/Schema/email.js +7 -4
  59. package/dist/Schema/ext.d.ts +117 -45
  60. package/dist/Schema/ext.d.ts.map +1 -1
  61. package/dist/Schema/ext.js +126 -43
  62. package/dist/Schema/moreStrings.d.ts +111 -11
  63. package/dist/Schema/moreStrings.d.ts.map +1 -1
  64. package/dist/Schema/moreStrings.js +14 -15
  65. package/dist/Schema/numbers.d.ts +127 -15
  66. package/dist/Schema/numbers.d.ts.map +1 -1
  67. package/dist/Schema/numbers.js +10 -12
  68. package/dist/Schema/phoneNumber.d.ts +1 -1
  69. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  70. package/dist/Schema/phoneNumber.js +6 -3
  71. package/dist/Schema/schema.d.ts +1 -1
  72. package/dist/Schema/strings.d.ts +37 -5
  73. package/dist/Schema/strings.d.ts.map +1 -1
  74. package/dist/Schema/strings.js +1 -5
  75. package/dist/Schema.d.ts +102 -56
  76. package/dist/Schema.d.ts.map +1 -1
  77. package/dist/Schema.js +128 -64
  78. package/dist/Set.d.ts +1 -1
  79. package/dist/Set.d.ts.map +1 -1
  80. package/dist/TypeTest.d.ts +1 -1
  81. package/dist/Types.d.ts +1 -1
  82. package/dist/Widen.type.d.ts +1 -1
  83. package/dist/_ext/Array.d.ts +1 -1
  84. package/dist/_ext/Array.d.ts.map +1 -1
  85. package/dist/_ext/date.d.ts +1 -1
  86. package/dist/_ext/misc.d.ts +1 -1
  87. package/dist/_ext/ord.ext.d.ts +1 -1
  88. package/dist/_ext/ord.ext.d.ts.map +1 -1
  89. package/dist/builtin.d.ts +1 -1
  90. package/dist/builtin.d.ts.map +1 -1
  91. package/dist/client/InvalidationKeys.d.ts +29 -0
  92. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  93. package/dist/client/InvalidationKeys.js +33 -0
  94. package/dist/client/apiClientFactory.d.ts +17 -31
  95. package/dist/client/apiClientFactory.d.ts.map +1 -1
  96. package/dist/client/apiClientFactory.js +78 -26
  97. package/dist/client/clientFor.d.ts +62 -10
  98. package/dist/client/clientFor.d.ts.map +1 -1
  99. package/dist/client/clientFor.js +9 -1
  100. package/dist/client/errors.d.ts +49 -25
  101. package/dist/client/errors.d.ts.map +1 -1
  102. package/dist/client/errors.js +43 -17
  103. package/dist/client/makeClient.d.ts +360 -30
  104. package/dist/client/makeClient.d.ts.map +1 -1
  105. package/dist/client/makeClient.js +63 -23
  106. package/dist/client.d.ts +2 -1
  107. package/dist/client.d.ts.map +1 -1
  108. package/dist/client.js +2 -1
  109. package/dist/faker.d.ts +1 -1
  110. package/dist/faker.d.ts.map +1 -1
  111. package/dist/http/Request.d.ts +2 -2
  112. package/dist/http/Request.d.ts.map +1 -1
  113. package/dist/http/Request.js +5 -5
  114. package/dist/http/internal/lib.d.ts +1 -1
  115. package/dist/http.d.ts +1 -1
  116. package/dist/ids.d.ts +3 -3
  117. package/dist/ids.d.ts.map +1 -1
  118. package/dist/ids.js +3 -2
  119. package/dist/index.d.ts +5 -8
  120. package/dist/index.d.ts.map +1 -1
  121. package/dist/index.js +6 -8
  122. package/dist/logger.d.ts +1 -1
  123. package/dist/middleware.d.ts +16 -9
  124. package/dist/middleware.d.ts.map +1 -1
  125. package/dist/middleware.js +13 -9
  126. package/dist/rpc/Invalidation.d.ts +845 -0
  127. package/dist/rpc/Invalidation.d.ts.map +1 -0
  128. package/dist/rpc/Invalidation.js +150 -0
  129. package/dist/rpc/MiddlewareMaker.d.ts +5 -4
  130. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  131. package/dist/rpc/MiddlewareMaker.js +26 -27
  132. package/dist/rpc/RpcContextMap.d.ts +3 -3
  133. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  134. package/dist/rpc/RpcContextMap.js +4 -4
  135. package/dist/rpc/RpcMiddleware.d.ts +5 -4
  136. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  137. package/dist/rpc/RpcMiddleware.js +1 -1
  138. package/dist/rpc.d.ts +2 -2
  139. package/dist/rpc.d.ts.map +1 -1
  140. package/dist/rpc.js +2 -2
  141. package/dist/transform.d.ts +1 -1
  142. package/dist/transform.d.ts.map +1 -1
  143. package/dist/transform.js +3 -3
  144. package/dist/utils/effectify.d.ts +1 -1
  145. package/dist/utils/extend.d.ts +1 -1
  146. package/dist/utils/extend.d.ts.map +1 -1
  147. package/dist/utils/gen.d.ts +2 -2
  148. package/dist/utils/gen.d.ts.map +1 -1
  149. package/dist/utils/logLevel.d.ts +2 -2
  150. package/dist/utils/logLevel.d.ts.map +1 -1
  151. package/dist/utils/logger.d.ts +3 -3
  152. package/dist/utils/logger.d.ts.map +1 -1
  153. package/dist/utils/logger.js +3 -3
  154. package/dist/utils.d.ts +30 -10
  155. package/dist/utils.d.ts.map +1 -1
  156. package/dist/utils.js +12 -6
  157. package/dist/validation/validators.d.ts +1 -1
  158. package/dist/validation/validators.d.ts.map +1 -1
  159. package/dist/validation.d.ts +1 -1
  160. package/dist/validation.d.ts.map +1 -1
  161. package/eslint.config.mjs +2 -2
  162. package/package.json +47 -23
  163. package/src/Config/SecretURL.ts +2 -1
  164. package/src/Config.ts +14 -0
  165. package/src/ConfigProvider.ts +48 -0
  166. package/src/{ServiceMap.ts → Context.ts} +52 -59
  167. package/src/Effect.ts +12 -14
  168. package/src/Layer.ts +5 -4
  169. package/src/Pure.ts +17 -18
  170. package/src/Schema/Class.ts +279 -62
  171. package/src/Schema/SchemaParser.ts +12 -0
  172. package/src/Schema/SpecialJsonSchema.ts +137 -0
  173. package/src/Schema/SpecialOpenApi.ts +130 -0
  174. package/src/Schema/brand.ts +9 -1
  175. package/src/Schema/email.ts +7 -2
  176. package/src/Schema/ext.ts +195 -73
  177. package/src/Schema/moreStrings.ts +22 -20
  178. package/src/Schema/numbers.ts +14 -16
  179. package/src/Schema/phoneNumber.ts +5 -1
  180. package/src/Schema/strings.ts +4 -8
  181. package/src/Schema.ts +265 -105
  182. package/src/client/InvalidationKeys.ts +50 -0
  183. package/src/client/apiClientFactory.ts +200 -119
  184. package/src/client/clientFor.ts +105 -23
  185. package/src/client/errors.ts +52 -26
  186. package/src/client/makeClient.ts +339 -63
  187. package/src/client.ts +1 -0
  188. package/src/http/Request.ts +7 -4
  189. package/src/ids.ts +2 -1
  190. package/src/index.ts +5 -10
  191. package/src/middleware.ts +12 -10
  192. package/src/rpc/Invalidation.ts +221 -0
  193. package/src/rpc/MiddlewareMaker.ts +36 -47
  194. package/src/rpc/README.md +2 -2
  195. package/src/rpc/RpcContextMap.ts +6 -5
  196. package/src/rpc/RpcMiddleware.ts +5 -4
  197. package/src/rpc.ts +1 -1
  198. package/src/transform.ts +2 -2
  199. package/src/utils/gen.ts +1 -1
  200. package/src/utils/logger.ts +2 -2
  201. package/src/utils.ts +49 -13
  202. package/test/dist/rpc.test.d.ts.map +1 -1
  203. package/test/dist/secretURL.test.d.ts.map +1 -0
  204. package/test/dist/special.test.d.ts.map +1 -0
  205. package/test/rpc.test.ts +38 -6
  206. package/test/schema.test.ts +591 -17
  207. package/test/secretURL.test.ts +157 -0
  208. package/test/special.test.ts +1023 -0
  209. package/test/utils.test.ts +6 -6
  210. package/tsconfig.base.json +3 -4
  211. package/tsconfig.json +0 -1
  212. package/tsconfig.json.bak +2 -2
  213. package/tsconfig.src.json +29 -29
  214. package/tsconfig.test.json +2 -2
  215. package/dist/ServiceMap.d.ts +0 -44
  216. package/dist/ServiceMap.d.ts.map +0 -1
  217. package/dist/ServiceMap.js +0 -91
  218. package/src/Operations.ts +0 -55
@@ -12,11 +12,16 @@ export type Email = string & EmailBrand
12
12
  export const Email = S
13
13
  .String
14
14
  .pipe(
15
+ S.annotate({
16
+ title: "Email",
17
+ description: "an email according to RFC 5322",
18
+ format: "email"
19
+ }),
20
+ S.check(S.isMinLength(3), /* a@b */ S.isMaxLength(998)),
15
21
  S.refine(isValidEmail as Refinement<string, Email>, {
16
22
  identifier: "Email",
17
- title: "Email",
18
23
  description: "an email according to RFC 5322",
19
- jsonSchema: { format: "email", minLength: 3, /* a@b */ maxLength: 998 }
24
+ jsonSchema: { format: "email", minLength: 3, maxLength: 998 }
20
25
  }),
21
26
  S.annotate({
22
27
  toArbitrary: () => (fc) => fc.emailAddress().map((_) => _ as Email)
package/src/Schema/ext.ts CHANGED
@@ -1,8 +1,10 @@
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
+ import { Effect, Function, Option, pipe, type SchemaAST, SchemaIssue, SchemaTransformation } from "effect"
4
4
  import * as S from "effect/Schema"
5
+ import { isDateValid } from "effect/Schema"
5
6
  import { type NonEmptyReadonlyArray } from "../Array.js"
7
+ import * as Context from "../Context.js"
6
8
  import { extendM, typedKeysOf } from "../utils.js"
7
9
  import { type AST } from "./schema.js"
8
10
 
@@ -13,51 +15,101 @@ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
13
15
  Exclude<Self["EncodingServices"], R>
14
16
  >
15
17
 
16
- // TODO: v4 migration withConstructorDefault signature changed, propertySignature removed
17
- // Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
18
- // because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
19
- // The narrowing assertions below are safe we're asserting "this schema hasn't had a default applied yet".
20
- export const withDefaultConstructor = <A>(
21
- makeDefault: () => NoInfer<A>
22
- ) =>
23
- <Self extends S.Top>(self: Self): S.withConstructorDefault<Self & S.WithoutConstructorDefault> => {
24
- type Narrowed = Self & S.WithoutConstructorDefault
25
- return S.withConstructorDefault<Narrowed>(
26
- () => Option.some(makeDefault() as Narrowed["~type.make.in"])
27
- )(self as Narrowed)
28
- }
18
+ export const DefaultParseOptions: SchemaAST.ParseOptions = { concurrency: "unbounded" }
19
+ /**
20
+ * Parse-options annotation used on schema constructors for decode paths where callers
21
+ * cannot currently pass parse options (notably some RPC / HttpApi integration paths).
22
+ *
23
+ * Keep this annotation in place so those framework-managed decodes still run with
24
+ * unbounded concurrency by default.
25
+ */
26
+ export const concurrencyUnbounded = { parseOptions: DefaultParseOptions } as const
27
+
28
+ type DecodeLike = (schema: any) => (input: any, options?: SchemaAST.ParseOptions) => any
29
+
30
+ export const withDefaultParseOptions = <Decode extends DecodeLike>(
31
+ decode: Decode,
32
+ defaultParseOptions: SchemaAST.ParseOptions = DefaultParseOptions
33
+ ): Decode =>
34
+ ((schema: any) => {
35
+ const run = decode(schema)
36
+ return (input: any, options?: SchemaAST.ParseOptions) => run(input, { ...defaultParseOptions, ...options })
37
+ }) as Decode
29
38
 
30
39
  // 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
- )
40
+
41
+ const DateString = S.String.annotate({
42
+ identifier: "Date",
43
+ description: "a string in ISO 8601 format that will be decoded as a Date",
44
+ format: "date-time"
45
+ })
46
+
47
+ /**
48
+ * Schema type for {@link DateFromString}.
49
+ *
50
+ * @category Schemas
51
+ * @since 4.0.0
52
+ */
53
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
54
+
55
+ /**
56
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
57
+ *
58
+ * Decoding:
59
+ * - A `string` is decoded as a `Date`.
60
+ *
61
+ * Encoding:
62
+ * - A `Date` is encoded as a `string`.
63
+ *
64
+ * @since 4.0.0
65
+ */
66
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
37
67
 
38
68
  /**
39
69
  * Like the default Schema `Date` but from String with `withDefault` => now
40
70
  */
41
71
  export const Date = Object.assign(DateFromString, {
42
- withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
72
+ withDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
73
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
74
+ })
75
+
76
+ /**
77
+ * Like the default Schema `DateValid` but from String with `withDefault` => now
78
+ */
79
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
80
+ withDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
81
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
43
82
  })
44
83
 
45
84
  /**
46
85
  * Like the default Schema `Boolean` but with `withDefault` => false
47
86
  */
48
87
  export const Boolean = Object.assign(S.Boolean, {
49
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
88
+ withDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false))),
89
+ withDecodingDefaultType: S.Boolean.pipe(S.withDecodingDefaultType(Effect.succeed(false)))
50
90
  })
51
91
 
52
92
  /**
93
+ * You probably want to use `Finite` instead of this.
53
94
  * Like the default Schema `Number` but with `withDefault` => 0
54
95
  */
55
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
96
+ export const Number = Object.assign(S.Number, {
97
+ withDefault: S.Number.pipe(S.withConstructorDefault(Effect.succeed(0))),
98
+ withDecodingDefaultType: S.Number.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
99
+ })
100
+
101
+ /**
102
+ * Like the default Schema `Finite` but with `withDefault` => 0
103
+ */
104
+ export const Finite = Object.assign(S.Finite, {
105
+ withDefault: S.Finite.pipe(S.withConstructorDefault(Effect.succeed(0))),
106
+ withDecodingDefaultType: S.Finite.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
107
+ })
56
108
 
57
109
  /**
58
- * Like the default Schema `Literal` but with `withDefault` => literals[0]
110
+ * Like the default Schema `Literals` but with `withDefault` => literals[0]
59
111
  */
60
- export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(...literals: Literals) =>
112
+ export const Literals = <const Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(literals: Literals) =>
61
113
  pipe(
62
114
  S.Literals(literals),
63
115
  (s) =>
@@ -65,11 +117,14 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
65
117
  changeDefault: <A extends Literals[number]>(a: A) => {
66
118
  return Object.assign(S.Literals(literals), {
67
119
  Default: a,
68
- withDefault: s.pipe(withDefaultConstructor(() => a))
120
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a))),
121
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(a)))
69
122
  }) // todo: copy annotations from original?
70
123
  },
71
- Default: literals[0] as typeof literals[0],
72
- withDefault: s.pipe(withDefaultConstructor(() => literals[0]))
124
+ // 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
125
+ Default: literals[0] as Literals[0],
126
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(literals[0]))),
127
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(literals[0])))
73
128
  })
74
129
  )
75
130
 
@@ -78,43 +133,96 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
78
133
  */
79
134
  export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
80
135
  return pipe(
81
- S.Array(value),
82
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
136
+ S.Array(value).annotate(concurrencyUnbounded),
137
+ (s) =>
138
+ Object.assign(s, {
139
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => []))),
140
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => [])))
141
+ })
83
142
  )
84
143
  }
85
144
 
86
145
  /**
87
- * Like the default Schema `Map` but with `withDefault` => []
146
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
88
147
  */
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())) })
148
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
149
+ const from = S
150
+ .Array(value)
151
+ .annotate({ ...concurrencyUnbounded, expected: "an array of unique items that will be decoded as a ReadonlySet" })
152
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
153
+ const schema = from.pipe(
154
+ S.decodeTo(
155
+ to,
156
+ SchemaTransformation.transform({
157
+ decode: (arr) => new Set(arr) as ReadonlySet<S.Schema.Type<ValueSchema>>,
158
+ encode: (set) => [...set]
159
+ })
160
+ )
93
161
  )
162
+ return schema
94
163
  }
95
164
 
96
- export { Map_ as Map }
165
+ /**
166
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
167
+ */
168
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
169
+ readonly key: KeySchema
170
+ readonly value: ValueSchema
171
+ }) => {
172
+ const from = S
173
+ .Array(S.Tuple([pair.key, pair.value]))
174
+ .annotate({
175
+ ...concurrencyUnbounded,
176
+ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap"
177
+ })
178
+ const to = S.instanceOf(Map) as S.instanceOf<
179
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
180
+ >
181
+ const schema = from.pipe(
182
+ S.decodeTo(
183
+ to,
184
+ SchemaTransformation.transform({
185
+ decode: (
186
+ arr
187
+ ) => new Map(arr) as ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>,
188
+ encode: (
189
+ map
190
+ ) => [...map.entries()] as any // fu
191
+ })
192
+ )
193
+ )
194
+ return schema
195
+ }
97
196
 
98
197
  /**
99
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
198
+ * Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
100
199
  */
101
200
  export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
102
201
  pipe(
103
- S.ReadonlySet(value),
202
+ ReadonlySetFromArray(value),
104
203
  (s) =>
105
- Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
204
+ Object.assign(s, {
205
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))),
206
+ withDecodingDefaultType: s.pipe(
207
+ S.withDecodingDefaultType(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))
208
+ )
209
+ })
106
210
  )
107
211
 
108
212
  /**
109
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
213
+ * Like the default Schema `ReadonlyMap` but from Array with `withDefault` => new Map()
110
214
  */
111
215
  export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
112
216
  readonly key: KeySchema
113
217
  readonly value: ValueSchema
114
218
  }) =>
115
219
  pipe(
116
- S.ReadonlyMap(pair.key, pair.value),
117
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
220
+ ReadonlyMapFromArray(pair),
221
+ (s) =>
222
+ Object.assign(s, {
223
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Map()))),
224
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new Map())))
225
+ })
118
226
  )
119
227
 
120
228
  /**
@@ -123,21 +231,30 @@ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(
123
231
  export const NullOr = <Schema extends S.Top>(self: Schema) =>
124
232
  pipe(
125
233
  S.NullOr(self),
126
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
234
+ (s) =>
235
+ Object.assign(s, {
236
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(null))),
237
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(null)))
238
+ })
127
239
  )
128
240
 
129
- export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
130
- schema.pipe(withDefaultConstructor(() => new global.Date()))
241
+ export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
242
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
131
243
 
132
- export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
244
+ export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
245
+ schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
133
246
 
134
- export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
247
+ export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
248
+ schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
135
249
 
136
- export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
250
+ export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
251
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
137
252
 
138
- export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
253
+ export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
254
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
139
255
 
140
- export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
256
+ export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
257
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
141
258
 
142
259
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
143
260
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -167,8 +284,11 @@ export type WithDefaults<Self extends S.Top> = (
167
284
  // : never
168
285
 
169
286
  export const inputDate = extendM(
170
- S.Union([S.DateValid, S.Date]),
171
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
287
+ S.Union([S.DateValid, Date]),
288
+ (s) => ({
289
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))),
290
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new globalThis.Date())))
291
+ })
172
292
  )
173
293
 
174
294
  export interface UnionBrand {}
@@ -218,7 +338,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
218
338
  { message: "One way schema transformation, encoding is not allowed" }
219
339
  )
220
340
  )
221
- }) as any
341
+ })
222
342
  )
223
343
  )
224
344
 
@@ -235,7 +355,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
235
355
  S.decodeTo(
236
356
  to,
237
357
  SchemaTransformation.transformOrFail({
238
- decode: decode as any,
358
+ decode,
239
359
  encode: (i: any) =>
240
360
  Effect.fail(
241
361
  new SchemaIssue.Forbidden(
@@ -243,34 +363,36 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
243
363
  { message: "One way schema transformation, encoding is not allowed" }
244
364
  )
245
365
  )
246
- }) as any
366
+ })
247
367
  )
248
368
  )
249
369
 
250
- export const provide = <Self extends S.Top, R>(
251
- self: Self,
252
- context: ServiceMap.ServiceMap<R>
253
- ): ProvidedCodec<Self, R> => {
370
+ export const provide: {
371
+ <R>(context: Context.Context<R>): <Self extends S.Top>(self: Self) => ProvidedCodec<Self, R>
372
+ <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R>
373
+ } = Function.dual(2, <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R> => {
254
374
  const prov = Effect.provide(context)
255
375
  return self.pipe(
256
376
  S.middlewareDecoding((effect) => prov(effect)),
257
377
  S.middlewareEncoding((effect) => prov(effect))
258
- ) as ProvidedCodec<Self, R>
259
- }
260
- export const contextFromServices = <
378
+ )
379
+ })
380
+ export const contextFromServices = Effect.fnUntraced(function*<
381
+ Self extends S.Top,
382
+ Tags extends ReadonlyArray<Context.Key<any, any>>
383
+ >(self: Self, ...services: Tags) {
384
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
385
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
386
+ )
387
+ return provide(self, context)
388
+ }) as <
261
389
  Self extends S.Top,
262
- Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
390
+ Tags extends ReadonlyArray<Context.Key<any, any>>
263
391
  >(
264
392
  self: Self,
265
393
  ...services: Tags
266
- ): Effect.Effect<
267
- ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
394
+ ) => Effect.Effect<
395
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
268
396
  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
- })
397
+ Context.Service.Identifier<Tags[number]>
398
+ >
@@ -1,4 +1,4 @@
1
- import { pipe } from "effect"
1
+ import { Effect, pipe } from "effect"
2
2
  import type { Refinement } from "effect-app/Function"
3
3
  import { extendM } from "effect-app/utils"
4
4
  import * as S from "effect/Schema"
@@ -6,7 +6,7 @@ import type { Simplify } from "effect/Types"
6
6
  import { customRandom, nanoid, urlAlphabet } from "nanoid"
7
7
  import validator from "validator"
8
8
  import { fromBrand, nominal } from "./brand.js"
9
- import { withDefaultConstructor, withDefaultMake, type WithDefaults } from "./ext.js"
9
+ import { withDefaultMake, type WithDefaults } from "./ext.js"
10
10
  import { type B } from "./schema.js"
11
11
  import type { NonEmptyString255Brand, NonEmptyStringBrand } from "./strings.js"
12
12
 
@@ -27,9 +27,8 @@ export type NonEmptyString50 = string & NonEmptyString50Brand
27
27
  */
28
28
  export const NonEmptyString50 = nonEmptyString.pipe(
29
29
  S.check(S.isMaxLength(50)),
30
- fromBrand(nominal<NonEmptyString50>(), {
30
+ fromBrand<NonEmptyString50>(nominal<NonEmptyString50>(), {
31
31
  identifier: "NonEmptyString50",
32
- title: "NonEmptyString50",
33
32
  jsonSchema: {}
34
33
  }),
35
34
  withDefaultMake
@@ -50,9 +49,8 @@ export type NonEmptyString64 = string & NonEmptyString64Brand
50
49
  */
51
50
  export const NonEmptyString64 = nonEmptyString.pipe(
52
51
  S.check(S.isMaxLength(64)),
53
- fromBrand(nominal<NonEmptyString64>(), {
52
+ fromBrand<NonEmptyString64>(nominal<NonEmptyString64>(), {
54
53
  identifier: "NonEmptyString64",
55
- title: "NonEmptyString64",
56
54
  jsonSchema: {}
57
55
  }),
58
56
  withDefaultMake
@@ -74,9 +72,8 @@ export type NonEmptyString80 = string & NonEmptyString80Brand
74
72
 
75
73
  export const NonEmptyString80 = nonEmptyString.pipe(
76
74
  S.check(S.isMaxLength(80)),
77
- fromBrand(nominal<NonEmptyString80>(), {
75
+ fromBrand<NonEmptyString80>(nominal<NonEmptyString80>(), {
78
76
  identifier: "NonEmptyString80",
79
- title: "NonEmptyString80",
80
77
  jsonSchema: {}
81
78
  }),
82
79
  withDefaultMake
@@ -97,9 +94,8 @@ export type NonEmptyString100 = string & NonEmptyString100Brand
97
94
  */
98
95
  export const NonEmptyString100 = nonEmptyString.pipe(
99
96
  S.check(S.isMaxLength(100)),
100
- fromBrand(nominal<NonEmptyString100>(), {
97
+ fromBrand<NonEmptyString100>(nominal<NonEmptyString100>(), {
101
98
  identifier: "NonEmptyString100",
102
- title: "NonEmptyString100",
103
99
  jsonSchema: {}
104
100
  }),
105
101
  withDefaultMake
@@ -121,7 +117,10 @@ export type Min3String255 = string & Min3String255Brand
121
117
  export const Min3String255 = pipe(
122
118
  S.String,
123
119
  S.check(S.isMinLength(3), S.isMaxLength(255)),
124
- fromBrand(nominal<Min3String255>(), { identifier: "Min3String255", title: "Min3String255", jsonSchema: {} }),
120
+ fromBrand<Min3String255>(nominal<Min3String255>(), {
121
+ identifier: "Min3String255",
122
+ jsonSchema: {}
123
+ }),
125
124
  withDefaultMake
126
125
  )
127
126
 
@@ -151,16 +150,15 @@ export const StringId = extendM(
151
150
  pipe(
152
151
  S.String,
153
152
  S.check(S.isMinLength(minLength), S.isMaxLength(maxLength)),
154
- fromBrand(nominal<StringId>(), {
153
+ fromBrand<StringId>(nominal<StringId>(), {
155
154
  identifier: "StringId",
156
- title: "StringId",
157
155
  toArbitrary: () => (fc) => StringIdArb()(fc),
158
156
  jsonSchema: {}
159
157
  })
160
158
  ),
161
159
  (s) => ({
162
160
  make: makeStringId,
163
- withDefault: s.pipe(withDefaultConstructor(makeStringId))
161
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(makeStringId)))
164
162
  })
165
163
  )
166
164
  .pipe(withDefaultMake)
@@ -182,16 +180,15 @@ export function prefixedStringId<Brand extends StringId>() {
182
180
  (x) => (pref + x.substring(0, 50 - pref.length)) as Brand
183
181
  )
184
182
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
185
- const s: S.Codec<string & Brand, string> = StringId
183
+ const s = StringId
186
184
  .pipe(
187
185
  S.refine((x: string): x is string & Brand => x.startsWith(pref), {
188
- identifier: name,
189
- title: name
186
+ identifier: name
190
187
  }),
191
188
  S.annotate({
192
189
  toArbitrary: () => (fc) => arb()(fc)
193
190
  })
194
- ) as S.Codec<string & Brand, string>
191
+ )
195
192
  const schema = s.pipe(withDefaultMake)
196
193
  const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Brand
197
194
 
@@ -208,7 +205,9 @@ export function prefixedStringId<Brand extends StringId>() {
208
205
  */
209
206
  prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => ex(str),
210
207
  prefix,
211
- withDefault: schema.pipe(withDefaultConstructor(make))
208
+ withDefault: schema.pipe(S.withConstructorDefault<S.Codec<Brand, string> & S.WithoutConstructorDefault>(
209
+ Effect.sync(make)
210
+ ))
212
211
  })
213
212
  )
214
213
  }
@@ -247,9 +246,12 @@ const isUrl: Refinement<string, Url> = (s: string): s is Url => {
247
246
  export const Url = S
248
247
  .String
249
248
  .pipe(
249
+ S.annotate({
250
+ title: "Url",
251
+ format: "uri"
252
+ }),
250
253
  S.refine(isUrl, {
251
254
  identifier: "Url",
252
- title: "Url",
253
255
  jsonSchema: { format: "uri" }
254
256
  }),
255
257
  S.annotate({
@@ -1,8 +1,9 @@
1
+ import { Effect } from "effect"
1
2
  import { extendM } from "effect-app/utils"
2
3
  import * as S from "effect/Schema"
3
4
  import type { Simplify } from "effect/Types"
4
5
  import { fromBrand, nominal } from "./brand.js"
5
- import { withDefaultConstructor, withDefaultMake } from "./ext.js"
6
+ import { withDefaultMake } from "./ext.js"
6
7
  import { type B } from "./schema.js"
7
8
 
8
9
  export interface PositiveIntBrand
@@ -11,10 +12,10 @@ export interface PositiveIntBrand
11
12
  export const PositiveInt = extendM(
12
13
  S.Int.pipe(
13
14
  S.check(S.isGreaterThan(0)),
14
- fromBrand(nominal<PositiveInt>(), { identifier: "PositiveInt", title: "PositiveInt", jsonSchema: {} }),
15
+ fromBrand<PositiveInt>(nominal<PositiveInt>(), { identifier: "PositiveInt", jsonSchema: {} }),
15
16
  withDefaultMake
16
17
  ),
17
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(1))) })
18
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
18
19
  )
19
20
  export type PositiveInt = number & PositiveIntBrand
20
21
 
@@ -22,53 +23,50 @@ export interface NonNegativeIntBrand extends Simplify<B.Brand<"NonNegativeInt">
22
23
  export const NonNegativeInt = extendM(
23
24
  S.Int.pipe(
24
25
  S.check(S.isGreaterThanOrEqualTo(0)),
25
- fromBrand(nominal<NonNegativeInt>(), {
26
+ fromBrand<NonNegativeInt>(nominal<NonNegativeInt>(), {
26
27
  identifier: "NonNegativeInt",
27
- title: "NonNegativeInt",
28
28
  jsonSchema: {}
29
29
  }),
30
30
  withDefaultMake
31
31
  ),
32
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
32
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
33
33
  )
34
34
  export type NonNegativeInt = number & NonNegativeIntBrand
35
35
 
36
36
  export interface IntBrand extends Simplify<B.Brand<"Int">> {}
37
37
  export const Int = extendM(
38
- S.Int.pipe(fromBrand(nominal<Int>(), { identifier: "Int", title: "Int", jsonSchema: {} }), withDefaultMake),
39
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
38
+ S.Int.pipe(fromBrand<Int>(nominal<Int>(), { identifier: "Int", jsonSchema: {} }), withDefaultMake),
39
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
40
40
  )
41
41
  export type Int = number & IntBrand
42
42
 
43
43
  export interface PositiveNumberBrand extends Simplify<B.Brand<"PositiveNumber"> & NonNegativeNumberBrand> {}
44
44
  export const PositiveNumber = extendM(
45
- S.Number.pipe(
45
+ S.Finite.pipe(
46
46
  S.check(S.isGreaterThan(0)),
47
- fromBrand(nominal<PositiveNumber>(), {
47
+ fromBrand<PositiveNumber>(nominal<PositiveNumber>(), {
48
48
  identifier: "PositiveNumber",
49
- title: "PositiveNumber",
50
49
  jsonSchema: {}
51
50
  }),
52
51
  withDefaultMake
53
52
  ),
54
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(1))) })
53
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
55
54
  )
56
55
  export type PositiveNumber = number & PositiveNumberBrand
57
56
 
58
57
  export interface NonNegativeNumberBrand extends Simplify<B.Brand<"NonNegativeNumber">> {}
59
58
  export const NonNegativeNumber = extendM(
60
59
  S
61
- .Number
60
+ .Finite
62
61
  .pipe(
63
62
  S.check(S.isGreaterThanOrEqualTo(0)),
64
- fromBrand(nominal<NonNegativeNumber>(), {
63
+ fromBrand<NonNegativeNumber>(nominal<NonNegativeNumber>(), {
65
64
  identifier: "NonNegativeNumber",
66
- title: "NonNegativeNumber",
67
65
  jsonSchema: {}
68
66
  }),
69
67
  withDefaultMake
70
68
  ),
71
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
69
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
72
70
  )
73
71
  export type NonNegativeNumber = number & NonNegativeNumberBrand
74
72
 
@@ -13,9 +13,13 @@ export type PhoneNumber = string & PhoneNumberBrand
13
13
  export const PhoneNumber = S
14
14
  .String
15
15
  .pipe(
16
+ S.annotate({
17
+ title: "PhoneNumber",
18
+ description: "a phone number with at least 7 digits",
19
+ format: "phone"
20
+ }),
16
21
  S.refine(isValidPhone as Refinement<string, PhoneNumber>, {
17
22
  identifier: "PhoneNumber",
18
- title: "PhoneNumber",
19
23
  description: "a phone number with at least 7 digits",
20
24
  jsonSchema: { format: "phone" }
21
25
  }),