effect-app 4.0.0-beta.21 → 4.0.0-beta.211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/CHANGELOG.md +954 -0
  2. package/dist/Array.d.ts +1 -1
  3. package/dist/Chunk.d.ts +1 -1
  4. package/dist/Chunk.d.ts.map +1 -1
  5. package/dist/Config/SecretURL.d.ts +1 -1
  6. package/dist/Config/SecretURL.d.ts.map +1 -1
  7. package/dist/Config/SecretURL.js +2 -2
  8. package/dist/Config/internal/configSecretURL.d.ts +1 -1
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config.d.ts +7 -0
  11. package/dist/Config.d.ts.map +1 -0
  12. package/dist/Config.js +6 -0
  13. package/dist/ConfigProvider.d.ts +39 -0
  14. package/dist/ConfigProvider.d.ts.map +1 -0
  15. package/dist/ConfigProvider.js +42 -0
  16. package/dist/Context.d.ts +40 -0
  17. package/dist/Context.d.ts.map +1 -0
  18. package/dist/Context.js +67 -0
  19. package/dist/Effect.d.ts +9 -10
  20. package/dist/Effect.d.ts.map +1 -1
  21. package/dist/Effect.js +3 -6
  22. package/dist/Function.d.ts +1 -1
  23. package/dist/Function.d.ts.map +1 -1
  24. package/dist/Inputify.type.d.ts +1 -1
  25. package/dist/Layer.d.ts +7 -6
  26. package/dist/Layer.d.ts.map +1 -1
  27. package/dist/Layer.js +1 -1
  28. package/dist/NonEmptySet.d.ts +1 -1
  29. package/dist/NonEmptySet.d.ts.map +1 -1
  30. package/dist/Option.d.ts +1 -1
  31. package/dist/Option.d.ts.map +1 -1
  32. package/dist/Pure.d.ts +5 -5
  33. package/dist/Pure.d.ts.map +1 -1
  34. package/dist/Pure.js +13 -13
  35. package/dist/Schema/Class.d.ts +66 -20
  36. package/dist/Schema/Class.d.ts.map +1 -1
  37. package/dist/Schema/Class.js +189 -22
  38. package/dist/Schema/FastCheck.d.ts +1 -1
  39. package/dist/Schema/FastCheck.d.ts.map +1 -1
  40. package/dist/Schema/Methods.d.ts +1 -1
  41. package/dist/Schema/SchemaParser.d.ts +5 -0
  42. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  43. package/dist/Schema/SchemaParser.js +6 -0
  44. package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
  45. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  46. package/dist/Schema/SpecialJsonSchema.js +122 -0
  47. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  48. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  49. package/dist/Schema/SpecialOpenApi.js +123 -0
  50. package/dist/Schema/brand.d.ts +4 -2
  51. package/dist/Schema/brand.d.ts.map +1 -1
  52. package/dist/Schema/brand.js +1 -1
  53. package/dist/Schema/email.d.ts +1 -1
  54. package/dist/Schema/email.d.ts.map +1 -1
  55. package/dist/Schema/email.js +7 -4
  56. package/dist/Schema/ext.d.ts +117 -45
  57. package/dist/Schema/ext.d.ts.map +1 -1
  58. package/dist/Schema/ext.js +131 -42
  59. package/dist/Schema/moreStrings.d.ts +37 -25
  60. package/dist/Schema/moreStrings.d.ts.map +1 -1
  61. package/dist/Schema/moreStrings.js +15 -16
  62. package/dist/Schema/numbers.d.ts +15 -15
  63. package/dist/Schema/numbers.d.ts.map +1 -1
  64. package/dist/Schema/numbers.js +10 -12
  65. package/dist/Schema/phoneNumber.d.ts +1 -1
  66. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  67. package/dist/Schema/phoneNumber.js +6 -3
  68. package/dist/Schema/schema.d.ts +1 -1
  69. package/dist/Schema/strings.d.ts +5 -5
  70. package/dist/Schema/strings.d.ts.map +1 -1
  71. package/dist/Schema/strings.js +1 -5
  72. package/dist/Schema.d.ts +147 -15
  73. package/dist/Schema.d.ts.map +1 -1
  74. package/dist/Schema.js +131 -16
  75. package/dist/Set.d.ts +1 -1
  76. package/dist/Set.d.ts.map +1 -1
  77. package/dist/TypeTest.d.ts +1 -1
  78. package/dist/Types.d.ts +1 -1
  79. package/dist/Widen.type.d.ts +1 -1
  80. package/dist/_ext/Array.d.ts +1 -1
  81. package/dist/_ext/Array.d.ts.map +1 -1
  82. package/dist/_ext/date.d.ts +1 -1
  83. package/dist/_ext/misc.d.ts +1 -1
  84. package/dist/_ext/ord.ext.d.ts +1 -1
  85. package/dist/_ext/ord.ext.d.ts.map +1 -1
  86. package/dist/builtin.d.ts +1 -1
  87. package/dist/builtin.d.ts.map +1 -1
  88. package/dist/client/InvalidationKeys.d.ts +29 -0
  89. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  90. package/dist/client/InvalidationKeys.js +33 -0
  91. package/dist/client/apiClientFactory.d.ts +20 -32
  92. package/dist/client/apiClientFactory.d.ts.map +1 -1
  93. package/dist/client/apiClientFactory.js +95 -32
  94. package/dist/client/clientFor.d.ts +51 -17
  95. package/dist/client/clientFor.d.ts.map +1 -1
  96. package/dist/client/clientFor.js +9 -1
  97. package/dist/client/errors.d.ts +49 -25
  98. package/dist/client/errors.d.ts.map +1 -1
  99. package/dist/client/errors.js +43 -17
  100. package/dist/client/makeClient.d.ts +481 -33
  101. package/dist/client/makeClient.d.ts.map +1 -1
  102. package/dist/client/makeClient.js +66 -24
  103. package/dist/client.d.ts +2 -1
  104. package/dist/client.d.ts.map +1 -1
  105. package/dist/client.js +2 -1
  106. package/dist/faker.d.ts +1 -1
  107. package/dist/faker.d.ts.map +1 -1
  108. package/dist/http/Request.d.ts +2 -2
  109. package/dist/http/Request.d.ts.map +1 -1
  110. package/dist/http/internal/lib.d.ts +1 -1
  111. package/dist/http.d.ts +1 -1
  112. package/dist/ids.d.ts +12 -12
  113. package/dist/ids.d.ts.map +1 -1
  114. package/dist/ids.js +3 -2
  115. package/dist/index.d.ts +5 -8
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +6 -8
  118. package/dist/logger.d.ts +1 -1
  119. package/dist/middleware.d.ts +14 -8
  120. package/dist/middleware.d.ts.map +1 -1
  121. package/dist/middleware.js +14 -8
  122. package/dist/rpc/Invalidation.d.ts +402 -0
  123. package/dist/rpc/Invalidation.d.ts.map +1 -0
  124. package/dist/rpc/Invalidation.js +150 -0
  125. package/dist/rpc/MiddlewareMaker.d.ts +5 -4
  126. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  127. package/dist/rpc/MiddlewareMaker.js +57 -37
  128. package/dist/rpc/RpcContextMap.d.ts +3 -3
  129. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  130. package/dist/rpc/RpcContextMap.js +4 -4
  131. package/dist/rpc/RpcMiddleware.d.ts +5 -4
  132. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  133. package/dist/rpc/RpcMiddleware.js +1 -1
  134. package/dist/rpc.d.ts +2 -2
  135. package/dist/rpc.d.ts.map +1 -1
  136. package/dist/rpc.js +2 -2
  137. package/dist/transform.d.ts +1 -1
  138. package/dist/transform.d.ts.map +1 -1
  139. package/dist/transform.js +3 -3
  140. package/dist/utils/effectify.d.ts +1 -1
  141. package/dist/utils/extend.d.ts +1 -1
  142. package/dist/utils/extend.d.ts.map +1 -1
  143. package/dist/utils/gen.d.ts +2 -2
  144. package/dist/utils/gen.d.ts.map +1 -1
  145. package/dist/utils/logLevel.d.ts +2 -2
  146. package/dist/utils/logLevel.d.ts.map +1 -1
  147. package/dist/utils/logger.d.ts +3 -3
  148. package/dist/utils/logger.d.ts.map +1 -1
  149. package/dist/utils/logger.js +3 -3
  150. package/dist/utils.d.ts +31 -38
  151. package/dist/utils.d.ts.map +1 -1
  152. package/dist/utils.js +12 -25
  153. package/dist/validation/validators.d.ts +1 -1
  154. package/dist/validation/validators.d.ts.map +1 -1
  155. package/dist/validation.d.ts +1 -1
  156. package/dist/validation.d.ts.map +1 -1
  157. package/package.json +46 -24
  158. package/src/Config/SecretURL.ts +2 -1
  159. package/src/Config.ts +14 -0
  160. package/src/ConfigProvider.ts +48 -0
  161. package/src/{ServiceMap.ts → Context.ts} +52 -59
  162. package/src/Effect.ts +12 -14
  163. package/src/Layer.ts +6 -5
  164. package/src/Pure.ts +17 -18
  165. package/src/Schema/Class.ts +268 -62
  166. package/src/Schema/SchemaParser.ts +12 -0
  167. package/src/Schema/SpecialJsonSchema.ts +137 -0
  168. package/src/Schema/SpecialOpenApi.ts +130 -0
  169. package/src/Schema/brand.ts +21 -1
  170. package/src/Schema/email.ts +7 -2
  171. package/src/Schema/ext.ts +204 -72
  172. package/src/Schema/moreStrings.ts +40 -37
  173. package/src/Schema/numbers.ts +14 -16
  174. package/src/Schema/phoneNumber.ts +5 -1
  175. package/src/Schema/strings.ts +4 -8
  176. package/src/Schema.ts +314 -20
  177. package/src/client/InvalidationKeys.ts +50 -0
  178. package/src/client/apiClientFactory.ts +223 -129
  179. package/src/client/clientFor.ts +95 -29
  180. package/src/client/errors.ts +52 -26
  181. package/src/client/makeClient.ts +572 -71
  182. package/src/client.ts +1 -0
  183. package/src/ids.ts +3 -2
  184. package/src/index.ts +5 -10
  185. package/src/middleware.ts +13 -9
  186. package/src/rpc/Invalidation.ts +226 -0
  187. package/src/rpc/MiddlewareMaker.ts +65 -60
  188. package/src/rpc/README.md +2 -2
  189. package/src/rpc/RpcContextMap.ts +6 -5
  190. package/src/rpc/RpcMiddleware.ts +5 -4
  191. package/src/rpc.ts +1 -1
  192. package/src/transform.ts +2 -2
  193. package/src/utils/gen.ts +1 -1
  194. package/src/utils/logger.ts +2 -2
  195. package/src/utils.ts +50 -132
  196. package/test/dist/rpc.test.d.ts.map +1 -1
  197. package/test/dist/secretURL.test.d.ts.map +1 -0
  198. package/test/dist/special.test.d.ts.map +1 -0
  199. package/test/dist/stream-error.types.d.ts +2 -0
  200. package/test/dist/stream-error.types.d.ts.map +1 -0
  201. package/test/dist/stream-error.types.js +27 -0
  202. package/test/rpc.test.ts +45 -6
  203. package/test/schema.test.ts +581 -7
  204. package/test/secretURL.test.ts +157 -0
  205. package/test/special.test.ts +1023 -0
  206. package/test/utils.test.ts +6 -6
  207. package/tsconfig.base.json +3 -4
  208. package/tsconfig.json +0 -1
  209. package/tsconfig.json.bak +2 -2
  210. package/tsconfig.src.json +29 -29
  211. package/tsconfig.test.json +2 -2
  212. package/dist/Operations.d.ts +0 -123
  213. package/dist/Operations.d.ts.map +0 -1
  214. package/dist/Operations.js +0 -29
  215. package/dist/ServiceMap.d.ts +0 -44
  216. package/dist/ServiceMap.d.ts.map +0 -1
  217. package/dist/ServiceMap.js +0 -91
  218. package/eslint.config.mjs +0 -26
  219. package/src/Operations.ts +0 -55
