effect-app 4.0.0-beta.17 → 4.0.0-beta.170
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.
- package/CHANGELOG.md +683 -0
- package/dist/Array.d.ts +1 -1
- package/dist/Chunk.d.ts +1 -1
- package/dist/Chunk.d.ts.map +1 -1
- package/dist/Config/SecretURL.d.ts +1 -1
- package/dist/Config/SecretURL.d.ts.map +1 -1
- package/dist/Config/SecretURL.js +2 -2
- package/dist/Config/internal/configSecretURL.d.ts +1 -1
- package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
- package/dist/Config.d.ts +7 -0
- package/dist/Config.d.ts.map +1 -0
- package/dist/Config.js +6 -0
- package/dist/ConfigProvider.d.ts +39 -0
- package/dist/ConfigProvider.d.ts.map +1 -0
- package/dist/ConfigProvider.js +42 -0
- package/dist/Context.d.ts +40 -0
- package/dist/Context.d.ts.map +1 -0
- package/dist/Context.js +67 -0
- package/dist/Effect.d.ts +9 -10
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -6
- package/dist/Function.d.ts +1 -1
- package/dist/Function.d.ts.map +1 -1
- package/dist/Inputify.type.d.ts +1 -1
- package/dist/Layer.d.ts +6 -5
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +1 -1
- package/dist/NonEmptySet.d.ts +1 -1
- package/dist/NonEmptySet.d.ts.map +1 -1
- package/dist/Operations.d.ts +369 -47
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Operations.js +10 -10
- package/dist/Option.d.ts +1 -1
- package/dist/Option.d.ts.map +1 -1
- package/dist/Pure.d.ts +5 -5
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +13 -13
- package/dist/Schema/Class.d.ts +69 -20
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +190 -22
- package/dist/Schema/FastCheck.d.ts +1 -1
- package/dist/Schema/FastCheck.d.ts.map +1 -1
- package/dist/Schema/Methods.d.ts +1 -1
- package/dist/Schema/SchemaParser.d.ts +5 -0
- package/dist/Schema/SchemaParser.d.ts.map +1 -0
- package/dist/Schema/SchemaParser.js +6 -0
- package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +122 -0
- package/dist/Schema/SpecialOpenApi.d.ts +32 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +123 -0
- package/dist/Schema/brand.d.ts +7 -2
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +1 -1
- package/dist/Schema/email.d.ts +1 -1
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +7 -4
- package/dist/Schema/ext.d.ts +121 -48
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +129 -53
- package/dist/Schema/moreStrings.d.ts +111 -11
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +14 -15
- package/dist/Schema/numbers.d.ts +127 -15
- package/dist/Schema/numbers.d.ts.map +1 -1
- package/dist/Schema/numbers.js +10 -12
- package/dist/Schema/phoneNumber.d.ts +1 -1
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +6 -3
- package/dist/Schema/schema.d.ts +1 -1
- package/dist/Schema/strings.d.ts +37 -5
- package/dist/Schema/strings.d.ts.map +1 -1
- package/dist/Schema/strings.js +1 -5
- package/dist/Schema.d.ts +102 -56
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +128 -64
- package/dist/Set.d.ts +1 -1
- package/dist/Set.d.ts.map +1 -1
- package/dist/TypeTest.d.ts +1 -1
- package/dist/Types.d.ts +1 -1
- package/dist/Widen.type.d.ts +1 -1
- package/dist/_ext/Array.d.ts +1 -1
- package/dist/_ext/Array.d.ts.map +1 -1
- package/dist/_ext/date.d.ts +1 -1
- package/dist/_ext/misc.d.ts +1 -1
- package/dist/_ext/ord.ext.d.ts +1 -1
- package/dist/_ext/ord.ext.d.ts.map +1 -1
- package/dist/builtin.d.ts +1 -1
- package/dist/builtin.d.ts.map +1 -1
- package/dist/client/apiClientFactory.d.ts +14 -30
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +18 -19
- package/dist/client/clientFor.d.ts +30 -11
- package/dist/client/clientFor.d.ts.map +1 -1
- package/dist/client/clientFor.js +9 -1
- package/dist/client/errors.d.ts +44 -19
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +35 -10
- package/dist/client/makeClient.d.ts +243 -29
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +53 -23
- package/dist/client.d.ts +1 -1
- package/dist/faker.d.ts +1 -1
- package/dist/faker.d.ts.map +1 -1
- package/dist/http/Request.d.ts +2 -2
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/http/internal/lib.d.ts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/ids.d.ts +3 -3
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +3 -2
- package/dist/index.d.ts +5 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -8
- package/dist/logger.d.ts +1 -1
- package/dist/middleware.d.ts +8 -8
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +8 -8
- package/dist/rpc/MiddlewareMaker.d.ts +5 -4
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +26 -27
- package/dist/rpc/RpcContextMap.d.ts +3 -3
- package/dist/rpc/RpcContextMap.d.ts.map +1 -1
- package/dist/rpc/RpcContextMap.js +4 -4
- package/dist/rpc/RpcMiddleware.d.ts +5 -4
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.js +1 -1
- package/dist/rpc.d.ts +1 -2
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +1 -2
- package/dist/transform.d.ts +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +3 -3
- package/dist/utils/effectify.d.ts +1 -1
- package/dist/utils/extend.d.ts +1 -1
- package/dist/utils/extend.d.ts.map +1 -1
- package/dist/utils/gen.d.ts +2 -2
- package/dist/utils/gen.d.ts.map +1 -1
- package/dist/utils/logLevel.d.ts +2 -2
- package/dist/utils/logLevel.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +3 -3
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -3
- package/dist/utils.d.ts +30 -10
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +10 -4
- package/dist/validation/validators.d.ts +1 -1
- package/dist/validation/validators.d.ts.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/eslint.config.mjs +2 -2
- package/package.json +39 -19
- package/src/Config/SecretURL.ts +2 -1
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/{ServiceMap.ts → Context.ts} +52 -59
- package/src/Effect.ts +12 -14
- package/src/Layer.ts +5 -4
- package/src/Operations.ts +14 -14
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +279 -62
- package/src/Schema/SchemaParser.ts +12 -0
- package/src/Schema/SpecialJsonSchema.ts +137 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +9 -1
- package/src/Schema/email.ts +7 -2
- package/src/Schema/ext.ts +217 -87
- package/src/Schema/moreStrings.ts +22 -20
- package/src/Schema/numbers.ts +14 -16
- package/src/Schema/phoneNumber.ts +5 -1
- package/src/Schema/strings.ts +4 -8
- package/src/Schema.ts +265 -105
- package/src/client/apiClientFactory.ts +107 -113
- package/src/client/clientFor.ts +45 -12
- package/src/client/errors.ts +42 -17
- package/src/client/makeClient.ts +319 -63
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +2 -1
- package/src/index.ts +5 -10
- package/src/middleware.ts +7 -9
- package/src/rpc/MiddlewareMaker.ts +36 -47
- package/src/rpc/README.md +2 -2
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +5 -4
- package/src/rpc.ts +0 -1
- package/src/transform.ts +2 -2
- package/src/utils/gen.ts +1 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils.ts +47 -11
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/dist/secretURL.test.d.ts.map +1 -0
- package/test/dist/special.test.d.ts.map +1 -0
- package/test/rpc.test.ts +38 -6
- package/test/schema.test.ts +591 -17
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +1023 -0
- package/test/utils.test.ts +6 -6
- package/tsconfig.base.json +3 -4
- package/tsconfig.json +0 -1
- package/tsconfig.json.bak +2 -2
- package/tsconfig.src.json +29 -29
- package/tsconfig.test.json +2 -2
- package/dist/ServiceMap.d.ts +0 -44
- package/dist/ServiceMap.d.ts.map +0 -1
- package/dist/ServiceMap.js +0 -91
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SpecialOpenApi — Deduplicates `components/schemas` entries in an OpenAPI spec
|
|
3
|
+
* and applies JSON Schema post-processing (null removal, allOf flattening).
|
|
4
|
+
*
|
|
5
|
+
* When `OpenApi.fromApi` generates the spec, different AST nodes sharing the
|
|
6
|
+
* same identifier can produce duplicate entries (e.g. "X" and "X1") in
|
|
7
|
+
* `components.schemas`. This module provides a transform function that
|
|
8
|
+
* collapses those duplicates, rewrites all `$ref` pointers accordingly,
|
|
9
|
+
* and post-processes schemas for better codegen compatibility.
|
|
10
|
+
*
|
|
11
|
+
* Usage with the OpenApi `Transform` annotation:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { OpenApi } from "effect/unstable"
|
|
15
|
+
* import { deduplicateOpenApiSchemas } from "./SpecialOpenApi.js"
|
|
16
|
+
*
|
|
17
|
+
* const api = HttpApi.make("myApi")
|
|
18
|
+
* .pipe(HttpApi.annotateContext(OpenApi.annotations({ transform: deduplicateOpenApiSchemas })))
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { postProcessJsonSchema } from "./SpecialJsonSchema.js"
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Deduplicates `components.schemas` entries in an OpenAPI spec.
|
|
26
|
+
*
|
|
27
|
+
* Entries sharing the same base identifier (e.g. "X" and "X1") whose JSON
|
|
28
|
+
* representations are identical are collapsed into a single canonical entry,
|
|
29
|
+
* and all `$ref` pointers throughout the spec are rewritten to point to
|
|
30
|
+
* the canonical key.
|
|
31
|
+
*
|
|
32
|
+
* Designed to be used as the `transform` option in `OpenApi.annotations`.
|
|
33
|
+
*/
|
|
34
|
+
export function deduplicateOpenApiSchemas(
|
|
35
|
+
spec: Record<string, any>
|
|
36
|
+
): Record<string, any> {
|
|
37
|
+
const components = spec["components"] as Record<string, any> | undefined
|
|
38
|
+
if (!components) return spec
|
|
39
|
+
const schemas = components["schemas"] as Record<string, any> | undefined
|
|
40
|
+
if (!schemas) return spec
|
|
41
|
+
|
|
42
|
+
const keys = Object.keys(schemas)
|
|
43
|
+
if (keys.length === 0) return spec
|
|
44
|
+
|
|
45
|
+
// Group keys by base identifier (strip trailing digits)
|
|
46
|
+
const groups = new Map<string, Array<{ key: string; fingerprint: string }>>()
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
const base = getBaseIdentifier(key)
|
|
49
|
+
const fingerprint = JSON.stringify(schemas[key])
|
|
50
|
+
const group = groups.get(base)
|
|
51
|
+
if (group === undefined) {
|
|
52
|
+
groups.set(base, [{ key, fingerprint }])
|
|
53
|
+
} else {
|
|
54
|
+
group.push({ key, fingerprint })
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Build remapping from duplicate keys to canonical keys
|
|
59
|
+
const remapping = new Map<string, string>()
|
|
60
|
+
for (const [, group] of groups) {
|
|
61
|
+
if (group.length <= 1) continue
|
|
62
|
+
const seen = new Map<string, string>() // fingerprint -> canonical key
|
|
63
|
+
for (const entry of group) {
|
|
64
|
+
const canonical = seen.get(entry.fingerprint)
|
|
65
|
+
if (canonical !== undefined) {
|
|
66
|
+
remapping.set(entry.key, canonical)
|
|
67
|
+
} else {
|
|
68
|
+
seen.set(entry.fingerprint, entry.key)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (remapping.size === 0) return postProcessJsonSchema(spec)
|
|
74
|
+
|
|
75
|
+
// Build new schemas object without duplicates
|
|
76
|
+
const newSchemas: Record<string, any> = {}
|
|
77
|
+
for (const key of keys) {
|
|
78
|
+
if (!remapping.has(key)) {
|
|
79
|
+
newSchemas[key] = schemas[key]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Deep clone the spec, replace schemas, and rewrite all $ref pointers
|
|
84
|
+
const newSpec = structuredClone(spec)
|
|
85
|
+
newSpec["components"]["schemas"] = newSchemas
|
|
86
|
+
rewriteRefs(newSpec, remapping)
|
|
87
|
+
|
|
88
|
+
return postProcessJsonSchema(newSpec)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Extracts the base identifier from a schema key by stripping trailing
|
|
93
|
+
* digits appended by the gen() function.
|
|
94
|
+
* E.g. "X1" -> "X", "X" -> "X", "MyType2" -> "MyType"
|
|
95
|
+
*/
|
|
96
|
+
function getBaseIdentifier(key: string): string {
|
|
97
|
+
const match = key.match(/^(.+?)(\d+)$/)
|
|
98
|
+
return match ? match[1]! : key
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Recursively rewrites `$ref` values in a JSON object tree.
|
|
103
|
+
* Mutates the object in-place (caller should pass a deep clone).
|
|
104
|
+
*/
|
|
105
|
+
function rewriteRefs(obj: any, remapping: Map<string, string>): void {
|
|
106
|
+
if (obj === null || typeof obj !== "object") return
|
|
107
|
+
|
|
108
|
+
if (Array.isArray(obj)) {
|
|
109
|
+
for (const item of obj) {
|
|
110
|
+
rewriteRefs(item, remapping)
|
|
111
|
+
}
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (typeof obj.$ref === "string") {
|
|
116
|
+
// OpenAPI refs look like "#/components/schemas/X1"
|
|
117
|
+
const prefix = "#/components/schemas/"
|
|
118
|
+
if (obj.$ref.startsWith(prefix)) {
|
|
119
|
+
const refKey = obj.$ref.slice(prefix.length)
|
|
120
|
+
const canonical = remapping.get(refKey)
|
|
121
|
+
if (canonical !== undefined) {
|
|
122
|
+
obj.$ref = prefix + canonical
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const value of Object.values(obj)) {
|
|
128
|
+
rewriteRefs(value, remapping)
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/Schema/brand.ts
CHANGED
|
@@ -35,11 +35,19 @@ type BrandAnnotations<C extends B.Brand<any>> =
|
|
|
35
35
|
: {}
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
type BrandedSchema<Self extends S.Top, C extends B.Brand<any>> =
|
|
39
|
+
& Omit<S.brand<Self["Rebuild"], B.Brand.Keys<C>>, "Type" | "Iso" | "~type.make">
|
|
40
|
+
& {
|
|
41
|
+
readonly Type: C
|
|
42
|
+
readonly Iso: C
|
|
43
|
+
readonly "~type.make": C
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
export const fromBrand = <C extends B.Brand<any>>(
|
|
39
47
|
constructor: Constructor<C>,
|
|
40
48
|
options?: BrandAnnotations<C>
|
|
41
49
|
) =>
|
|
42
|
-
<Self extends S.Top>(self: Self):
|
|
50
|
+
<Self extends S.Top>(self: Self): BrandedSchema<Self, C> => {
|
|
43
51
|
const branded = S.fromBrand(options?.identifier ?? "Brand", constructor as any)(self as any)
|
|
44
52
|
return options ? (branded as any).pipe(S.annotate(options)) : branded as any
|
|
45
53
|
}
|
package/src/Schema/email.ts
CHANGED
|
@@ -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,
|
|
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,56 +1,115 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
3
|
-
import { Effect, Option, pipe,
|
|
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
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
type ProvidedCodec<Self extends S.Top, R> = S.Codec<
|
|
12
|
+
Self["Type"],
|
|
13
|
+
Self["Encoded"],
|
|
14
|
+
Exclude<Self["DecodingServices"], R>,
|
|
15
|
+
Exclude<Self["EncodingServices"], R>
|
|
16
|
+
>
|
|
17
|
+
|
|
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
|
|
22
38
|
|
|
23
39
|
// TODO: v4 migration - Date is no longer by default encoded to string.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
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))
|
|
30
67
|
|
|
31
68
|
/**
|
|
32
69
|
* Like the default Schema `Date` but from String with `withDefault` => now
|
|
33
70
|
*/
|
|
34
71
|
export const Date = Object.assign(DateFromString, {
|
|
35
|
-
withDefault: DateFromString.pipe(
|
|
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())))
|
|
36
82
|
})
|
|
37
83
|
|
|
38
84
|
/**
|
|
39
85
|
* Like the default Schema `Boolean` but with `withDefault` => false
|
|
40
86
|
*/
|
|
41
87
|
export const Boolean = Object.assign(S.Boolean, {
|
|
42
|
-
withDefault: S.Boolean.pipe(
|
|
88
|
+
withDefault: S.Boolean.pipe(S.withConstructorDefault(Effect.succeed(false))),
|
|
89
|
+
withDecodingDefaultType: S.Boolean.pipe(S.withDecodingDefaultType(Effect.succeed(false)))
|
|
43
90
|
})
|
|
44
91
|
|
|
45
92
|
/**
|
|
93
|
+
* You probably want to use `Finite` instead of this.
|
|
46
94
|
* Like the default Schema `Number` but with `withDefault` => 0
|
|
47
95
|
*/
|
|
48
|
-
export const Number = Object.assign(S.Number, {
|
|
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
|
+
})
|
|
49
100
|
|
|
50
101
|
/**
|
|
51
|
-
* Like the default Schema `
|
|
102
|
+
* Like the default Schema `Finite` but with `withDefault` => 0
|
|
52
103
|
*/
|
|
53
|
-
export const
|
|
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
|
+
})
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Like the default Schema `Literals` but with `withDefault` => literals[0]
|
|
111
|
+
*/
|
|
112
|
+
export const Literals = <const Literals extends NonEmptyReadonlyArray<AST.LiteralValue>>(literals: Literals) =>
|
|
54
113
|
pipe(
|
|
55
114
|
S.Literals(literals),
|
|
56
115
|
(s) =>
|
|
@@ -58,79 +117,144 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
|
|
|
58
117
|
changeDefault: <A extends Literals[number]>(a: A) => {
|
|
59
118
|
return Object.assign(S.Literals(literals), {
|
|
60
119
|
Default: a,
|
|
61
|
-
withDefault: s.pipe(
|
|
120
|
+
withDefault: s.pipe(S.withConstructorDefault(Effect.succeed(a))),
|
|
121
|
+
withDecodingDefaultType: s.pipe(S.withDecodingDefaultType(Effect.succeed(a)))
|
|
62
122
|
}) // todo: copy annotations from original?
|
|
63
123
|
},
|
|
64
|
-
|
|
65
|
-
|
|
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])))
|
|
66
128
|
})
|
|
67
129
|
)
|
|
68
130
|
|
|
69
131
|
/**
|
|
70
132
|
* Like the default Schema `Array` but with `withDefault` => []
|
|
71
133
|
*/
|
|
72
|
-
export function Array<
|
|
134
|
+
export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
|
|
73
135
|
return pipe(
|
|
74
|
-
S.Array(value),
|
|
75
|
-
(s) =>
|
|
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
|
+
})
|
|
76
142
|
)
|
|
77
143
|
}
|
|
78
144
|
|
|
79
145
|
/**
|
|
80
|
-
*
|
|
146
|
+
* An annotated `S.Array` of unique items that decodes to a `ReadonlySet`.
|
|
81
147
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
(
|
|
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
|
+
)
|
|
86
161
|
)
|
|
162
|
+
return schema
|
|
87
163
|
}
|
|
88
164
|
|
|
89
|
-
|
|
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
|
+
}
|
|
90
196
|
|
|
91
197
|
/**
|
|
92
|
-
* Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
|
|
198
|
+
* Like the default Schema `ReadonlySet` but from Array with `withDefault` => new Set()
|
|
93
199
|
*/
|
|
94
|
-
export const ReadonlySet = <
|
|
200
|
+
export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
|
|
95
201
|
pipe(
|
|
96
|
-
|
|
97
|
-
(s) =>
|
|
202
|
+
ReadonlySetFromArray(value),
|
|
203
|
+
(s) =>
|
|
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
|
+
})
|
|
98
210
|
)
|
|
99
211
|
|
|
100
212
|
/**
|
|
101
|
-
* Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
|
|
213
|
+
* Like the default Schema `ReadonlyMap` but from Array with `withDefault` => new Map()
|
|
102
214
|
*/
|
|
103
|
-
export const ReadonlyMap = <
|
|
104
|
-
readonly key:
|
|
105
|
-
readonly value:
|
|
215
|
+
export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
|
|
216
|
+
readonly key: KeySchema
|
|
217
|
+
readonly value: ValueSchema
|
|
106
218
|
}) =>
|
|
107
219
|
pipe(
|
|
108
|
-
|
|
109
|
-
(s) =>
|
|
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
|
+
})
|
|
110
226
|
)
|
|
111
227
|
|
|
112
228
|
/**
|
|
113
229
|
* Like the default Schema `NullOr` but with `withDefault` => null
|
|
114
230
|
*/
|
|
115
|
-
export const NullOr = <
|
|
231
|
+
export const NullOr = <Schema extends S.Top>(self: Schema) =>
|
|
116
232
|
pipe(
|
|
117
233
|
S.NullOr(self),
|
|
118
|
-
(s) =>
|
|
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
|
+
})
|
|
119
239
|
)
|
|
120
240
|
|
|
121
|
-
export const defaultDate =
|
|
241
|
+
export const defaultDate = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
|
|
242
|
+
schema.pipe(S.withConstructorDefault(Effect.sync(() => new global.Date())))
|
|
122
243
|
|
|
123
|
-
export const defaultBool =
|
|
244
|
+
export const defaultBool = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
|
|
245
|
+
schema.pipe(S.withConstructorDefault(Effect.succeed(false)))
|
|
124
246
|
|
|
125
|
-
export const defaultNullable = (
|
|
126
|
-
|
|
127
|
-
) => s.pipe(withDefaultConstructor(() => null))
|
|
247
|
+
export const defaultNullable = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
|
|
248
|
+
schema.pipe(S.withConstructorDefault(Effect.succeed(null)))
|
|
128
249
|
|
|
129
|
-
export const defaultArray =
|
|
250
|
+
export const defaultArray = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
|
|
251
|
+
schema.pipe(S.withConstructorDefault(Effect.sync(() => [])))
|
|
130
252
|
|
|
131
|
-
export const defaultMap =
|
|
253
|
+
export const defaultMap = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
|
|
254
|
+
schema.pipe(S.withConstructorDefault(Effect.sync(() => new Map())))
|
|
132
255
|
|
|
133
|
-
export const defaultSet =
|
|
256
|
+
export const defaultSet = <Schema extends S.Top & S.WithoutConstructorDefault>(schema: Schema) =>
|
|
257
|
+
schema.pipe(S.withConstructorDefault(Effect.sync(() => new Set())))
|
|
134
258
|
|
|
135
259
|
export const withDefaultMake = <Self extends S.Top>(s: Self) => {
|
|
136
260
|
const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
|
|
@@ -160,8 +284,11 @@ export type WithDefaults<Self extends S.Top> = (
|
|
|
160
284
|
// : never
|
|
161
285
|
|
|
162
286
|
export const inputDate = extendM(
|
|
163
|
-
S.Union([S.DateValid,
|
|
164
|
-
(s) => ({
|
|
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
|
+
})
|
|
165
292
|
)
|
|
166
293
|
|
|
167
294
|
export interface UnionBrand {}
|
|
@@ -211,7 +338,7 @@ export const transformTo = <To extends S.Top, From extends S.Top>(
|
|
|
211
338
|
{ message: "One way schema transformation, encoding is not allowed" }
|
|
212
339
|
)
|
|
213
340
|
)
|
|
214
|
-
})
|
|
341
|
+
})
|
|
215
342
|
)
|
|
216
343
|
)
|
|
217
344
|
|
|
@@ -228,7 +355,7 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
|
|
|
228
355
|
S.decodeTo(
|
|
229
356
|
to,
|
|
230
357
|
SchemaTransformation.transformOrFail({
|
|
231
|
-
decode
|
|
358
|
+
decode,
|
|
232
359
|
encode: (i: any) =>
|
|
233
360
|
Effect.fail(
|
|
234
361
|
new SchemaIssue.Forbidden(
|
|
@@ -236,33 +363,36 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
|
|
|
236
363
|
{ message: "One way schema transformation, encoding is not allowed" }
|
|
237
364
|
)
|
|
238
365
|
)
|
|
239
|
-
})
|
|
366
|
+
})
|
|
240
367
|
)
|
|
241
368
|
)
|
|
242
369
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
context: ServiceMap.ServiceMap<R>
|
|
248
|
-
): any => {
|
|
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> => {
|
|
249
374
|
const prov = Effect.provide(context)
|
|
250
|
-
return
|
|
251
|
-
.
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
375
|
+
return self.pipe(
|
|
376
|
+
S.middlewareDecoding((effect) => prov(effect)),
|
|
377
|
+
S.middlewareEncoding((effect) => prov(effect))
|
|
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 <
|
|
389
|
+
Self extends S.Top,
|
|
390
|
+
Tags extends ReadonlyArray<Context.Key<any, any>>
|
|
391
|
+
>(
|
|
392
|
+
self: Self,
|
|
393
|
+
...services: Tags
|
|
394
|
+
) => Effect.Effect<
|
|
395
|
+
ProvidedCodec<Self, Context.Service.Identifier<Tags[number]>>,
|
|
396
|
+
never,
|
|
397
|
+
Context.Service.Identifier<Tags[number]>
|
|
398
|
+
>
|