effect-app 4.0.0-beta.99 → 4.0.0
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 +2028 -0
- package/dist/Array.d.ts +2 -1
- package/dist/Array.d.ts.map +1 -1
- package/dist/Array.js +4 -4
- package/dist/Chunk.d.ts +2 -2
- package/dist/Chunk.d.ts.map +1 -1
- package/dist/Config/SecretURL.d.ts +3 -1
- package/dist/Config/SecretURL.d.ts.map +1 -1
- package/dist/Config/SecretURL.js +3 -6
- package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
- package/dist/Config/internal/configSecretURL.js +2 -2
- package/dist/Config.d.ts +0 -5
- package/dist/Config.d.ts.map +1 -1
- package/dist/Config.js +7 -5
- package/dist/ConfigProvider.d.ts +0 -37
- package/dist/ConfigProvider.d.ts.map +1 -1
- package/dist/ConfigProvider.js +5 -41
- package/dist/Context.d.ts +6 -4
- package/dist/Context.d.ts.map +1 -1
- package/dist/Context.js +4 -3
- package/dist/DataDependencies.d.ts +136 -0
- package/dist/DataDependencies.d.ts.map +1 -0
- package/dist/DataDependencies.js +112 -0
- package/dist/Effect.d.ts +3 -3
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +6 -9
- package/dist/Emailer.d.ts +51 -0
- package/dist/Emailer.d.ts.map +1 -0
- package/dist/Emailer.js +7 -0
- package/dist/Function.d.ts.map +1 -1
- package/dist/Layer.d.ts +6 -3
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +3 -2
- package/dist/Model/Repository/Registry.d.ts +22 -0
- package/dist/Model/Repository/Registry.d.ts.map +1 -0
- package/dist/Model/Repository/Registry.js +18 -0
- package/dist/Model/Repository/dependency.d.ts +6 -0
- package/dist/Model/Repository/dependency.d.ts.map +1 -0
- package/dist/Model/Repository/dependency.js +29 -0
- package/dist/Model/Repository/ext.d.ts +60 -0
- package/dist/Model/Repository/ext.d.ts.map +1 -0
- package/dist/Model/Repository/ext.js +124 -0
- package/dist/Model/Repository/internal/internal.d.ts +68 -0
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
- package/dist/Model/Repository/internal/internal.js +611 -0
- package/dist/Model/Repository/legacy.d.ts +21 -0
- package/dist/Model/Repository/legacy.d.ts.map +1 -0
- package/dist/Model/Repository/legacy.js +2 -0
- package/dist/Model/Repository/makeRepo.d.ts +71 -0
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
- package/dist/Model/Repository/makeRepo.js +30 -0
- package/dist/Model/Repository/service.d.ts +121 -0
- package/dist/Model/Repository/service.d.ts.map +1 -0
- package/dist/Model/Repository/service.js +2 -0
- package/dist/Model/Repository/validation.d.ts +72 -0
- package/dist/Model/Repository/validation.d.ts.map +1 -0
- package/dist/Model/Repository/validation.js +39 -0
- package/dist/Model/Repository.d.ts +8 -0
- package/dist/Model/Repository.d.ts.map +1 -0
- package/dist/Model/Repository.js +8 -0
- package/dist/Model/dsl.d.ts +33 -0
- package/dist/Model/dsl.d.ts.map +1 -0
- package/dist/Model/dsl.js +43 -0
- package/dist/Model/filter/filterApi.d.ts +30 -0
- package/dist/Model/filter/filterApi.d.ts.map +1 -0
- package/dist/Model/filter/filterApi.js +2 -0
- package/dist/Model/filter/types/errors.d.ts +29 -0
- package/dist/Model/filter/types/errors.d.ts.map +1 -0
- package/dist/Model/filter/types/errors.js +2 -0
- package/dist/Model/filter/types/fields.d.ts +15 -0
- package/dist/Model/filter/types/fields.d.ts.map +1 -0
- package/dist/Model/filter/types/fields.js +2 -0
- package/dist/Model/filter/types/path/common.d.ts +316 -0
- package/dist/Model/filter/types/path/common.d.ts.map +1 -0
- package/dist/Model/filter/types/path/common.js +2 -0
- package/dist/Model/filter/types/path/eager.d.ts +94 -0
- package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
- package/dist/Model/filter/types/path/eager.js +36 -0
- package/dist/Model/filter/types/path/index.d.ts +4 -0
- package/dist/Model/filter/types/path/index.d.ts.map +1 -0
- package/dist/Model/filter/types/path/index.js +3 -0
- package/dist/Model/filter/types/utils.d.ts +79 -0
- package/dist/Model/filter/types/utils.d.ts.map +1 -0
- package/dist/Model/filter/types/utils.js +2 -0
- package/dist/Model/filter/types/validator.d.ts +30 -0
- package/dist/Model/filter/types/validator.d.ts.map +1 -0
- package/dist/Model/filter/types/validator.js +2 -0
- package/dist/Model/filter/types.d.ts +5 -0
- package/dist/Model/filter/types.d.ts.map +1 -0
- package/dist/Model/filter/types.js +7 -0
- package/dist/Model/query/dsl.d.ts +630 -0
- package/dist/Model/query/dsl.d.ts.map +1 -0
- package/dist/Model/query/dsl.js +376 -0
- package/dist/Model/query/new-kid-interpreter.d.ts +132 -0
- package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
- package/dist/Model/query/new-kid-interpreter.js +370 -0
- package/dist/Model/query.d.ts +15 -0
- package/dist/Model/query.d.ts.map +1 -0
- package/dist/Model/query.js +3 -0
- package/dist/Model.d.ts +6 -0
- package/dist/Model.d.ts.map +1 -0
- package/dist/Model.js +6 -0
- package/dist/NonEmptySet.d.ts +3 -1
- package/dist/NonEmptySet.d.ts.map +1 -1
- package/dist/NonEmptySet.js +2 -2
- package/dist/Option.d.ts +1 -0
- package/dist/Option.d.ts.map +1 -1
- package/dist/Option.js +3 -1
- package/dist/Pure.d.ts +5 -3
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +5 -2
- package/dist/QueueMaker.d.ts +11 -0
- package/dist/QueueMaker.d.ts.map +1 -0
- package/dist/QueueMaker.js +11 -0
- package/dist/RequestContext.d.ts +87 -0
- package/dist/RequestContext.d.ts.map +1 -0
- package/dist/RequestContext.js +45 -0
- package/dist/RequestScopedDependencies.d.ts +21 -0
- package/dist/RequestScopedDependencies.d.ts.map +1 -0
- package/dist/RequestScopedDependencies.js +16 -0
- package/dist/Schema/Class.d.ts +163 -16
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +193 -21
- package/dist/Schema/FastCheck.d.ts.map +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 +15 -2
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -1
- package/dist/Schema/SpecialJsonSchema.js +70 -12
- package/dist/Schema/SpecialOpenApi.d.ts +1 -1
- package/dist/Schema/SpecialOpenApi.js +3 -3
- package/dist/Schema/brand.d.ts +5 -5
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +6 -4
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +8 -5
- package/dist/Schema/ext.d.ts +297 -72
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +331 -67
- package/dist/Schema/moreStrings.d.ts +82 -48
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +85 -51
- package/dist/Schema/numbers.d.ts +33 -21
- package/dist/Schema/numbers.d.ts.map +1 -1
- package/dist/Schema/numbers.js +64 -12
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +6 -6
- package/dist/Schema/schema.d.ts +1 -1
- package/dist/Schema/schema.d.ts.map +1 -1
- package/dist/Schema/schema.js +2 -2
- package/dist/Schema/strings.d.ts +18 -4
- package/dist/Schema/strings.d.ts.map +1 -1
- package/dist/Schema/strings.js +1 -5
- package/dist/Schema.d.ts +223 -6
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +189 -23
- package/dist/SchemaAST.d.ts +2 -0
- package/dist/SchemaAST.d.ts.map +1 -0
- package/dist/SchemaAST.js +2 -0
- package/dist/Set.d.ts +4 -1
- package/dist/Set.d.ts.map +1 -1
- package/dist/Set.js +3 -2
- package/dist/Store.d.ts +215 -0
- package/dist/Store.d.ts.map +1 -0
- package/dist/Store.js +113 -0
- package/dist/_ext/Array.d.ts +1 -1
- package/dist/_ext/Array.d.ts.map +1 -1
- package/dist/_ext/Array.js +4 -2
- package/dist/_ext/misc.d.ts +4 -1
- package/dist/_ext/misc.d.ts.map +1 -1
- package/dist/_ext/misc.js +4 -2
- package/dist/_ext/ord.ext.d.ts +2 -1
- package/dist/_ext/ord.ext.d.ts.map +1 -1
- package/dist/_ext/ord.ext.js +2 -2
- package/dist/builtin.d.ts +2 -14
- package/dist/builtin.d.ts.map +1 -1
- package/dist/builtin.js +1 -1
- package/dist/client/InvalidationKeys.d.ts +30 -0
- package/dist/client/InvalidationKeys.d.ts.map +1 -0
- package/dist/client/InvalidationKeys.js +37 -0
- package/dist/client/apiClientFactory.d.ts +13 -8
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +148 -29
- package/dist/client/clientFor.d.ts +48 -13
- package/dist/client/clientFor.d.ts.map +1 -1
- package/dist/client/clientFor.js +9 -1
- package/dist/client/errors.d.ts +74 -28
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +41 -10
- package/dist/client/makeClient.d.ts +487 -32
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +47 -21
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -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 +2 -2
- package/dist/http/internal/lib.d.ts +12 -12
- package/dist/http/internal/lib.d.ts.map +1 -1
- package/dist/http/internal/lib.js +13 -13
- package/dist/ids.d.ts +32 -14
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +38 -7
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/middleware.d.ts +12 -6
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +13 -7
- package/dist/rpc/Invalidation.d.ts +1124 -0
- package/dist/rpc/Invalidation.d.ts.map +1 -0
- package/dist/rpc/Invalidation.js +178 -0
- package/dist/rpc/MiddlewareMaker.d.ts +10 -7
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +57 -37
- package/dist/rpc/RpcContextMap.d.ts +2 -2
- package/dist/rpc/RpcContextMap.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.d.ts +13 -10
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.js +2 -2
- package/dist/rpc.d.ts +1 -1
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +2 -2
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +40 -0
- package/dist/setupRequest.d.ts +20 -0
- package/dist/setupRequest.d.ts.map +1 -0
- package/dist/setupRequest.js +72 -0
- package/dist/toast.d.ts +51 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +34 -0
- package/dist/transform.d.ts +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +4 -5
- package/dist/utils/effectify.d.ts +1 -1
- package/dist/utils/effectify.d.ts.map +1 -1
- package/dist/utils/effectify.js +2 -2
- package/dist/utils/extend.d.ts +6 -1
- package/dist/utils/extend.d.ts.map +1 -1
- package/dist/utils/extend.js +24 -6
- package/dist/utils/gen.d.ts +4 -4
- 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 +2 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +2 -2
- package/dist/utils.d.ts +34 -39
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +33 -37
- package/dist/validation/validators.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/withToast.d.ts +30 -0
- package/dist/withToast.d.ts.map +1 -0
- package/dist/withToast.js +64 -0
- package/package.json +26 -266
- package/src/Array.ts +5 -5
- package/src/Chunk.ts +3 -3
- package/src/Config/SecretURL.ts +6 -3
- package/src/Config/internal/configSecretURL.ts +2 -2
- package/src/Config.ts +6 -13
- package/src/ConfigProvider.ts +4 -47
- package/src/Context.ts +9 -8
- package/src/DataDependencies.ts +171 -0
- package/src/Effect.ts +9 -12
- package/src/Emailer.ts +51 -0
- package/src/Inputify.type.ts +1 -1
- package/src/Layer.ts +8 -5
- package/src/Model/Repository/Registry.ts +35 -0
- package/src/Model/Repository/dependency.ts +32 -0
- package/src/Model/Repository/ext.ts +374 -0
- package/src/Model/Repository/internal/internal.ts +988 -0
- package/src/Model/Repository/legacy.ts +29 -0
- package/src/Model/Repository/makeRepo.ts +168 -0
- package/src/Model/Repository/service.ts +676 -0
- package/src/Model/Repository/validation.ts +38 -0
- package/src/Model/Repository.ts +7 -0
- package/src/Model/dsl.ts +129 -0
- package/src/Model/filter/filterApi.ts +78 -0
- package/src/Model/filter/types/errors.ts +47 -0
- package/src/Model/filter/types/fields.ts +50 -0
- package/src/Model/filter/types/path/common.ts +404 -0
- package/src/Model/filter/types/path/eager.ts +329 -0
- package/src/Model/filter/types/path/index.ts +4 -0
- package/src/Model/filter/types/utils.ts +128 -0
- package/src/Model/filter/types/validator.ts +46 -0
- package/src/Model/filter/types.ts +6 -0
- package/src/Model/query/dsl.ts +3092 -0
- package/src/Model/query/new-kid-interpreter.ts +532 -0
- package/src/Model/query.ts +13 -0
- package/src/Model.ts +5 -0
- package/src/NonEmptySet.ts +6 -4
- package/src/Option.ts +2 -0
- package/src/Pure.ts +6 -3
- package/src/QueueMaker.ts +11 -0
- package/src/RequestContext.ts +48 -0
- package/src/RequestScopedDependencies.ts +45 -0
- package/src/Schema/Class.ts +550 -56
- package/src/Schema/SchemaParser.ts +12 -0
- package/src/Schema/SpecialJsonSchema.ts +75 -7
- package/src/Schema/SpecialOpenApi.ts +5 -5
- package/src/Schema/brand.ts +27 -12
- package/src/Schema/email.ts +9 -7
- package/src/Schema/ext.ts +408 -91
- package/src/Schema/moreStrings.ts +213 -88
- package/src/Schema/numbers.ts +107 -29
- package/src/Schema/phoneNumber.ts +8 -9
- package/src/Schema/schema.ts +1 -1
- package/src/Schema/strings.ts +23 -14
- package/src/Schema.ts +403 -46
- package/src/SchemaAST.ts +1 -0
- package/src/Set.ts +6 -2
- package/src/Store.ts +325 -0
- package/src/_ext/Array.ts +4 -2
- package/src/_ext/misc.ts +4 -1
- package/src/_ext/ord.ext.ts +2 -1
- package/src/builtin-json.d.ts +24 -0
- package/src/builtin.ts +16 -21
- package/src/client/InvalidationKeys.ts +55 -0
- package/src/client/apiClientFactory.ts +291 -126
- package/src/client/clientFor.ts +101 -33
- package/src/client/errors.ts +66 -20
- package/src/client/makeClient.ts +519 -60
- package/src/client.ts +6 -4
- package/src/http/Request.ts +4 -4
- package/src/http/internal/lib.ts +12 -12
- package/src/http.ts +1 -1
- package/src/ids.ts +45 -8
- package/src/index.ts +20 -16
- package/src/middleware.ts +12 -8
- package/src/rpc/Invalidation.ts +278 -0
- package/src/rpc/MiddlewareMaker.ts +86 -79
- package/src/rpc/README.md +2 -2
- package/src/rpc/RpcContextMap.ts +4 -4
- package/src/rpc/RpcMiddleware.ts +18 -13
- package/src/rpc.ts +4 -4
- package/src/runtime.ts +57 -0
- package/src/setupRequest.ts +138 -0
- package/src/toast.ts +54 -0
- package/src/transform.ts +4 -4
- package/src/utils/effectify.ts +1 -1
- package/src/utils/extend.ts +24 -5
- package/src/utils/gen.ts +7 -7
- package/src/utils/logLevel.ts +1 -1
- package/src/utils/logger.ts +3 -2
- package/src/utils.ts +85 -158
- package/src/validation.ts +2 -2
- package/src/withToast.ts +133 -0
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/dist/rpcMiddlewareContext.test.d.ts.map +1 -0
- package/test/moreStrings.test.ts +55 -10
- package/test/rpc.test.ts +28 -11
- package/test/rpcMiddlewareContext.test.ts +63 -0
- package/test/schema.test.ts +318 -55
- package/test/secretURL.test.ts +4 -1
- package/test/special.test.ts +663 -131
- package/test/utils.test.ts +5 -5
- package/tsconfig.base.json +6 -4
- package/tsconfig.json +9 -1
- package/tsconfig.src.json +29 -29
- package/tsconfig.test.json +2 -2
- package/dist/Operations.d.ts +0 -124
- package/dist/Operations.d.ts.map +0 -1
- package/dist/Operations.js +0 -29
- package/eslint.config.mjs +0 -26
- package/src/Operations.ts +0 -55
- package/tsconfig.json.bak +0 -12
package/test/special.test.ts
CHANGED
|
@@ -1,145 +1,595 @@
|
|
|
1
|
-
import { Option, Predicate, Schema, SchemaGetter } from "effect"
|
|
2
1
|
import { InvalidStateError, LoginError, NotFoundError, NotLoggedInError, OptimisticConcurrencyException, ServiceUnavailableError, UnauthorizedError, ValidationError } from "effect-app/client/errors"
|
|
2
|
+
import { RequestContext } from "effect-app/RequestContext"
|
|
3
3
|
import * as AppSchema from "effect-app/Schema"
|
|
4
|
-
import { Class
|
|
5
|
-
import { flattenSimpleAllOf, specialJsonSchemaDocument } from "effect-app/Schema/SpecialJsonSchema"
|
|
4
|
+
import { Class } from "effect-app/Schema/Class"
|
|
5
|
+
import { flattenNestedAnyOf, flattenSimpleAllOf, specialJsonSchemaDocument } from "effect-app/Schema/SpecialJsonSchema"
|
|
6
6
|
import { deduplicateOpenApiSchemas } from "effect-app/Schema/SpecialOpenApi"
|
|
7
|
+
import * as Effect from "effect/Effect"
|
|
8
|
+
import * as Option from "effect/Option"
|
|
9
|
+
import * as Predicate from "effect/Predicate"
|
|
7
10
|
import * as S from "effect/Schema"
|
|
11
|
+
import * as SchemaGetter from "effect/SchemaGetter"
|
|
8
12
|
import { describe, expect, it } from "vitest"
|
|
9
13
|
|
|
14
|
+
class _FacadeModel extends AppSchema.Class<_FacadeModel>("FacadeModel")({ value: S.String }) {
|
|
15
|
+
get upperValue() {
|
|
16
|
+
return this.value.toUpperCase()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static describe(self: { readonly value: string }) {
|
|
20
|
+
return `value:${self.value}`
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface FacadeModelEncoded {
|
|
25
|
+
readonly value: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface FacadeModelMake {
|
|
29
|
+
readonly value: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class FacadeModel extends AppSchema.OpaqueFacade<
|
|
33
|
+
FacadeModel,
|
|
34
|
+
FacadeModelEncoded,
|
|
35
|
+
FacadeModelMake,
|
|
36
|
+
never,
|
|
37
|
+
never
|
|
38
|
+
>()(_FacadeModel) {
|
|
39
|
+
declare readonly value: string
|
|
40
|
+
declare readonly upperValue: string
|
|
41
|
+
|
|
42
|
+
get decoratedValue() {
|
|
43
|
+
return `facade:${this.value}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
label() {
|
|
47
|
+
return this.decoratedValue
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class _RenamedFacadeModel extends AppSchema.Class<_RenamedFacadeModel>("RenamedFacadeModel")({
|
|
52
|
+
internalName: S.String
|
|
53
|
+
}) {
|
|
54
|
+
get upperName() {
|
|
55
|
+
return this.internalName.toUpperCase()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface RenamedFacadeModelEncoded {
|
|
60
|
+
readonly external_name: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface RenamedFacadeModelMake {
|
|
64
|
+
readonly internalName: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class RenamedFacadeModel extends AppSchema.OpaqueFacade<
|
|
68
|
+
RenamedFacadeModel,
|
|
69
|
+
RenamedFacadeModelEncoded,
|
|
70
|
+
RenamedFacadeModelMake,
|
|
71
|
+
never,
|
|
72
|
+
never
|
|
73
|
+
>()(_RenamedFacadeModel.pipe(S.encodeKeys({ internalName: "external_name" }))) {
|
|
74
|
+
declare readonly internalName: string
|
|
75
|
+
declare readonly upperName: string
|
|
76
|
+
|
|
77
|
+
get decoratedName() {
|
|
78
|
+
return `facade:${this.internalName}`
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const _StructFacadeModel = AppSchema.Struct({ value: S.String })
|
|
83
|
+
|
|
84
|
+
interface StructFacadeModelEncoded {
|
|
85
|
+
readonly value: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface StructFacadeModelMake {
|
|
89
|
+
readonly value: string
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class StructFacadeModel extends AppSchema.OpaqueFacade<
|
|
93
|
+
StructFacadeModel,
|
|
94
|
+
StructFacadeModelEncoded,
|
|
95
|
+
StructFacadeModelMake,
|
|
96
|
+
never,
|
|
97
|
+
never
|
|
98
|
+
>()(_StructFacadeModel) {
|
|
99
|
+
declare readonly value: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const _RenamedStructFacadeModel = AppSchema.Struct({ internalName: S.String }).pipe(
|
|
103
|
+
S.encodeKeys({ internalName: "external_name" })
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
interface RenamedStructFacadeModelEncoded {
|
|
107
|
+
readonly external_name: string
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface RenamedStructFacadeModelMake {
|
|
111
|
+
readonly internalName: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class RenamedStructFacadeModel extends AppSchema.OpaqueFacade<
|
|
115
|
+
RenamedStructFacadeModel,
|
|
116
|
+
RenamedStructFacadeModelEncoded,
|
|
117
|
+
RenamedStructFacadeModelMake,
|
|
118
|
+
never,
|
|
119
|
+
never
|
|
120
|
+
>()(_RenamedStructFacadeModel) {
|
|
121
|
+
declare readonly internalName: string
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const _RenamedOpaqueStructFacadeModel = AppSchema
|
|
125
|
+
.Opaque<
|
|
126
|
+
{ readonly internalName: string },
|
|
127
|
+
RenamedStructFacadeModelEncoded
|
|
128
|
+
>()(AppSchema.Struct({ internalName: S.String }))
|
|
129
|
+
.pipe(S.encodeKeys({ internalName: "external_name" }))
|
|
130
|
+
|
|
131
|
+
class RenamedOpaqueStructFacadeModel extends AppSchema.OpaqueFacade<
|
|
132
|
+
RenamedOpaqueStructFacadeModel,
|
|
133
|
+
RenamedStructFacadeModelEncoded,
|
|
134
|
+
RenamedStructFacadeModelMake,
|
|
135
|
+
never,
|
|
136
|
+
never
|
|
137
|
+
>()(_RenamedOpaqueStructFacadeModel) {
|
|
138
|
+
declare readonly internalName: string
|
|
139
|
+
}
|
|
140
|
+
|
|
10
141
|
describe("Class", () => {
|
|
11
|
-
|
|
12
|
-
|
|
142
|
+
describe("strict", () => {
|
|
143
|
+
it("encoding doesnt accepts plain objects matching the struct (Fields argument)", () => {
|
|
144
|
+
class A extends Class<A>("A")({ a: S.String }) {}
|
|
13
145
|
|
|
14
|
-
|
|
15
|
-
|
|
146
|
+
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
|
|
147
|
+
expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
|
|
148
|
+
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
|
|
149
|
+
})
|
|
16
150
|
|
|
17
|
-
|
|
18
|
-
|
|
151
|
+
it("encoding rejects plain objects matching the struct (Struct argument)", () => {
|
|
152
|
+
class A extends Class<A>("A")(S.Struct({ a: S.String })) {}
|
|
19
153
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
154
|
+
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
|
|
155
|
+
expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
|
|
156
|
+
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
|
|
157
|
+
})
|
|
23
158
|
|
|
24
|
-
|
|
25
|
-
|
|
159
|
+
it("decoding still works normally", () => {
|
|
160
|
+
class A extends Class<A>("A")({ a: S.String }) {}
|
|
26
161
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
})
|
|
162
|
+
const decoded = S.decodeUnknownSync(A)({ a: "hello" })
|
|
163
|
+
expect(decoded).toBeInstanceOf(A)
|
|
164
|
+
expect(decoded.a).toBe("hello")
|
|
31
165
|
|
|
32
|
-
|
|
33
|
-
|
|
166
|
+
expect(() => S.decodeUnknownSync(A)(null)).toThrow()
|
|
167
|
+
expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
|
|
168
|
+
})
|
|
34
169
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
expect((decoded as A).a).toBe("hello")
|
|
170
|
+
it("S.is accepts class instances and not matching plain objects", () => {
|
|
171
|
+
class A extends Class<A>("A")({ a: S.String }) {}
|
|
38
172
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
173
|
+
expect(S.is(A)(new A({ a: "hello" }))).toBe(true)
|
|
174
|
+
expect(S.is(A)({ a: "world" })).toBe(false)
|
|
175
|
+
expect(S.is(A)({ a: 1 })).toBe(false)
|
|
176
|
+
expect(S.is(A)(null)).toBe(false)
|
|
177
|
+
})
|
|
42
178
|
|
|
43
|
-
|
|
44
|
-
|
|
179
|
+
it("rejects values that don't match the struct", () => {
|
|
180
|
+
class A extends Class<A>("A")({ a: S.String }) {}
|
|
45
181
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
182
|
+
expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
|
|
183
|
+
expect(() => S.encodeUnknownSync(A)("not an object")).toThrow()
|
|
184
|
+
})
|
|
49
185
|
|
|
50
|
-
|
|
51
|
-
|
|
186
|
+
it("returns a class constructor — new and instanceof work", () => {
|
|
187
|
+
class A extends Class<A>("A")({ a: S.String }) {}
|
|
52
188
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
189
|
+
const instance = new A({ a: "hello" })
|
|
190
|
+
expect(instance).toBeInstanceOf(A)
|
|
191
|
+
expect(instance.a).toBe("hello")
|
|
192
|
+
})
|
|
57
193
|
|
|
58
|
-
|
|
59
|
-
|
|
194
|
+
it("preserves fields and identifier", () => {
|
|
195
|
+
class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
|
|
60
196
|
|
|
61
|
-
|
|
62
|
-
|
|
197
|
+
expect(A.identifier).toBe("A")
|
|
198
|
+
expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
|
|
199
|
+
})
|
|
63
200
|
})
|
|
64
|
-
})
|
|
65
201
|
|
|
66
|
-
describe("
|
|
67
|
-
|
|
68
|
-
|
|
202
|
+
describe("non strict", () => {
|
|
203
|
+
it("encoding accepts plain objects matching the struct (Fields argument)", () => {
|
|
204
|
+
class A extends Class<A>("A")({ a: S.String }, undefined, { strict: false }) {}
|
|
205
|
+
|
|
206
|
+
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
|
|
207
|
+
expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
|
|
208
|
+
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
|
|
209
|
+
})
|
|
69
210
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
expect(instance).toBeInstanceOf(A)
|
|
73
|
-
expect(instance.a).toBe("hello")
|
|
211
|
+
it("encoding accepts plain objects matching the struct (Struct argument)", () => {
|
|
212
|
+
class A extends Class<A>("A")(S.Struct({ a: S.String }), undefined, { strict: false }) {}
|
|
74
213
|
|
|
75
|
-
|
|
76
|
-
|
|
214
|
+
expect(S.encodeUnknownSync(A)(new A({ a: "hello" }))).toStrictEqual({ a: "hello" })
|
|
215
|
+
expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
|
|
216
|
+
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
|
|
217
|
+
})
|
|
77
218
|
|
|
78
|
-
|
|
79
|
-
|
|
219
|
+
it("S.is accepts class instances and matching plain objects", () => {
|
|
220
|
+
class A extends Class<A>("A")({ a: S.String }, undefined, { strict: false }) {}
|
|
80
221
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
222
|
+
expect(S.is(A)(new A({ a: "hello" }))).toBe(true)
|
|
223
|
+
expect(S.is(A)({ a: "world" })).toBe(true)
|
|
224
|
+
expect(S.is(A)({ a: 1 })).toBe(false)
|
|
225
|
+
expect(S.is(A)(null)).toBe(false)
|
|
226
|
+
})
|
|
84
227
|
})
|
|
228
|
+
})
|
|
85
229
|
|
|
86
|
-
|
|
87
|
-
|
|
230
|
+
describe("Class constructor", () => {
|
|
231
|
+
describe("strict", () => {
|
|
232
|
+
it("works as a base class — new, instanceof, encoding plain objects", () => {
|
|
233
|
+
class A extends Class<A>("A")({ a: S.String }) {}
|
|
234
|
+
|
|
235
|
+
const instance = new A({ a: "hello" })
|
|
236
|
+
expect(instance).toBeInstanceOf(A)
|
|
237
|
+
expect(instance.a).toBe("hello")
|
|
238
|
+
|
|
239
|
+
expect(S.encodeUnknownSync(A)(instance)).toStrictEqual({ a: "hello" })
|
|
240
|
+
expect(() => S.encodeUnknownSync(A)({ a: "world" })).toThrow()
|
|
241
|
+
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
|
|
242
|
+
expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
|
|
243
|
+
})
|
|
88
244
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
expect((decoded as A).a).toBe("hello")
|
|
245
|
+
it("decoding works normally", () => {
|
|
246
|
+
class A extends Class<A>("A")({ a: S.String }) {}
|
|
92
247
|
|
|
93
|
-
|
|
248
|
+
const decoded = S.decodeUnknownSync(A)({ a: "hello" })
|
|
249
|
+
expect(decoded).toBeInstanceOf(A)
|
|
250
|
+
expect(decoded.a).toBe("hello")
|
|
251
|
+
|
|
252
|
+
expect(() => S.decodeUnknownSync(A)({ a: 1 })).toThrow()
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it("exposes fields, identifier", () => {
|
|
256
|
+
class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
|
|
257
|
+
|
|
258
|
+
expect(A.identifier).toBe("A")
|
|
259
|
+
expect(Object.keys(A.fields)).toStrictEqual(["a", "b"])
|
|
260
|
+
})
|
|
94
261
|
})
|
|
95
262
|
|
|
96
|
-
|
|
97
|
-
|
|
263
|
+
describe("non strict", () => {
|
|
264
|
+
it("works as a base class — new, instanceof, encoding plain objects", () => {
|
|
265
|
+
class A extends Class<A>("A")({ a: S.String }, undefined, { strict: false }) {}
|
|
98
266
|
|
|
99
|
-
|
|
100
|
-
|
|
267
|
+
const instance = new A({ a: "hello" })
|
|
268
|
+
expect(instance).toBeInstanceOf(A)
|
|
269
|
+
expect(instance.a).toBe("hello")
|
|
270
|
+
|
|
271
|
+
expect(S.encodeUnknownSync(A)(instance)).toStrictEqual({ a: "hello" })
|
|
272
|
+
expect(S.encodeUnknownSync(A)({ a: "world" })).toStrictEqual({ a: "world" })
|
|
273
|
+
expect(() => S.encodeUnknownSync(A)(null)).toThrow()
|
|
274
|
+
expect(() => S.encodeUnknownSync(A)({ a: 123 })).toThrow()
|
|
275
|
+
})
|
|
101
276
|
})
|
|
102
277
|
})
|
|
103
278
|
|
|
104
|
-
describe("
|
|
105
|
-
it("works
|
|
106
|
-
|
|
279
|
+
describe("TaggedStruct constructor", () => {
|
|
280
|
+
it("works with _tag, make, and encoding plain objects", () => {
|
|
281
|
+
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
|
|
107
282
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
expect(instance).toBeInstanceOf(Circle)
|
|
111
|
-
expect(instance._tag).toBe("Circle")
|
|
112
|
-
expect(instance.radius).toBe(5)
|
|
283
|
+
const instance = Circle.make({ radius: 5 })
|
|
284
|
+
expect(instance).toEqual({ _tag: "Circle", radius: 5 })
|
|
113
285
|
|
|
114
|
-
// Encoding a class instance
|
|
115
286
|
expect(S.encodeUnknownSync(Circle)(instance)).toStrictEqual({ _tag: "Circle", radius: 5 })
|
|
116
|
-
|
|
117
|
-
// Encoding a plain object
|
|
118
287
|
expect(S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: 10 })).toStrictEqual({ _tag: "Circle", radius: 10 })
|
|
119
288
|
|
|
120
|
-
// Encoding invalid input fails
|
|
121
289
|
expect(() => S.encodeUnknownSync(Circle)(null)).toThrow()
|
|
122
290
|
expect(() => S.encodeUnknownSync(Circle)({ _tag: "Circle", radius: "nope" })).toThrow()
|
|
123
291
|
})
|
|
124
292
|
|
|
125
293
|
it("decoding works normally", () => {
|
|
126
|
-
|
|
294
|
+
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
|
|
127
295
|
|
|
128
296
|
const decoded = S.decodeUnknownSync(Circle)({ _tag: "Circle", radius: 5 })
|
|
129
|
-
expect(decoded).
|
|
130
|
-
|
|
131
|
-
|
|
297
|
+
expect(decoded).toEqual({ _tag: "Circle", radius: 5 })
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it("S.decodeSync(S.toType(X)) should report n length schema error", () => {
|
|
301
|
+
class X extends S.Opaque<X>()(S.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
S.decodeSync(S.toType(X))({ _tag: "X", n: "a" /* not length 3 */ })
|
|
305
|
+
expect.fail("expected decode to fail with a SchemaError")
|
|
306
|
+
} catch (error) {
|
|
307
|
+
expect(S.isSchemaError(error)).toBe(true)
|
|
308
|
+
if (S.isSchemaError(error)) {
|
|
309
|
+
expect(error.message).toContain("n")
|
|
310
|
+
expect(error.message.toLowerCase()).toContain("length")
|
|
311
|
+
}
|
|
312
|
+
}
|
|
132
313
|
})
|
|
133
314
|
|
|
134
|
-
it("exposes fields
|
|
135
|
-
|
|
315
|
+
it("exposes fields", () => {
|
|
316
|
+
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
|
|
136
317
|
|
|
137
|
-
expect(Circle.identifier).toBe("Circle")
|
|
138
318
|
expect(Object.keys(Circle.fields)).toContain("_tag")
|
|
139
319
|
expect(Object.keys(Circle.fields)).toContain("radius")
|
|
140
320
|
})
|
|
141
321
|
})
|
|
142
322
|
|
|
323
|
+
describe("strict declaration option", () => {
|
|
324
|
+
it("Class strict: true keeps class-level expected errors", () => {
|
|
325
|
+
class X extends Class<X>("X")({ n: S.String.pipe(S.check(S.isMinLength(3))) }, undefined, { strict: true }) {}
|
|
326
|
+
|
|
327
|
+
expect(() => S.decodeSync(S.toType(X))({ n: "a" })).toThrow("Expected X")
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it("TaggedStruct preserves field-level expected errors", () => {
|
|
331
|
+
class X extends S.Opaque<X>()(AppSchema.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
|
|
332
|
+
|
|
333
|
+
expect(() => S.decodeSync(S.toType(X))({ _tag: "X", n: "a" })).toThrow()
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it("Class with encoded override strict: true keeps class-level expected errors", () => {
|
|
337
|
+
class X extends Class<X, never>("X")({ n: S.String.pipe(S.check(S.isMinLength(3))) }, undefined, {
|
|
338
|
+
strict: true
|
|
339
|
+
}) {}
|
|
340
|
+
|
|
341
|
+
expect(() => S.decodeSync(S.toType(X))({ n: "a" })).toThrow("Expected X")
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
it("TaggedStruct with opaque wrapper decodes tagged input", () => {
|
|
345
|
+
class X extends S.Opaque<X>()(AppSchema.TaggedStruct("X", { n: S.String.pipe(S.check(S.isMinLength(3))) })) {}
|
|
346
|
+
|
|
347
|
+
expect(() => S.decodeSync(S.toType(X))({ _tag: "X", n: "a" })).toThrow()
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
describe("Class.copy", () => {
|
|
352
|
+
it("creates a new instance with updated fields", () => {
|
|
353
|
+
class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
|
|
354
|
+
|
|
355
|
+
const instance = new A({ a: "hello", b: 1 })
|
|
356
|
+
const copied: A = A.copy(instance, { b: 2 })
|
|
357
|
+
expect(copied).toBeInstanceOf(A)
|
|
358
|
+
expect(copied.a).toBe("hello")
|
|
359
|
+
expect(copied.b).toBe(2)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it("accepts a function for updates", () => {
|
|
363
|
+
class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
|
|
364
|
+
|
|
365
|
+
const instance = new A({ a: "hello", b: 1 })
|
|
366
|
+
const copied: A = A.copy(instance, (a) => ({ b: a.b + 1 }))
|
|
367
|
+
expect(copied).toBeInstanceOf(A)
|
|
368
|
+
expect(copied.b).toBe(2)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it("is pipeable", () => {
|
|
372
|
+
class A extends Class<A>("A")({ a: S.String, b: S.Number }) {}
|
|
373
|
+
|
|
374
|
+
const instance = new A({ a: "hello", b: 1 })
|
|
375
|
+
const copied: A = A.copy({ b: 2 })(instance)
|
|
376
|
+
expect(copied).toBeInstanceOf(A)
|
|
377
|
+
expect(copied.b).toBe(2)
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
describe("TaggedStruct (opaque).copy", () => {
|
|
382
|
+
it("creates a new instance with updated fields", () => {
|
|
383
|
+
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
|
|
384
|
+
|
|
385
|
+
const instance = Circle.make({ radius: 5 })
|
|
386
|
+
const copied = Circle.copy(instance, { radius: 10 })
|
|
387
|
+
expect(copied).toEqual({ _tag: "Circle", radius: 10 })
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it("accepts a function for updates", () => {
|
|
391
|
+
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
|
|
392
|
+
|
|
393
|
+
const instance = Circle.make({ radius: 5 })
|
|
394
|
+
const copied = Circle.copy(instance, (c) => ({ radius: c.radius * 2 }))
|
|
395
|
+
expect(copied).toEqual({ _tag: "Circle", radius: 10 })
|
|
396
|
+
})
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
describe("OpaqueFacade", () => {
|
|
400
|
+
it("keeps OpaqueFacade usable with struct schemas", () => {
|
|
401
|
+
const decoded = S.decodeSync(StructFacadeModel)({ value: "hello" })
|
|
402
|
+
|
|
403
|
+
expect(decoded).toStrictEqual({ value: "hello" })
|
|
404
|
+
expect(S.encodeSync(StructFacadeModel)(decoded)).toStrictEqual({ value: "hello" })
|
|
405
|
+
expect(S.is(StructFacadeModel)(decoded)).toBe(true)
|
|
406
|
+
expect(S.is(StructFacadeModel)({ value: 1 })).toBe(false)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it("keeps encoded-key struct facades on the struct path", () => {
|
|
410
|
+
const decoded = S.decodeSync(RenamedStructFacadeModel)({ external_name: "fran" })
|
|
411
|
+
|
|
412
|
+
expect(decoded).toStrictEqual({ internalName: "fran" })
|
|
413
|
+
expect(decoded).not.toBeInstanceOf(RenamedStructFacadeModel)
|
|
414
|
+
expect(S.encodeSync(RenamedStructFacadeModel)(decoded)).toStrictEqual({ external_name: "fran" })
|
|
415
|
+
expect(S.is(RenamedStructFacadeModel)(decoded)).toBe(true)
|
|
416
|
+
expect(S.is(RenamedStructFacadeModel)({ external_name: "fran" })).toBe(false)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it("keeps encoded-key opaque struct facades on the struct path", () => {
|
|
420
|
+
const decoded = S.decodeSync(RenamedOpaqueStructFacadeModel)({ external_name: "fran" })
|
|
421
|
+
|
|
422
|
+
expect(decoded).toStrictEqual({ internalName: "fran" })
|
|
423
|
+
expect(decoded).not.toBeInstanceOf(RenamedOpaqueStructFacadeModel)
|
|
424
|
+
expect(S.encodeSync(RenamedOpaqueStructFacadeModel)(decoded)).toStrictEqual({ external_name: "fran" })
|
|
425
|
+
expect(S.is(RenamedOpaqueStructFacadeModel)(decoded)).toBe(true)
|
|
426
|
+
expect(S.is(RenamedOpaqueStructFacadeModel)({ external_name: "fran" })).toBe(false)
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
it("decodes through the public facade class", () => {
|
|
430
|
+
const decoded = S.decodeSync(FacadeModel)({ value: "hello" })
|
|
431
|
+
const decodedEffect = Effect.runSync(S.decodeUnknownEffect(FacadeModel)({ value: "effect" }))
|
|
432
|
+
|
|
433
|
+
expect(decoded).toBeInstanceOf(FacadeModel)
|
|
434
|
+
expect(decodedEffect).toBeInstanceOf(FacadeModel)
|
|
435
|
+
expect(decoded).toBeInstanceOf(_FacadeModel)
|
|
436
|
+
expect(decoded.value).toBe("hello")
|
|
437
|
+
expect(decoded.upperValue).toBe("HELLO")
|
|
438
|
+
expect(decoded.decoratedValue).toBe("facade:hello")
|
|
439
|
+
expect(decoded.label()).toBe("facade:hello")
|
|
440
|
+
expect(FacadeModel.describe(decoded)).toBe("value:hello")
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it("validates facade instances with S.is", () => {
|
|
444
|
+
const instance = new FacadeModel({ value: "hello" })
|
|
445
|
+
|
|
446
|
+
expect(instance).toBeInstanceOf(FacadeModel)
|
|
447
|
+
expect(instance.upperValue).toBe("HELLO")
|
|
448
|
+
expect(instance.label()).toBe("facade:hello")
|
|
449
|
+
expect(S.is(FacadeModel)(instance)).toBe(true)
|
|
450
|
+
expect(S.is(FacadeModel)(S.decodeSync(FacadeModel)({ value: "world" }))).toBe(true)
|
|
451
|
+
expect(S.is(FacadeModel)({ value: "plain" })).toBe(false)
|
|
452
|
+
expect(S.is(FacadeModel)({ value: 1 })).toBe(false)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it("decodes renamed keys through the public facade class", () => {
|
|
456
|
+
const decoded = S.decodeSync(RenamedFacadeModel)({ external_name: "fran" })
|
|
457
|
+
|
|
458
|
+
expect(decoded).toBeInstanceOf(RenamedFacadeModel)
|
|
459
|
+
expect(decoded).toBeInstanceOf(_RenamedFacadeModel)
|
|
460
|
+
expect(Object.getPrototypeOf(decoded)).toBe(RenamedFacadeModel.prototype)
|
|
461
|
+
expect(decoded.internalName).toBe("fran")
|
|
462
|
+
expect(decoded.upperName).toBe("FRAN")
|
|
463
|
+
expect(decoded.decoratedName).toBe("facade:fran")
|
|
464
|
+
expect(S.encodeSync(RenamedFacadeModel)(decoded)).toStrictEqual({ external_name: "fran" })
|
|
465
|
+
expect(S.is(RenamedFacadeModel)(decoded)).toBe(true)
|
|
466
|
+
expect(S.is(RenamedFacadeModel)({ internalName: "plain" })).toBe(false)
|
|
467
|
+
expect(S.is(RenamedFacadeModel)({ external_name: "plain" })).toBe(false)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it("keeps generated struct facades decodable with public statics", () => {
|
|
471
|
+
const input: typeof RequestContext.Encoded = {
|
|
472
|
+
span: { traceId: "trace", spanId: "span", sampled: true },
|
|
473
|
+
name: "operation",
|
|
474
|
+
locale: "en",
|
|
475
|
+
namespace: "tenant"
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const decoded = S.decodeSync(RequestContext)(input)
|
|
479
|
+
|
|
480
|
+
expect(decoded).not.toBeInstanceOf(RequestContext)
|
|
481
|
+
expect(S.is(RequestContext)(decoded)).toBe(true)
|
|
482
|
+
expect(RequestContext.toMonitoring(decoded)).toStrictEqual({ operationName: "operation", locale: "en" })
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it("keeps error facades class-only", () => {
|
|
486
|
+
const makeStructErrorFacade = () =>
|
|
487
|
+
AppSchema.OpaqueErrorFacadeClass<
|
|
488
|
+
{ readonly value: string },
|
|
489
|
+
{ readonly value: string },
|
|
490
|
+
{ readonly value: string },
|
|
491
|
+
never,
|
|
492
|
+
never
|
|
493
|
+
>()(
|
|
494
|
+
// Runtime guard: only class schemas are accepted (struct schemas throw).
|
|
495
|
+
AppSchema.Struct({ value: S.String }) as never
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
expect(makeStructErrorFacade).toThrow("OpaqueErrorFacadeClass requires a class schema")
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
describe("Struct.copy", () => {
|
|
503
|
+
it("creates a new value with updated fields", () => {
|
|
504
|
+
const A = AppSchema.Struct({ a: S.String, b: S.Number })
|
|
505
|
+
|
|
506
|
+
const instance = A.make({ a: "hello", b: 1 })
|
|
507
|
+
const copied = A.copy(instance, { b: 2 })
|
|
508
|
+
|
|
509
|
+
expect(copied).toEqual({ a: "hello", b: 2 })
|
|
510
|
+
expect(copied).not.toBe(instance)
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it("accepts a function for updates", () => {
|
|
514
|
+
const A = AppSchema.Struct({ a: S.String, b: S.Number })
|
|
515
|
+
|
|
516
|
+
const instance = A.make({ a: "hello", b: 1 })
|
|
517
|
+
const copied = A.copy(instance, (a) => ({ b: a.b + 1 }))
|
|
518
|
+
|
|
519
|
+
expect(copied).toEqual({ a: "hello", b: 2 })
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
it("allows widening updates against the full struct type", () => {
|
|
523
|
+
const A = AppSchema.Struct({
|
|
524
|
+
name: S.String,
|
|
525
|
+
state: S.Union([
|
|
526
|
+
S.Struct({ _tag: S.tag("a"), a: S.String }),
|
|
527
|
+
S.Struct({ _tag: S.tag("b"), b: S.Number })
|
|
528
|
+
])
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
const instance = A.make({ name: "x", state: { _tag: "a", a: "a" } })
|
|
532
|
+
const copied = A.copy(instance, { state: { _tag: "b", b: 1 } })
|
|
533
|
+
|
|
534
|
+
expect(copied).toEqual({ name: "x", state: { _tag: "b", b: 1 } })
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
it("copy function is preserved when using .annotate()", () => {
|
|
538
|
+
const A = AppSchema.Struct({ a: S.String, b: S.Number }).annotate({ title: "A" })
|
|
539
|
+
|
|
540
|
+
const instance = A.make({ a: "hello", b: 1 })
|
|
541
|
+
const copied = A.copy(instance, { b: 2 })
|
|
542
|
+
|
|
543
|
+
expect(copied).toEqual({ a: "hello", b: 2 })
|
|
544
|
+
expect(copied).not.toBe(instance)
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
it("copy function is preserved when using .annotateKey()", () => {
|
|
548
|
+
const A = AppSchema.Struct({ a: S.String, b: S.Number }).annotateKey({ title: "A" })
|
|
549
|
+
|
|
550
|
+
const instance = A.make({ a: "hello", b: 1 })
|
|
551
|
+
const copied = A.copy(instance, { b: 2 })
|
|
552
|
+
|
|
553
|
+
expect(copied).toEqual({ a: "hello", b: 2 })
|
|
554
|
+
expect(copied).not.toBe(instance)
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
it("copy function is preserved when using .mapFields()", () => {
|
|
558
|
+
const A = AppSchema.Struct({ a: S.String, b: S.Number }).mapFields((f) => ({ ...f }))
|
|
559
|
+
|
|
560
|
+
const instance = A.make({ a: "hello", b: 1 })
|
|
561
|
+
const copied = A.copy(instance, { b: 2 })
|
|
562
|
+
|
|
563
|
+
expect(copied).toEqual({ a: "hello", b: 2 })
|
|
564
|
+
expect(copied).not.toBe(instance)
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
it("copy function is preserved through chained calls", () => {
|
|
568
|
+
const A = AppSchema
|
|
569
|
+
.Struct({ a: S.String, b: S.Number })
|
|
570
|
+
.annotate({ title: "A" })
|
|
571
|
+
.annotateKey({ description: "test" })
|
|
572
|
+
|
|
573
|
+
const instance = A.make({ a: "hello", b: 1 })
|
|
574
|
+
const copied = A.copy(instance, { b: 2 })
|
|
575
|
+
|
|
576
|
+
expect(copied).toEqual({ a: "hello", b: 2 })
|
|
577
|
+
expect(copied).not.toBe(instance)
|
|
578
|
+
})
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
describe("TaggedStruct.copy", () => {
|
|
582
|
+
it("creates a new tagged value with updated fields", () => {
|
|
583
|
+
const Circle = AppSchema.TaggedStruct("Circle", { radius: S.Number })
|
|
584
|
+
|
|
585
|
+
const instance = Circle.make({ radius: 5 })
|
|
586
|
+
const copied = Circle.copy(instance, { radius: 10 })
|
|
587
|
+
|
|
588
|
+
expect(copied).toEqual({ _tag: "Circle", radius: 10 })
|
|
589
|
+
expect(copied).not.toBe(instance)
|
|
590
|
+
})
|
|
591
|
+
})
|
|
592
|
+
|
|
143
593
|
describe("TaggedError", () => {
|
|
144
594
|
it("InvalidStateError toString includes the message", () => {
|
|
145
595
|
const error = new InvalidStateError("something went wrong")
|
|
@@ -191,36 +641,36 @@ describe("TaggedError", () => {
|
|
|
191
641
|
|
|
192
642
|
describe("SpecialJsonSchema", () => {
|
|
193
643
|
it("nullable to optional — from NullOr", () => {
|
|
194
|
-
const nullableDecodedUndefinedEncoded = (schema:
|
|
644
|
+
const nullableDecodedUndefinedEncoded = (schema: S.Top) => {
|
|
195
645
|
const isNullableSchema = "members" in schema
|
|
196
646
|
&& globalThis.Array.isArray((schema as any).members)
|
|
197
647
|
&& (schema as any).members.length === 2
|
|
198
648
|
&& (schema as any).members.some((member: any) => member.ast._tag === "Null")
|
|
199
649
|
|
|
200
|
-
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<
|
|
650
|
+
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<S.Top> : undefined
|
|
201
651
|
const innerSchema = nullableMembers
|
|
202
652
|
? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
|
|
203
653
|
: schema
|
|
204
654
|
|
|
205
|
-
const nullableSchema = isNullableSchema ? schema :
|
|
655
|
+
const nullableSchema = isNullableSchema ? schema : S.NullOr(schema)
|
|
206
656
|
|
|
207
657
|
return nullableSchema.pipe(
|
|
208
|
-
|
|
658
|
+
S.encodeTo(S.optionalKey(innerSchema), {
|
|
209
659
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
210
660
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
211
661
|
})
|
|
212
662
|
)
|
|
213
663
|
}
|
|
214
664
|
|
|
215
|
-
const fromNullOr = nullableDecodedUndefinedEncoded(
|
|
216
|
-
const structFromNullOr =
|
|
665
|
+
const fromNullOr = nullableDecodedUndefinedEncoded(S.NullOr(S.String))
|
|
666
|
+
const structFromNullOr = S.Struct({ status: fromNullOr })
|
|
217
667
|
|
|
218
|
-
const encode =
|
|
219
|
-
const encodedNull = encode({ status: null })
|
|
668
|
+
const encode = S.encodeUnknownSync(structFromNullOr as any)
|
|
669
|
+
const encodedNull = encode({ status: null })
|
|
220
670
|
expect("status" in encodedNull).toBe(false)
|
|
221
671
|
expect(encode({ status: "test" })).toStrictEqual({ status: "test" })
|
|
222
672
|
|
|
223
|
-
const decode =
|
|
673
|
+
const decode = S.decodeUnknownSync(structFromNullOr as any)
|
|
224
674
|
expect(decode({})).toStrictEqual({ status: null })
|
|
225
675
|
expect(decode({ status: "test" })).toStrictEqual({ status: "test" })
|
|
226
676
|
|
|
@@ -229,34 +679,34 @@ describe("SpecialJsonSchema", () => {
|
|
|
229
679
|
dialect: "draft-2020-12",
|
|
230
680
|
schema: {
|
|
231
681
|
"type": "object",
|
|
682
|
+
"additionalProperties": true,
|
|
232
683
|
"properties": {
|
|
233
684
|
"status": { "type": "string" }
|
|
234
|
-
}
|
|
235
|
-
"additionalProperties": false
|
|
685
|
+
}
|
|
236
686
|
},
|
|
237
687
|
definitions: {}
|
|
238
688
|
})
|
|
239
689
|
})
|
|
240
690
|
|
|
241
691
|
it("identifies X universally — deduplicates same-fingerprint references", () => {
|
|
242
|
-
const X =
|
|
692
|
+
const X = S.String.annotate({ title: "X", identifier: "X" })
|
|
243
693
|
|
|
244
|
-
const s =
|
|
245
|
-
a:
|
|
246
|
-
|
|
694
|
+
const s = S.Struct({
|
|
695
|
+
a: S.NullOr(X).pipe(
|
|
696
|
+
S.encodeTo(S.optionalKey(X), {
|
|
247
697
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
248
698
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
249
699
|
})
|
|
250
700
|
),
|
|
251
|
-
b:
|
|
252
|
-
|
|
701
|
+
b: S.NullOr(X).pipe(
|
|
702
|
+
S.encodeTo(S.optionalKey(X), {
|
|
253
703
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
254
704
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
255
705
|
})
|
|
256
706
|
),
|
|
257
|
-
c:
|
|
707
|
+
c: S.NullOr(X),
|
|
258
708
|
d: X,
|
|
259
|
-
e: X.pipe(
|
|
709
|
+
e: X.pipe(S.optionalKey)
|
|
260
710
|
})
|
|
261
711
|
|
|
262
712
|
const doc = specialJsonSchemaDocument(s)
|
|
@@ -264,6 +714,7 @@ describe("SpecialJsonSchema", () => {
|
|
|
264
714
|
dialect: "draft-2020-12",
|
|
265
715
|
schema: {
|
|
266
716
|
"type": "object",
|
|
717
|
+
"additionalProperties": true,
|
|
267
718
|
"properties": {
|
|
268
719
|
"a": { "$ref": "#/$defs/X" },
|
|
269
720
|
"b": { "$ref": "#/$defs/X" },
|
|
@@ -276,8 +727,7 @@ describe("SpecialJsonSchema", () => {
|
|
|
276
727
|
"d": { "$ref": "#/$defs/X" },
|
|
277
728
|
"e": { "$ref": "#/$defs/X" }
|
|
278
729
|
},
|
|
279
|
-
"required": ["c", "d"]
|
|
280
|
-
"additionalProperties": false
|
|
730
|
+
"required": ["c", "d"]
|
|
281
731
|
},
|
|
282
732
|
definitions: {
|
|
283
733
|
X: {
|
|
@@ -289,16 +739,16 @@ describe("SpecialJsonSchema", () => {
|
|
|
289
739
|
})
|
|
290
740
|
|
|
291
741
|
it("shared annotated schema via helper — deduplicates", () => {
|
|
292
|
-
const X =
|
|
742
|
+
const X = S.String.annotate({ title: "X", identifier: "X" })
|
|
293
743
|
|
|
294
744
|
const cache = new WeakMap()
|
|
295
|
-
const nullableDecodedUndefinedEncoded = (schema:
|
|
745
|
+
const nullableDecodedUndefinedEncoded = (schema: S.Top) => {
|
|
296
746
|
const isNullableSchema = "members" in schema
|
|
297
747
|
&& globalThis.Array.isArray((schema as any).members)
|
|
298
748
|
&& (schema as any).members.length === 2
|
|
299
749
|
&& (schema as any).members.some((member: any) => member.ast._tag === "Null")
|
|
300
750
|
|
|
301
|
-
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<
|
|
751
|
+
const nullableMembers = isNullableSchema ? (schema as any).members as ReadonlyArray<S.Top> : undefined
|
|
302
752
|
const innerSchema = nullableMembers
|
|
303
753
|
? nullableMembers.find((member: any) => member.ast._tag !== "Null")!
|
|
304
754
|
: schema
|
|
@@ -306,9 +756,9 @@ describe("SpecialJsonSchema", () => {
|
|
|
306
756
|
const cached = cache.get(innerSchema.ast)
|
|
307
757
|
if (cached !== undefined) return cached
|
|
308
758
|
|
|
309
|
-
const nullableSchema = isNullableSchema ? schema :
|
|
759
|
+
const nullableSchema = isNullableSchema ? schema : S.NullOr(schema)
|
|
310
760
|
const out = nullableSchema.pipe(
|
|
311
|
-
|
|
761
|
+
S.encodeTo(S.optionalKey(innerSchema), {
|
|
312
762
|
decode: SchemaGetter.transformOptional(Option.orElseSome(() => null)),
|
|
313
763
|
encode: SchemaGetter.transformOptional(Option.filter(Predicate.isNotNull))
|
|
314
764
|
})
|
|
@@ -318,12 +768,12 @@ describe("SpecialJsonSchema", () => {
|
|
|
318
768
|
return out
|
|
319
769
|
}
|
|
320
770
|
|
|
321
|
-
const structWithShared =
|
|
771
|
+
const structWithShared = S.Struct({
|
|
322
772
|
a: nullableDecodedUndefinedEncoded(X),
|
|
323
|
-
b: nullableDecodedUndefinedEncoded(
|
|
324
|
-
c:
|
|
773
|
+
b: nullableDecodedUndefinedEncoded(S.NullOr(X)),
|
|
774
|
+
c: S.NullOr(X),
|
|
325
775
|
d: X,
|
|
326
|
-
e: X.pipe(
|
|
776
|
+
e: X.pipe(S.optionalKey)
|
|
327
777
|
})
|
|
328
778
|
|
|
329
779
|
const doc = specialJsonSchemaDocument(structWithShared)
|
|
@@ -331,6 +781,7 @@ describe("SpecialJsonSchema", () => {
|
|
|
331
781
|
dialect: "draft-2020-12",
|
|
332
782
|
schema: {
|
|
333
783
|
"type": "object",
|
|
784
|
+
"additionalProperties": true,
|
|
334
785
|
"properties": {
|
|
335
786
|
"a": { "$ref": "#/$defs/X" },
|
|
336
787
|
"b": { "$ref": "#/$defs/X" },
|
|
@@ -343,8 +794,7 @@ describe("SpecialJsonSchema", () => {
|
|
|
343
794
|
"d": { "$ref": "#/$defs/X" },
|
|
344
795
|
"e": { "$ref": "#/$defs/X" }
|
|
345
796
|
},
|
|
346
|
-
"required": ["c", "d"]
|
|
347
|
-
"additionalProperties": false
|
|
797
|
+
"required": ["c", "d"]
|
|
348
798
|
},
|
|
349
799
|
definitions: {
|
|
350
800
|
X: {
|
|
@@ -658,8 +1108,7 @@ describe("Post-processing integration — real Effect Schema types", () => {
|
|
|
658
1108
|
const doc = specialJsonSchemaDocument(AppSchema.PositiveInt)
|
|
659
1109
|
expect(doc.definitions["PositiveInt"]).toStrictEqual({
|
|
660
1110
|
type: "integer",
|
|
661
|
-
exclusiveMinimum: 0
|
|
662
|
-
title: "PositiveInt"
|
|
1111
|
+
exclusiveMinimum: 0
|
|
663
1112
|
})
|
|
664
1113
|
})
|
|
665
1114
|
|
|
@@ -668,8 +1117,7 @@ describe("Post-processing integration — real Effect Schema types", () => {
|
|
|
668
1117
|
expect(doc.definitions["NonEmptyString255"]).toStrictEqual({
|
|
669
1118
|
type: "string",
|
|
670
1119
|
minLength: 1,
|
|
671
|
-
maxLength: 255
|
|
672
|
-
title: "NonEmptyString255"
|
|
1120
|
+
maxLength: 255
|
|
673
1121
|
})
|
|
674
1122
|
})
|
|
675
1123
|
|
|
@@ -680,6 +1128,7 @@ describe("Post-processing integration — real Effect Schema types", () => {
|
|
|
680
1128
|
// null variant preserved (correct JSON Schema for NullOr)
|
|
681
1129
|
expect(doc.schema).toStrictEqual({
|
|
682
1130
|
type: "object",
|
|
1131
|
+
additionalProperties: true,
|
|
683
1132
|
properties: {
|
|
684
1133
|
note: {
|
|
685
1134
|
anyOf: [
|
|
@@ -688,16 +1137,14 @@ describe("Post-processing integration — real Effect Schema types", () => {
|
|
|
688
1137
|
]
|
|
689
1138
|
}
|
|
690
1139
|
},
|
|
691
|
-
required: ["note"]
|
|
692
|
-
additionalProperties: false
|
|
1140
|
+
required: ["note"]
|
|
693
1141
|
})
|
|
694
1142
|
|
|
695
1143
|
// allOf flattened in the referenced definition
|
|
696
1144
|
expect(doc.definitions["NonEmptyString64k"]).toStrictEqual({
|
|
697
1145
|
type: "string",
|
|
698
1146
|
minLength: 1,
|
|
699
|
-
maxLength: 65536
|
|
700
|
-
title: "NonEmptyString64k"
|
|
1147
|
+
maxLength: 65536
|
|
701
1148
|
})
|
|
702
1149
|
})
|
|
703
1150
|
|
|
@@ -705,14 +1152,13 @@ describe("Post-processing integration — real Effect Schema types", () => {
|
|
|
705
1152
|
const doc = specialJsonSchemaDocument(AppSchema.NonNegativeInt)
|
|
706
1153
|
expect(doc.definitions["NonNegativeInt"]).toStrictEqual({
|
|
707
1154
|
type: "integer",
|
|
708
|
-
minimum: 0
|
|
709
|
-
title: "NonNegativeInt"
|
|
1155
|
+
minimum: 0
|
|
710
1156
|
})
|
|
711
1157
|
})
|
|
712
1158
|
|
|
713
|
-
it("NullOr union
|
|
714
|
-
const A = S.String.annotate({ identifier: "A"
|
|
715
|
-
const B = S.Boolean.annotate({ identifier: "B"
|
|
1159
|
+
it("NullOr union flattens nested anyOf members", () => {
|
|
1160
|
+
const A = S.String.annotate({ identifier: "A" })
|
|
1161
|
+
const B = S.Boolean.annotate({ identifier: "B" })
|
|
716
1162
|
const schema = S.Struct({
|
|
717
1163
|
value: S.NullOr(S.Union([A, B]))
|
|
718
1164
|
})
|
|
@@ -721,9 +1167,95 @@ describe("Post-processing integration — real Effect Schema types", () => {
|
|
|
721
1167
|
|
|
722
1168
|
expect(valueProp).toStrictEqual({
|
|
723
1169
|
anyOf: [
|
|
724
|
-
{
|
|
1170
|
+
{ $ref: "#/$defs/A" },
|
|
1171
|
+
{ $ref: "#/$defs/B" },
|
|
725
1172
|
{ type: "null" }
|
|
726
1173
|
]
|
|
727
1174
|
})
|
|
728
1175
|
})
|
|
729
1176
|
})
|
|
1177
|
+
|
|
1178
|
+
describe("flattenNestedAnyOf", () => {
|
|
1179
|
+
it("flattens nested anyOf with no sibling keys", () => {
|
|
1180
|
+
const input = {
|
|
1181
|
+
anyOf: [
|
|
1182
|
+
{ anyOf: [{ type: "string" }, { type: "number" }] },
|
|
1183
|
+
{ type: "null" }
|
|
1184
|
+
]
|
|
1185
|
+
}
|
|
1186
|
+
expect(flattenNestedAnyOf(input)).toStrictEqual({
|
|
1187
|
+
anyOf: [
|
|
1188
|
+
{ type: "string" },
|
|
1189
|
+
{ type: "number" },
|
|
1190
|
+
{ type: "null" }
|
|
1191
|
+
]
|
|
1192
|
+
})
|
|
1193
|
+
})
|
|
1194
|
+
|
|
1195
|
+
it("does not flatten anyOf entry with sibling keys", () => {
|
|
1196
|
+
const input = {
|
|
1197
|
+
anyOf: [
|
|
1198
|
+
{ anyOf: [{ type: "string" }], title: "X" },
|
|
1199
|
+
{ type: "null" }
|
|
1200
|
+
]
|
|
1201
|
+
}
|
|
1202
|
+
// The inner anyOf is not flattened into the outer (sibling "title" prevents it),
|
|
1203
|
+
// but the single-element inner anyOf is unwrapped within the entry itself
|
|
1204
|
+
expect(flattenNestedAnyOf(input)).toStrictEqual({
|
|
1205
|
+
anyOf: [
|
|
1206
|
+
{ type: "string", title: "X" },
|
|
1207
|
+
{ type: "null" }
|
|
1208
|
+
]
|
|
1209
|
+
})
|
|
1210
|
+
})
|
|
1211
|
+
|
|
1212
|
+
it("unwraps anyOf with single item after flattening", () => {
|
|
1213
|
+
const input = {
|
|
1214
|
+
anyOf: [
|
|
1215
|
+
{ anyOf: [{ type: "string" }] }
|
|
1216
|
+
]
|
|
1217
|
+
}
|
|
1218
|
+
expect(flattenNestedAnyOf(input)).toStrictEqual({ type: "string" })
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
it("unwraps anyOf with single item, merging sibling properties", () => {
|
|
1222
|
+
const input = {
|
|
1223
|
+
title: "MyField",
|
|
1224
|
+
anyOf: [{ type: "string" }]
|
|
1225
|
+
}
|
|
1226
|
+
expect(flattenNestedAnyOf(input)).toStrictEqual({
|
|
1227
|
+
title: "MyField",
|
|
1228
|
+
type: "string"
|
|
1229
|
+
})
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
it("recurses into nested objects", () => {
|
|
1233
|
+
const input = {
|
|
1234
|
+
properties: {
|
|
1235
|
+
field: {
|
|
1236
|
+
anyOf: [
|
|
1237
|
+
{ anyOf: [{ $ref: "#/defs/A" }, { $ref: "#/defs/B" }] },
|
|
1238
|
+
{ type: "null" }
|
|
1239
|
+
]
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
expect(flattenNestedAnyOf(input)).toStrictEqual({
|
|
1244
|
+
properties: {
|
|
1245
|
+
field: {
|
|
1246
|
+
anyOf: [
|
|
1247
|
+
{ $ref: "#/defs/A" },
|
|
1248
|
+
{ $ref: "#/defs/B" },
|
|
1249
|
+
{ type: "null" }
|
|
1250
|
+
]
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
})
|
|
1254
|
+
})
|
|
1255
|
+
|
|
1256
|
+
it("passes through non-objects unchanged", () => {
|
|
1257
|
+
expect(flattenNestedAnyOf(null)).toBe(null)
|
|
1258
|
+
expect(flattenNestedAnyOf(42)).toBe(42)
|
|
1259
|
+
expect(flattenNestedAnyOf("hello")).toBe("hello")
|
|
1260
|
+
})
|
|
1261
|
+
})
|