@@ -12,11 +12,16 @@ export type Email = string & EmailBrand
12
12
  export const Email = S
13
13
  .String
14
14
  .pipe(
15
+ S.annotate({
16
+ title: "Email",
17
+ description: "an email according to RFC 5322",
18
+ format: "email"
19
+ }),
20
+ S.check(S.isMinLength(3), /* a@b */ S.isMaxLength(998)),
15
21
  S.refine(isValidEmail as Refinement<string, Email>, {
16
22
  identifier: "Email",
17
- title: "Email",
18
23
  description: "an email according to RFC 5322",
19
- jsonSchema: { format: "email", minLength: 3, /* a@b */ maxLength: 998 }
24
+ jsonSchema: { format: "email", minLength: 3, maxLength: 998 }
20
25
  }),
21
26
  S.annotate({
22
27
  toArbitrary: () => (fc) => fc.emailAddress().map((_) => _ as Email)
package/src/Schema/ext.ts CHANGED
@@ -1,8 +1,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 { Config, 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,111 @@ 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)
18
+ const concurrencySetting = Effect.runSync(
19
+ Config
20
+ .literal("unbounded", "SCHEMA_CONCURRENCY")
21
+ .pipe(Config.orElse(() => Config.number("SCHEMA_CONCURRENCY")), Config.option)
22
+ .asEffect()
23
+ )
24
+
25
+ export const DefaultParseOptions: SchemaAST.ParseOptions = {
26
+ concurrency: Option.getOrElse(concurrencySetting, () => "unbounded" as const)
28
27
  }
29
28
 
29
+ /**
30
+ * Parse-options annotation used on schema constructors for decode paths where callers
31
+ * cannot currently pass parse options (notably some RPC / HttpApi integration paths).
32
+ *
33
+ * Keep this annotation in place so those framework-managed decodes still run with
34
+ * unbounded concurrency by default.
35
+ */
36
+ export const concurrencyUnbounded = { parseOptions: DefaultParseOptions } as const
37
+
38
+ type DecodeLike = (schema: any) => (input: any, options?: SchemaAST.ParseOptions) => any
39
+
40
+ export const withDefaultParseOptions = <Decode extends DecodeLike>(
41
+ decode: Decode,
42
+ defaultParseOptions: SchemaAST.ParseOptions = DefaultParseOptions
43
+ ): Decode =>
44
+ ((schema: any) => {
45
+ const run = decode(schema)
46
+ return (input: any, options?: SchemaAST.ParseOptions) => run(input, { ...defaultParseOptions, ...options })
47
+ }) as Decode
48
+
30
49
  // 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
- )
50
+
51
+ const DateString = S.String.annotate({
52
+ identifier: "Date",
53
+ description: "a string in ISO 8601 format that will be decoded as a Date",
54
+ format: "date-time"
55
+ })
56
+
57
+ /**
58
+ * Schema type for {@link DateFromString}.
59
+ *
60
+ * @category Schemas
61
+ * @since 4.0.0
62
+ */
63
+ export interface DateFromString extends S.decodeTo<S.Date, S.String> {}
64
+
65
+ /**
66
+ * A transformation schema that parses an ISO 8601 string into a `Date`.
67
+ *
68
+ * Decoding:
69
+ * - A `string` is decoded as a `Date`.
70
+ *
71
+ * Encoding:
72
+ * - A `Date` is encoded as a `string`.
73
+ *
74
+ * @since 4.0.0
75
+ */
76
+ export const DateFromString: DateFromString = DateString.pipe(S.decodeTo(S.Date, SchemaTransformation.dateFromString))
37
77
 
38
78
  /**
39
79
  * Like the default Schema `Date` but from String with `withDefault` => now
40
80
  */
41
81
  export const Date = Object.assign(DateFromString, {
42
- withDefault: DateFromString.pipe(withDefaultConstructor(() => new global.Date()))
82
+ withDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
83
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
84
+ })
85
+
86
+ /**
87
+ * Like the default Schema `DateValid` but from String with `withDefault` => now
88
+ */
89
+ export const DateValid = Object.assign(Date.check(isDateValid()), {
90
+ withDefault: DateFromString.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date()))),
91
+ withDecodingDefaultType: DateFromString.pipe(S.withDecodingDefaultType(Effect.sync(() => new global.Date())))
43
92
  })
44
93
 
45
94
  /**
46
95
  * Like the default Schema `Boolean` but with `withDefault` => false
47
96
  */
48
97
  export const Boolean = Object.assign(S.Boolean, {
49
- withDefault: S.Boolean.pipe(withDefaultConstructor(() => false))
98
+ withDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false))),
99
+ withDecodingDefaultType: S.Boolean.pipe(S.withDecodingDefaultType(Effect.succeed(false)))
50
100
  })
51
101
 
52
102
  /**
103
+ * You probably want to use `Finite` instead of this.
53
104
  * Like the default Schema `Number` but with `withDefault` => 0
54
105
  */
55
- export const Number = Object.assign(S.Number, { withDefault: S.Number.pipe(withDefaultConstructor(() => 0)) })
106
+ export const Number = Object.assign(S.Number, {
107
+ withDefault: S.Number.pipe(S.withConstructorDefault(Effect.succeed(0))),
108
+ withDecodingDefaultType: S.Number.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
109
+ })
110
+
111
+ /**
112
+ * Like the default Schema `Finite` but with `withDefault` => 0
113
+ */
114
+ export const Finite = Object.assign(S.Finite, {
115
+ withDefault: S.Finite.pipe(S.withConstructorDefault(Effect.succeed(0))),
116
+ withDecodingDefaultType: S.Finite.pipe(S.withDecodingDefaultType(Effect.succeed(0)))
117
+ })
56
118
 
57
119
  /**
58
- * Like the default Schema `Literal` but with `withDefault` => literals[0]
120
+ * Like the default Schema `Literals` but with `withDefault` => literals[0]
59
121
  */
60
- export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(...literals: Literals) =>
122
+ export const Literals = <const Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(literals: Literals) =>
61
123
  pipe(
62
124
  S.Literals(literals),
63
125
  (s) =>
@@ -65,11 +127,14 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
65
127
  changeDefault: <A extends Literals[number]>(a: A) => {
66
128
  return Object.assign(S.Literals(literals), {
67
129
  Default: a,
68
- withDefault: s.pipe(withDefaultConstructor(() => a))
130
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a))),
131
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(a)))
69
132
  }) // todo: copy annotations from original?
70
133
  },
71
- Default: literals[0] as typeof literals[0],
72
- withDefault: s.pipe(withDefaultConstructor(() => literals[0]))
134
+ // 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
135
+ Default: literals[0] as Literals[0],
136
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(literals[0]))),
137
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(literals[0])))
73
138
  })
74
139
  )
75
140
 
@@ -78,43 +143,96 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
78
143
  */
79
144
  export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
80
145
  return pipe(
81
- S.Array(value),
82
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
146
+ S.Array(value).annotate(concurrencyUnbounded),
147
+ (s) =>
148
+ Object.assign(s, {
149
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => []))),
150
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => [])))
151
+ })
83
152
  )
84
153
  }
85
154
 
86
155
  /**
87
- * Like the default Schema `Map` but with `withDefault` => []
156
+ * An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
88
157
  */
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())) })
158
+ export const ReadonlySetFromArray = <ValueSchema extends S.Top>(value: ValueSchema) => {
159
+ const from = S
160
+ .Array(value)
161
+ .annotate({ ...concurrencyUnbounded, expected: "an array of unique items that will be decoded as a ReadonlySet" })
162
+ const to = S.instanceOf(Set) as S.instanceOf<ReadonlySet<S.Schema.Type<ValueSchema>>>
163
+ const schema = from.pipe(
164
+ S.decodeTo(
165
+ to,
166
+ SchemaTransformation.transform({
167
+ decode: (arr) => new Set(arr) as ReadonlySet<S.Schema.Type<ValueSchema>>,
168
+ encode: (set) => [...set]
169
+ })
170
+ )
93
171
  )
172
+ return schema
94
173
  }
95
174
 
96
- export { Map_ as Map }
175
+ /**
176
+ * An annotated `S.Array` of key-value tuples that decodes to a `ReadonlyMap`.
177
+ */
178
+ export const ReadonlyMapFromArray = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
179
+ readonly key: KeySchema
180
+ readonly value: ValueSchema
181
+ }) => {
182
+ const from = S
183
+ .Array(S.Tuple([pair.key, pair.value]))
184
+ .annotate({
185
+ ...concurrencyUnbounded,
186
+ expected: "an array of key-value tuples that will be decoded as a ReadonlyMap"
187
+ })
188
+ const to = S.instanceOf(Map) as S.instanceOf<
189
+ ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>
190
+ >
191
+ const schema = from.pipe(
192
+ S.decodeTo(
193
+ to,
194
+ SchemaTransformation.transform({
195
+ decode: (
196
+ arr
197
+ ) => new Map(arr) as ReadonlyMap<S.Schema.Type<KeySchema>, S.Schema.Type<ValueSchema>>,
198
+ encode: (
199
+ map
200
+ ) => [...map.entries()] as any // fu
201
+ })
202
+ )
203
+ )
204
+ return schema
205
+ }
97
206
 
98
207
  /**
99
- * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
208
+ * Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
100
209
  */
101
210
  export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
102
211
  pipe(
103
- S.ReadonlySet(value),
212
+ ReadonlySetFromArray(value),
104
213
  (s) =>
105
- Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
214
+ Object.assign(s, {
215
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))),
216
+ withDecodingDefaultType: s.pipe(
217
+ S.withDecodingDefaultType(Effect.sync(() => new Set<S.Schema.Type<ValueSchema>>()))
218
+ )
219
+ })
106
220
  )
107
221
 
108
222
  /**
109
- * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
223
+ * Like the default Schema `ReadonlyMap` but from Array with `withDefault` => new Map()
110
224
  */
111
225
  export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
112
226
  readonly key: KeySchema
113
227
  readonly value: ValueSchema
114
228
  }) =>
115
229
  pipe(
116
- S.ReadonlyMap(pair.key, pair.value),
117
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Map())) })
230
+ ReadonlyMapFromArray(pair),
231
+ (s) =>
232
+ Object.assign(s, {
233
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new Map()))),
234
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new Map())))
235
+ })
118
236
  )
119
237
 
120
238
  /**
@@ -123,21 +241,30 @@ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(
123
241
  export const NullOr = <Schema extends S.Top>(self: Schema) =>
124
242
  pipe(
125
243
  S.NullOr(self),
126
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
244
+ (s) =>
245
+ Object.assign(s, {
246
+ withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(null))),
247
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(null)))
248
+ })
127
249
  )
128
250
 
129
- export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
130
- schema.pipe(withDefaultConstructor(() => new global.Date()))
251
+ export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
252
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
131
253
 
132
- export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
254
+ export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
255
+ schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
133
256
 
134
- export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
257
+ export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
258
+ schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
135
259
 
136
- export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
260
+ export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
261
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
137
262
 
138
- export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
263
+ export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
264
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
139
265
 
140
- export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
266
+ export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
267
+ schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
141
268
 
142
269
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
143
270
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -167,8 +294,11 @@ export type WithDefaults<Self extends S.Top> = (
167
294
  // : never
168
295
 
169
296
  export const inputDate = extendM(
170
- S.Union([S.DateValid, S.Date]),
171
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => new globalThis.Date())) })
297
+ S.Union([S.DateValid, Date]),
298
+ (s) => ({
299
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => new globalThis.Date()))),
300
+ withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.sync(() => new globalThis.Date())))
301
+ })
172
302
  )
173
303
 
174
304
  export interface UnionBrand {}
@@ -218,7 +348,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
218
348
  { message: "One way schema transformation, encoding is not allowed" }
219
349
  )
220
350
  )
221
- }) as any
351
+ })
222
352
  )
223
353
  )
224
354
 
@@ -235,7 +365,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
235
365
  S.decodeTo(
236
366
  to,
237
367
  SchemaTransformation.transformOrFail({
238
- decode: decode as any,
368
+ decode,
239
369
  encode: (i: any) =>
240
370
  Effect.fail(
241
371
  new SchemaIssue.Forbidden(
@@ -243,34 +373,36 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
243
373
  { message: "One way schema transformation, encoding is not allowed" }
244
374
  )
245
375
  )
246
- }) as any
376
+ })
247
377
  )
248
378
  )
249
379
 
250
- export const provide = <Self extends S.Top, R>(
251
- self: Self,
252
- context: ServiceMap.ServiceMap<R>
253
- ): ProvidedCodec<Self, R> => {
380
+ export const provide: {
381
+ <R>(context: Context.Context<R>): <Self extends S.Top>(self: Self) => ProvidedCodec<Self, R>
382
+ <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R>
383
+ } = Function.dual(2, <Self extends S.Top, R>(self: Self, context: Context.Context<R>): ProvidedCodec<Self, R> => {
254
384
  const prov = Effect.provide(context)
255
385
  return self.pipe(
256
386
  S.middlewareDecoding((effect) => prov(effect)),
257
387
  S.middlewareEncoding((effect) => prov(effect))
258
- ) as ProvidedCodec<Self, R>
259
- }
260
- export const contextFromServices = <
388
+ )
389
+ })
390
+ export const contextFromServices = Effect.fnUntraced(function*<
391
+ Self extends S.Top,
392
+ Tags extends ReadonlyArray<Context.Key<any, any>>
393
+ >(self: Self, ...services: Tags) {
394
+ const context: Context.Context<Context.Service.Identifier<Tags[number]>> = Context.pick(...services)(
395
+ yield* Effect.context<Context.Service.Identifier<Tags[number]>>()
396
+ )
397
+ return provide(self, context)
398
+ }) as <
261
399
  Self extends S.Top,
262
- Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
400
+ Tags extends ReadonlyArray<Context.Key<any, any>>
263
401
  >(
264
402
  self: Self,
265
403
  ...services: Tags
266
- ): Effect.Effect<
267
- ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
404
+ ) => Effect.Effect<
405
+ ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
268
406
  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
- })
407
+ Context.Service.Identifier<Tags[number]>
408
+ >
@@ -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
 
@@ -135,7 +134,8 @@ export interface StringIdBrand extends Simplify<B.Brand<"StringId"> & NonEmptySt
135
134
  */
136
135
  export type StringId = string & StringIdBrand
137
136
 
138
- const makeStringId = (): StringId => nanoid() as unknown as StringId
137
+ const makeStringId = (s?: string): StringId =>
138
+ s !== undefined ? S.decodeSync(StringId)(s) : nanoid() as unknown as StringId
139
139
  const minLength = 6
140
140
  const maxLength = 50
141
141
  const size = 21
@@ -151,16 +151,15 @@ export const StringId = extendM(
151
151
  pipe(
152
152
  S.String,
153
153
  S.check(S.isMinLength(minLength), S.isMaxLength(maxLength)),
154
- fromBrand(nominal<StringId>(), {
154
+ fromBrand<StringId>(nominal<StringId>(), {
155
155
  identifier: "StringId",
156
- title: "StringId",
157
156
  toArbitrary: () => (fc) => StringIdArb()(fc),
158
157
  jsonSchema: {}
159
158
  })
160
159
  ),
161
160
  (s) => ({
162
161
  make: makeStringId,
163
- withDefault: s.pipe(withDefaultConstructor(makeStringId))
162
+ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(makeStringId)))
164
163
  })
165
164
  )
166
165
  .pipe(withDefaultMake)
@@ -169,7 +168,7 @@ export const StringId = extendM(
169
168
 
170
169
  // const prefixedStringIdUnsafeThunk = (prefix: string) => () => prefixedStringIdUnsafe(prefix)
171
170
 
172
- export function prefixedStringId<Brand extends StringId>() {
171
+ export function prefixedStringId<Type extends StringId>() {
173
172
  return <Prefix extends string, Separator extends string = "-">(
174
173
  prefix: Prefix,
175
174
  name: string,
@@ -177,27 +176,26 @@ export function prefixedStringId<Brand extends StringId>() {
177
176
  ) => {
178
177
  type FullPrefix = `${Prefix}${Separator}`
179
178
  const pref = `${prefix}${separator ?? "-"}` as FullPrefix
180
- const arb = (): S.LazyArbitrary<string & Brand> => (fc) =>
179
+ const arb = (): S.LazyArbitrary<Type> => (fc) =>
181
180
  StringIdArb()(fc).map(
182
- (x) => (pref + x.substring(0, 50 - pref.length)) as Brand
181
+ (x) => (pref + x.substring(0, 50 - pref.length)) as Type
183
182
  )
184
183
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
185
- const s: S.Codec<string & Brand, string> = StringId
184
+ const s = StringId
186
185
  .pipe(
187
- S.refine((x: string): x is string & Brand => x.startsWith(pref), {
188
- identifier: name,
189
- title: name
186
+ S.refine((x: string): x is Type => x.startsWith(pref), {
187
+ identifier: name
190
188
  }),
191
189
  S.annotate({
192
190
  toArbitrary: () => (fc) => arb()(fc)
193
191
  })
194
- ) as S.Codec<string & Brand, string>
192
+ )
195
193
  const schema = s.pipe(withDefaultMake)
196
- const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Brand
194
+ const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Type
197
195
 
198
196
  return extendM(
199
197
  schema,
200
- (ex): PrefixedStringUtils<Brand, Prefix, Separator> => ({
198
+ (ex): PrefixedStringUtils<Type, Prefix, Separator> => ({
201
199
  make,
202
200
  /**
203
201
  * Automatically adds the prefix.
@@ -208,32 +206,34 @@ export function prefixedStringId<Brand extends StringId>() {
208
206
  */
209
207
  prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => ex(str),
210
208
  prefix,
211
- withDefault: schema.pipe(withDefaultConstructor(make))
209
+ withDefault: schema.pipe(S.withConstructorDefault<S.Codec<Type, string> & S.WithoutConstructorDefault>(
210
+ Effect.sync(make)
211
+ ))
212
212
  })
213
213
  )
214
214
  }
215
215
  }
216
216
 
217
217
  export const brandedStringId = <
218
- Brand extends StringIdBrand
218
+ Id
219
219
  >() =>
220
220
  withDefaultMake(
221
- Object.assign(Object.create(StringId), StringId) as S.Codec<string & Brand, string> & {
222
- make: () => string & Brand
223
- withDefault: S.withConstructorDefault<S.Codec<string & Brand, string> & S.WithoutConstructorDefault>
224
- } & WithDefaults<S.Codec<string & Brand, string>>
221
+ Object.assign(Object.create(StringId), StringId) as S.Codec<Id, string> & {
222
+ withDefault: S.withConstructorDefault<S.Codec<Id, string> & S.WithoutConstructorDefault>
223
+ make: () => Id
224
+ } & WithDefaults<S.Codec<Id, string>>
225
225
  )
226
226
 
227
227
  export interface PrefixedStringUtils<
228
- Brand extends StringId,
228
+ Type extends StringId,
229
229
  Prefix extends string,
230
230
  Separator extends string
231
231
  > {
232
- readonly make: () => Brand
233
- readonly unsafeFrom: (str: string) => Brand
234
- prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => Brand
232
+ readonly make: () => Type
233
+ readonly unsafeFrom: (str: string) => Type
234
+ prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => Type
235
235
  readonly prefix: Prefix
236
- readonly withDefault: S.withConstructorDefault<S.Codec<Brand, string> & S.WithoutConstructorDefault>
236
+ readonly withDefault: S.withConstructorDefault<S.Codec<Type, string> & S.WithoutConstructorDefault>
237
237
  }
238
238
 
239
239
  export interface UrlBrand extends Simplify<B.Brand<"Url"> & NonEmptyStringBrand> {}
@@ -247,9 +247,12 @@ const isUrl: Refinement<string, Url> = (s: string): s is Url => {
247
247
  export const Url = S
248
248
  .String
249
249
  .pipe(
250
+ S.annotate({
251
+ title: "Url",
252
+ format: "uri"
253
+ }),
250
254
  S.refine(isUrl, {
251
255
  identifier: "Url",
252
- title: "Url",
253
256
  jsonSchema: { format: "uri" }
254
257
  }),
255
258
  S.annotate({