effect-app 4.0.0-beta.6 → 4.0.0-beta.60
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 +265 -0
- 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/{ServiceMap.d.ts → Context.d.ts} +9 -12
- package/dist/Context.d.ts.map +1 -0
- package/dist/Context.js +87 -0
- package/dist/Effect.d.ts +8 -7
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -2
- package/dist/Layer.d.ts +5 -4
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +1 -1
- package/dist/Operations.d.ts +51 -15
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Pure.d.ts +2 -2
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +13 -13
- package/dist/Schema/Class.d.ts +39 -1
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +89 -12
- package/dist/Schema/SpecialJsonSchema.d.ts +40 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +199 -0
- package/dist/Schema/SpecialOpenApi.d.ts +30 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +120 -0
- package/dist/Schema/brand.d.ts +8 -5
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +1 -1
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +4 -3
- package/dist/Schema/ext.d.ts +142 -44
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +145 -35
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +6 -4
- package/dist/Schema/numbers.d.ts +8 -8
- package/dist/Schema/numbers.js +2 -2
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +3 -2
- package/dist/Schema.d.ts +21 -54
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +43 -64
- package/dist/client/apiClientFactory.d.ts +3 -3
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +12 -13
- package/dist/client/errors.d.ts +8 -0
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +35 -10
- package/dist/client/makeClient.d.ts +13 -12
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +5 -2
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/ids.d.ts +1 -1
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +1 -1
- package/dist/index.d.ts +6 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -9
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +3 -3
- package/dist/rpc/MiddlewareMaker.d.ts +4 -3
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +6 -5
- package/dist/rpc/RpcContextMap.d.ts +2 -2
- package/dist/rpc/RpcContextMap.d.ts.map +1 -1
- package/dist/rpc/RpcContextMap.js +4 -4
- package/dist/rpc/RpcMiddleware.d.ts +4 -3
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.js +1 -1
- package/dist/utils/gen.d.ts +1 -1
- package/dist/utils/gen.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +2 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -3
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -5
- package/package.json +29 -17
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/{ServiceMap.ts → Context.ts} +16 -17
- package/src/Effect.ts +11 -9
- package/src/Layer.ts +5 -4
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +114 -16
- package/src/Schema/SpecialJsonSchema.ts +216 -0
- package/src/Schema/SpecialOpenApi.ts +126 -0
- package/src/Schema/brand.ts +13 -7
- package/src/Schema/email.ts +4 -2
- package/src/Schema/ext.ts +222 -57
- package/src/Schema/moreStrings.ts +10 -6
- package/src/Schema/numbers.ts +2 -2
- package/src/Schema/phoneNumber.ts +3 -1
- package/src/Schema.ts +79 -103
- package/src/client/apiClientFactory.ts +16 -19
- package/src/client/errors.ts +46 -12
- package/src/client/makeClient.ts +32 -12
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +1 -1
- package/src/index.ts +6 -9
- package/src/middleware.ts +2 -2
- package/src/rpc/MiddlewareMaker.ts +7 -6
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +5 -4
- package/src/utils/gen.ts +1 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils.ts +26 -4
- package/test/dist/moreStrings.test.d.ts.map +1 -0
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/dist/special.test.d.ts.map +1 -0
- package/test/moreStrings.test.ts +17 -0
- package/test/rpc.test.ts +26 -5
- package/test/schema.test.ts +292 -1
- package/test/special.test.ts +525 -0
- package/test/utils.test.ts +1 -1
- package/tsconfig.base.json +0 -1
- package/tsconfig.json +0 -1
- package/dist/ServiceMap.d.ts.map +0 -1
- package/dist/ServiceMap.js +0 -91
- package/dist/Struct.d.ts +0 -44
- package/dist/Struct.d.ts.map +0 -1
- package/dist/Struct.js +0 -29
- package/src/Struct.ts +0 -54
package/src/index.ts
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
1
|
import "./builtin.js"
|
|
2
2
|
|
|
3
|
-
import * as
|
|
3
|
+
import * as Context from "./Context.js"
|
|
4
4
|
|
|
5
5
|
export * as Fnc from "./Function.js"
|
|
6
6
|
export * as Utils from "./utils.js"
|
|
7
7
|
|
|
8
8
|
export * as Array from "./Array.js"
|
|
9
|
+
export * as Config from "./Config.js"
|
|
10
|
+
export * as ConfigProvider from "./ConfigProvider.js"
|
|
11
|
+
export * as Context from "./Context.js"
|
|
9
12
|
export * as Effect from "./Effect.js"
|
|
10
13
|
export * as Layer from "./Layer.js"
|
|
11
14
|
export * as NonEmptySet from "./NonEmptySet.js"
|
|
12
|
-
export * as ServiceMap from "./ServiceMap.js"
|
|
13
15
|
export * as Set from "./Set.js"
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* @deprecated use ServiceMap directly instead
|
|
18
|
-
*/
|
|
19
|
-
ServiceMap as Context
|
|
20
|
-
}
|
|
17
|
+
/** @deprecated Use `Context` instead */
|
|
18
|
+
export { Context as ServiceMap }
|
|
21
19
|
|
|
22
20
|
export { type NonEmptyArray, type NonEmptyReadonlyArray } from "./Array.js"
|
|
23
21
|
|
|
24
22
|
export * from "effect"
|
|
25
23
|
|
|
26
|
-
export * as Struct from "./Struct.js"
|
|
27
24
|
export type * as Types from "./Types.js"
|
|
28
25
|
|
|
29
26
|
export * as SecretURL from "./Config/SecretURL.js"
|
package/src/middleware.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import {
|
|
2
|
+
import { Context } from "effect-app"
|
|
3
3
|
import { RpcX } from "./rpc.js"
|
|
4
4
|
|
|
5
|
-
export class DevMode extends
|
|
5
|
+
export class DevMode extends Context.Reference("DevMode", { defaultValue: () => false }) {}
|
|
6
6
|
|
|
7
7
|
export class RequestCacheMiddleware
|
|
8
8
|
extends RpcX.RpcMiddleware.Tag<RequestCacheMiddleware>()("RequestCacheMiddleware")
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { Effect, Layer, type Schema, Schema as S, type Scope
|
|
2
|
+
import { Effect, Layer, type Schema, Schema as S, type Scope } from "effect"
|
|
3
3
|
import { type NonEmptyArray, type NonEmptyReadonlyArray } from "effect/Array"
|
|
4
4
|
import { type Simplify } from "effect/Types"
|
|
5
5
|
import { Rpc, type RpcGroup, type RpcSchema } from "effect/unstable/rpc"
|
|
6
6
|
import { type HandlersFrom } from "effect/unstable/rpc/RpcGroup"
|
|
7
7
|
import { type RequestId } from "effect/unstable/rpc/RpcMessage"
|
|
8
|
+
import * as Context from "../Context.js"
|
|
8
9
|
import { type HttpHeaders } from "../http.js"
|
|
9
10
|
import { PreludeLogger } from "../logger.js"
|
|
10
11
|
import { type TypeTestId } from "../TypeTest.js"
|
|
@@ -61,13 +62,13 @@ export interface MiddlewareMaker<
|
|
|
61
62
|
}
|
|
62
63
|
>
|
|
63
64
|
{
|
|
64
|
-
readonly layer: Layer.Layer<Self, never,
|
|
65
|
+
readonly layer: Layer.Layer<Self, never, Context.Service.Identifier<MiddlewareProviders[number]>>
|
|
65
66
|
readonly requestContext: RequestContextTag<RequestContextMap>
|
|
66
67
|
readonly requestContextMap: RequestContextMap
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
export interface RequestContextTag<RequestContextMap extends Record<string, RpcContextMap.Any>>
|
|
70
|
-
extends
|
|
71
|
+
extends Context.Service<"RequestContextConfig", GetContextConfig<RequestContextMap>>
|
|
71
72
|
{}
|
|
72
73
|
|
|
73
74
|
export namespace MiddlewareMaker {
|
|
@@ -295,7 +296,7 @@ const middlewareMaker = <
|
|
|
295
296
|
// inspired from Effect/RpcMiddleware
|
|
296
297
|
for (const tag of middlewares) {
|
|
297
298
|
// use the tag to get the middleware from context
|
|
298
|
-
const middleware =
|
|
299
|
+
const middleware = Context.getUnsafe(context, tag)
|
|
299
300
|
|
|
300
301
|
// wrap the current handler, allowing the middleware to run before and after it
|
|
301
302
|
handler = PreludeLogger.logDebug("Applying middleware wrap " + tag.key).pipe(
|
|
@@ -367,7 +368,7 @@ const makeMiddlewareBasic = <Self>() =>
|
|
|
367
368
|
return Object.assign(MiddlewareMaker, {
|
|
368
369
|
layer,
|
|
369
370
|
// tag to be used to retrieve the RequestContextConfig from Rpc annotations
|
|
370
|
-
requestContext:
|
|
371
|
+
requestContext: Context.Service<"RequestContextConfig", GetContextConfig<RequestContextMap>>(
|
|
371
372
|
"RequestContextConfig"
|
|
372
373
|
),
|
|
373
374
|
requestContextMap: rcm
|
|
@@ -380,7 +381,7 @@ export const Tag = <Self>() =>
|
|
|
380
381
|
RequestContextMap extends RequestContextMapTagAny
|
|
381
382
|
>(id: Id, rcm: RequestContextMap): MiddlewaresBuilder<Self, Id, RequestContextMap["config"]> => {
|
|
382
383
|
let allMiddleware: MiddlewareMaker.Any[] = []
|
|
383
|
-
const requestContext =
|
|
384
|
+
const requestContext = Context.Service<"RequestContextConfig", GetContextConfig<RequestContextMap["config"]>>(
|
|
384
385
|
"RequestContextConfig"
|
|
385
386
|
)
|
|
386
387
|
const it = {
|
package/src/rpc/RpcContextMap.ts
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
4
|
|
|
5
|
-
import { type Schema as S
|
|
5
|
+
import { type Schema as S } from "effect"
|
|
6
6
|
import { type AnyWithProps } from "effect/unstable/rpc/Rpc"
|
|
7
|
+
import * as Context from "../Context.js"
|
|
7
8
|
import { type RpcDynamic } from "./RpcMiddleware.js"
|
|
8
9
|
|
|
9
10
|
type Values<T extends Record<any, any>> = T[keyof T]
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* Middleware is inactivate by default, the Key is optional in route context, and the service is optionally provided as Effect
|
|
13
|
+
* Middleware is inactivate by default, the Key is optional in route context, and the service is optionally provided as Effect Context.
|
|
13
14
|
* Unless explicitly configured as `true`.
|
|
14
15
|
*/
|
|
15
16
|
export type RpcContextMap<Service, E> = {
|
|
@@ -22,7 +23,7 @@ export type RpcContextMap<Service, E> = {
|
|
|
22
23
|
|
|
23
24
|
export declare namespace RpcContextMap {
|
|
24
25
|
/**
|
|
25
|
-
* Middleware is active by default, and provides the Service at Key in route context, and the Service is provided as Effect
|
|
26
|
+
* Middleware is active by default, and provides the Service at Key in route context, and the Service is provided as Effect Context.
|
|
26
27
|
* Unless explicitly omitted.
|
|
27
28
|
*/
|
|
28
29
|
export type Inverted<Service, E> = {
|
|
@@ -97,7 +98,7 @@ export type GetEffectError<RequestContextMap extends Record<string, RpcContextMa
|
|
|
97
98
|
}
|
|
98
99
|
>
|
|
99
100
|
|
|
100
|
-
const tag =
|
|
101
|
+
const tag = Context.Service("RequestContextConfig")
|
|
101
102
|
|
|
102
103
|
export const makeMap = <const Config extends Record<string, RpcContextMap.Any>>(config: Config) => {
|
|
103
104
|
const cls = class {
|
|
@@ -109,7 +110,7 @@ export const makeMap = <const Config extends Record<string, RpcContextMap.Any>>(
|
|
|
109
110
|
return Object.assign(cls, {
|
|
110
111
|
config, /** Retrieves RequestContextConfig out of the Rpc annotations */
|
|
111
112
|
getConfig: (rpc: AnyWithProps): GetContextConfig<Config> => {
|
|
112
|
-
return
|
|
113
|
+
return Context.getOrElse(rpc.annotations, tag as any, () => ({}))
|
|
113
114
|
},
|
|
114
115
|
/** Adapter used when setting the dynamic prop on a middleware implementation */
|
|
115
116
|
get: <
|
package/src/rpc/RpcMiddleware.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
4
|
-
import { type Effect, type Schema, type Schema as S, type Scope, type
|
|
4
|
+
import { type Effect, type Schema, type Schema as S, type Scope, type Stream } from "effect"
|
|
5
5
|
import { type NonEmptyReadonlyArray } from "effect/Array"
|
|
6
6
|
import { type Rpc, RpcMiddleware } from "effect/unstable/rpc"
|
|
7
7
|
import { type TypeId } from "effect/unstable/rpc/RpcMiddleware"
|
|
8
|
+
import type * as Context from "../Context.js"
|
|
8
9
|
import { type GetEffectContext, type RpcContextMap } from "./RpcContextMap.js"
|
|
9
10
|
|
|
10
11
|
export type RpcMiddlewareV4<Provides, E, Requires> = RpcMiddleware.RpcMiddleware<Provides, E, Requires>
|
|
@@ -102,8 +103,8 @@ export declare namespace TagClass {
|
|
|
102
103
|
requires?: any
|
|
103
104
|
provides?: any
|
|
104
105
|
}
|
|
105
|
-
> extends
|
|
106
|
-
new(_: never):
|
|
106
|
+
> extends Context.Service<Self, Service> {
|
|
107
|
+
new(_: never): Context.ServiceClass.Shape<Name, Service>
|
|
107
108
|
readonly [TypeId]: TypeId
|
|
108
109
|
readonly optional: Optional<Options>
|
|
109
110
|
readonly error: FailureSchema<Options>
|
|
@@ -226,7 +227,7 @@ export type ExtractProvides<R extends Rpc.Any, Tag extends string> = R extends
|
|
|
226
227
|
Rpc.Rpc<Tag, infer _Payload, infer _Success, infer _Error, infer _Middleware, infer _Requires> ? _Middleware extends {
|
|
227
228
|
readonly provides: infer _P
|
|
228
229
|
} ? [_P] extends [never] ? never
|
|
229
|
-
: _P /*_P extends
|
|
230
|
+
: _P /*_P extends Context.Service<infer _I, infer _S> ? _I
|
|
230
231
|
: never */
|
|
231
232
|
: never
|
|
232
233
|
: never
|
package/src/utils/gen.ts
CHANGED
|
@@ -15,7 +15,7 @@ export namespace EffectGenUtils {
|
|
|
15
15
|
: EG extends (..._: infer _3) => Generator<Yieldable<any, infer _, infer E, infer _R>, infer _A, infer _2> ? E
|
|
16
16
|
: never
|
|
17
17
|
|
|
18
|
-
export type
|
|
18
|
+
export type Context<EG> = EG extends Effect<infer _A, infer _E, infer R> ? R
|
|
19
19
|
// there could be a case where the generator function does not yield anything, so we need to handle that
|
|
20
20
|
: EG extends (..._: infer _3) => Generator<never, infer _A, infer _2> ? never
|
|
21
21
|
// v4: generators can yield Yieldable (Effect, Service, etc.), all have asEffect()
|
package/src/utils/logger.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
3
|
|
|
4
4
|
import { Effect, type LogLevel } from "effect"
|
|
5
|
-
import * as
|
|
5
|
+
import * as Context from "../Context.js"
|
|
6
6
|
|
|
7
7
|
type Levels = "info" | "debug" | "warn" | "error"
|
|
8
8
|
|
|
9
|
-
export class LogLevels extends
|
|
9
|
+
export class LogLevels extends Context.Reference("LogLevels", {
|
|
10
10
|
defaultValue: () => new Map<string, Levels>()
|
|
11
11
|
}) {}
|
|
12
12
|
|
package/src/utils.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-redundant-type-constituents */
|
|
4
|
-
import { Effect, Exit, Fiber, Option, Record } from "effect"
|
|
4
|
+
import { Cause, Effect, Exit, Fiber, Option, Record } from "effect"
|
|
5
5
|
import { dual } from "effect/Function"
|
|
6
|
-
import { isFunction } from "effect/Predicate"
|
|
6
|
+
import { isFunction, isObject } from "effect/Predicate"
|
|
7
7
|
import * as Result from "effect/Result"
|
|
8
8
|
import type { GetFieldType, NumericDictionary, PropertyPath } from "lodash"
|
|
9
9
|
import { identity, pipe } from "./Function.js"
|
|
@@ -924,8 +924,8 @@ export const runtimeFiberAsPromise = <A, E>(fiber: Fiber.Fiber<A, E>, signal?: A
|
|
|
924
924
|
if (Exit.isSuccess(exit)) {
|
|
925
925
|
resolve(exit.value)
|
|
926
926
|
} else {
|
|
927
|
-
//
|
|
928
|
-
reject(exit.cause)
|
|
927
|
+
// eslint-disable-next-line
|
|
928
|
+
reject(Cause.squash(exit.cause))
|
|
929
929
|
}
|
|
930
930
|
})
|
|
931
931
|
)
|
|
@@ -950,3 +950,25 @@ export type UnionToTuples<T, U = T> = [T] extends [never] ? []
|
|
|
950
950
|
| [T, ...UnionToTuples<Exclude<U, T>>]
|
|
951
951
|
| UnionToTuples<Exclude<U, T>>
|
|
952
952
|
: []
|
|
953
|
+
|
|
954
|
+
const genConstructor = (function*() {}).constructor
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* @example
|
|
958
|
+
* ```ts
|
|
959
|
+
* import { Utils } from "effect"
|
|
960
|
+
*
|
|
961
|
+
* function* generatorFn() {
|
|
962
|
+
* yield 1
|
|
963
|
+
* yield 2
|
|
964
|
+
* }
|
|
965
|
+
*
|
|
966
|
+
* console.log(Utils.isGeneratorFunction(generatorFn)) // true
|
|
967
|
+
* console.log(Utils.isGeneratorFunction(() => {})) // false
|
|
968
|
+
* ```
|
|
969
|
+
*
|
|
970
|
+
* @category predicates
|
|
971
|
+
* @since 3.11.0
|
|
972
|
+
*/
|
|
973
|
+
export const isGeneratorFunction = (u: unknown): u is (...args: Array<any>) => Generator<any, any, any> =>
|
|
974
|
+
isObject(u) && u.constructor === genConstructor
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"moreStrings.test.d.ts","sourceRoot":"","sources":["../moreStrings.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.test.d.ts","sourceRoot":"","sources":["../rpc.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rpc.test.d.ts","sourceRoot":"","sources":["../rpc.test.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAErF,OAAO,EAAE,CAAC,EAAE,MAAM,iBAAiB,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;;;;;;;;;;;;;;;;;;;;;;;;AAE7C,qBAAa,iBAAkB,SAAQ,sBAIrC;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIL,qBAAa,KAAM,SAAQ,UAQzB;CAAG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"special.test.d.ts","sourceRoot":"","sources":["../special.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { S } from "effect-app"
|
|
2
|
+
import * as fc from "fast-check"
|
|
3
|
+
import { urlAlphabet } from "nanoid"
|
|
4
|
+
import { test } from "vitest"
|
|
5
|
+
|
|
6
|
+
const nanoidAlphabet = new Set(urlAlphabet)
|
|
7
|
+
|
|
8
|
+
const isNanoId = (value: string) => value.length === 21 && Array.from(value).every((char) => nanoidAlphabet.has(char))
|
|
9
|
+
|
|
10
|
+
test("StringId arbitrary generates nanoid-shaped values", () => {
|
|
11
|
+
fc.assert(
|
|
12
|
+
fc.property(S.toArbitrary(S.StringId), (value) => {
|
|
13
|
+
expect(isNanoId(value)).toBe(true)
|
|
14
|
+
expect(S.is(S.StringId)(value)).toBe(true)
|
|
15
|
+
})
|
|
16
|
+
)
|
|
17
|
+
})
|
package/test/rpc.test.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
1
2
|
import { makeRpcClient, NotLoggedInError, UnauthorizedError } from "../src/client.js"
|
|
3
|
+
import { ForceVoid } from "../src/client/makeClient.js"
|
|
2
4
|
import { S } from "../src/index.js"
|
|
3
5
|
import { RpcContextMap } from "../src/rpc.js"
|
|
4
6
|
|
|
@@ -13,11 +15,30 @@ const { TaggedRequest } = makeRpcClient(RequestContextMap)
|
|
|
13
15
|
export class Stats extends TaggedRequest<Stats>()("Stats", {}, {
|
|
14
16
|
allowedRoles: ["manager"],
|
|
15
17
|
success: {
|
|
16
|
-
usersActive24Hours: S.
|
|
17
|
-
usersActiveLastWeek: S.
|
|
18
|
-
newUsersLast24Hours: S.
|
|
19
|
-
newUsersLastWeek: S.
|
|
18
|
+
usersActive24Hours: S.Finite,
|
|
19
|
+
usersActiveLastWeek: S.Finite,
|
|
20
|
+
newUsersLast24Hours: S.Finite,
|
|
21
|
+
newUsersLastWeek: S.Finite
|
|
20
22
|
}
|
|
21
23
|
}) {}
|
|
22
24
|
|
|
23
|
-
declare const _stats: typeof Stats.
|
|
25
|
+
declare const _stats: typeof Stats.Type
|
|
26
|
+
declare const _statsSuccess: typeof Stats.success.Type
|
|
27
|
+
declare const _statsError: typeof Stats.error.Type
|
|
28
|
+
|
|
29
|
+
test("ForceVoid decodes and encodes as void", () => {
|
|
30
|
+
expect(S.decodeUnknownSync(ForceVoid)(undefined)).toBe(undefined)
|
|
31
|
+
expect(S.is(ForceVoid)(undefined)).toBe(true)
|
|
32
|
+
expect(S.decodeUnknownSync(ForceVoid)("test")).toBe(undefined)
|
|
33
|
+
expect(S.is(ForceVoid)("test")).toBe(true)
|
|
34
|
+
expect(S.encodeUnknownSync(ForceVoid)("test")).toBe(undefined)
|
|
35
|
+
expect(S.encodeUnknownSync(S.toCodecJson(ForceVoid))("test")).toBe(null)
|
|
36
|
+
expectTypeOf<typeof _stats>().toEqualTypeOf<Stats>()
|
|
37
|
+
expectTypeOf<typeof _statsSuccess>().toEqualTypeOf<{
|
|
38
|
+
readonly usersActive24Hours: number
|
|
39
|
+
readonly usersActiveLastWeek: number
|
|
40
|
+
readonly newUsersLast24Hours: number
|
|
41
|
+
readonly newUsersLastWeek: number
|
|
42
|
+
}>()
|
|
43
|
+
expectTypeOf<typeof _statsError>().toEqualTypeOf<NotLoggedInError | UnauthorizedError>()
|
|
44
|
+
})
|
package/test/schema.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// import { generateFromArbitrary } from "@effect-app/infra/test"
|
|
2
2
|
import { Array, S } from "effect-app"
|
|
3
|
-
import { test } from "vitest"
|
|
3
|
+
import { describe, expect, expectTypeOf, test } from "vitest"
|
|
4
4
|
|
|
5
5
|
const A = S.Struct({ a: S.NonEmptyString255, email: S.NullOr(S.Email) })
|
|
6
6
|
test("works", () => {
|
|
@@ -23,3 +23,294 @@ test("literal default works", () => {
|
|
|
23
23
|
const s2 = S.Struct({ l: l2.withDefault })
|
|
24
24
|
expect(s2.makeUnsafe({}).l).toBe("b")
|
|
25
25
|
})
|
|
26
|
+
|
|
27
|
+
test("tagged union derives tag map and tags from v4 literal ast", () => {
|
|
28
|
+
const schema = S.TaggedUnion(
|
|
29
|
+
S.TaggedStruct("A", { a: S.String }),
|
|
30
|
+
S.TaggedStruct("B", { b: S.Finite }),
|
|
31
|
+
S.TaggedStruct("C", { c: S.Boolean })
|
|
32
|
+
)
|
|
33
|
+
const caseA = schema.cases["A"]
|
|
34
|
+
const caseB = schema.cases["B"]
|
|
35
|
+
const caseC = schema.cases["C"]
|
|
36
|
+
const isAOrB = schema.isAnyOf(["A", "B"])
|
|
37
|
+
|
|
38
|
+
expect(caseA.fields._tag.ast.literal).toBe("A")
|
|
39
|
+
expect(caseB.fields._tag.ast.literal).toBe("B")
|
|
40
|
+
expect(caseC.fields._tag.ast.literal).toBe("C")
|
|
41
|
+
expect(S.decodeSync(schema.tags)("A")).toBe("A")
|
|
42
|
+
expect(S.decodeSync(schema.tags)("B")).toBe("B")
|
|
43
|
+
expect(S.decodeSync(schema.tags)("C")).toBe("C")
|
|
44
|
+
expect(() => S.decodeUnknownSync(schema.tags)("D")).toThrow()
|
|
45
|
+
|
|
46
|
+
expect(schema.guards.A({ _tag: "A", a: "ok" })).toBe(true)
|
|
47
|
+
expect(schema.guards.A({ _tag: "B", b: 1 })).toBe(false)
|
|
48
|
+
expect(schema.guards.B({ _tag: "B", b: 1 })).toBe(true)
|
|
49
|
+
expect(schema.guards.B({ _tag: "A", a: "ok" })).toBe(false)
|
|
50
|
+
expect(schema.guards.C({ _tag: "C", c: true })).toBe(true)
|
|
51
|
+
expect(schema.guards.C({ _tag: "A", a: "ok" })).toBe(false)
|
|
52
|
+
|
|
53
|
+
expect(isAOrB({ _tag: "A", a: "ok" })).toBe(true)
|
|
54
|
+
expect(isAOrB({ _tag: "B", b: 1 })).toBe(true)
|
|
55
|
+
expect(isAOrB({ _tag: "C", c: true })).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("TaggedUnion tags returns a Literals schema with correct literal values", () => {
|
|
59
|
+
const schema = S.TaggedUnion(
|
|
60
|
+
S.TaggedStruct("X", { x: S.String }),
|
|
61
|
+
S.TaggedStruct("Y", { y: S.Finite })
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
expect(schema.tags.literals).toEqual(["X", "Y"])
|
|
65
|
+
expectTypeOf(schema.tags.literals).toMatchTypeOf<readonly ["X", "Y"]>()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test("TaggedUnion tags.pick returns a subset of the tag literals", () => {
|
|
69
|
+
const schema = S.TaggedUnion(
|
|
70
|
+
S.TaggedStruct("A", { a: S.String }),
|
|
71
|
+
S.TaggedStruct("B", { b: S.Finite }),
|
|
72
|
+
S.TaggedStruct("C", { c: S.Boolean })
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const subset = schema.tags.pick(["A", "C"])
|
|
76
|
+
expect(subset.literals).toEqual(["A", "C"])
|
|
77
|
+
expect(S.decodeSync(subset)("A")).toBe("A")
|
|
78
|
+
expect(S.decodeSync(subset)("C")).toBe("C")
|
|
79
|
+
expect(() => S.decodeUnknownSync(subset)("B")).toThrow()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("tags standalone function extracts tags from member schemas", () => {
|
|
83
|
+
const members = [
|
|
84
|
+
S.TaggedStruct("Foo", { foo: S.String }),
|
|
85
|
+
S.TaggedStruct("Bar", { bar: S.Finite })
|
|
86
|
+
] as const
|
|
87
|
+
|
|
88
|
+
const tagSchema = S.tags(members)
|
|
89
|
+
expect(tagSchema.literals).toEqual(["Foo", "Bar"])
|
|
90
|
+
expect(S.decodeSync(tagSchema)("Foo")).toBe("Foo")
|
|
91
|
+
expect(S.decodeSync(tagSchema)("Bar")).toBe("Bar")
|
|
92
|
+
expect(() => S.decodeUnknownSync(tagSchema)("Baz")).toThrow()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test("ExtendTaggedUnion adds tags to an existing Union", () => {
|
|
96
|
+
const union = S.Union([
|
|
97
|
+
S.TaggedStruct("P", { p: S.String }),
|
|
98
|
+
S.TaggedStruct("Q", { q: S.Finite })
|
|
99
|
+
])
|
|
100
|
+
const extended = S.ExtendTaggedUnion(union)
|
|
101
|
+
|
|
102
|
+
expect(extended.tags.literals).toEqual(["P", "Q"])
|
|
103
|
+
expect(S.decodeSync(extended.tags)("P")).toBe("P")
|
|
104
|
+
expect(S.decodeSync(extended.tags)("Q")).toBe("Q")
|
|
105
|
+
expect(() => S.decodeUnknownSync(extended.tags)("R")).toThrow()
|
|
106
|
+
expect(extended.cases["P"].fields._tag.ast.literal).toBe("P")
|
|
107
|
+
expect(extended.guards.P({ _tag: "P", p: "ok" })).toBe(true)
|
|
108
|
+
expect(extended.guards.P({ _tag: "Q", q: 1 })).toBe(false)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("TaggedUnion match dispatches on _tag", () => {
|
|
112
|
+
const schema = S.TaggedUnion(
|
|
113
|
+
S.TaggedStruct("A", { a: S.String }),
|
|
114
|
+
S.TaggedStruct("B", { b: S.Finite })
|
|
115
|
+
)
|
|
116
|
+
type T = S.Schema.Type<typeof schema>
|
|
117
|
+
|
|
118
|
+
const matcher = schema.match({
|
|
119
|
+
A: (v) => `got A: ${v.a}`,
|
|
120
|
+
B: (v) => `got B: ${v.b}`
|
|
121
|
+
})
|
|
122
|
+
expect(matcher({ _tag: "A", a: "hello" } as T)).toBe("got A: hello")
|
|
123
|
+
expect(matcher({ _tag: "B", b: 42 } as T)).toBe("got B: 42")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test("TaggedUnion with single member", () => {
|
|
127
|
+
const schema = S.TaggedUnion(
|
|
128
|
+
S.TaggedStruct("Only", { val: S.String })
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
expect(schema.tags.literals).toEqual(["Only"])
|
|
132
|
+
expect(S.decodeSync(schema.tags)("Only")).toBe("Only")
|
|
133
|
+
expect(() => S.decodeUnknownSync(schema.tags)("Other")).toThrow()
|
|
134
|
+
expect(schema.guards.Only({ _tag: "Only", val: "x" })).toBe(true)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test("TaggedUnion tags type is narrowed to the exact tag literals", () => {
|
|
138
|
+
const schema = S.TaggedUnion(
|
|
139
|
+
S.TaggedStruct("Alpha", { a: S.String }),
|
|
140
|
+
S.TaggedStruct("Beta", { b: S.Finite }),
|
|
141
|
+
S.TaggedStruct("Gamma", { c: S.Boolean })
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
type Tags = S.Schema.Type<typeof schema.tags>
|
|
145
|
+
expectTypeOf<Tags>().toEqualTypeOf<"Alpha" | "Beta" | "Gamma">()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test("TaggedUnion with encodeKeys renaming a non-tag key", () => {
|
|
149
|
+
const MemberA = S.TaggedStruct("A", { firstName: S.String }).pipe(
|
|
150
|
+
S.encodeKeys({ firstName: "first_name" })
|
|
151
|
+
)
|
|
152
|
+
const MemberB = S.TaggedStruct("B", { lastName: S.Finite }).pipe(
|
|
153
|
+
S.encodeKeys({ lastName: "last_name" })
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const schema = S.TaggedUnion(MemberA, MemberB)
|
|
157
|
+
|
|
158
|
+
expect(schema.tags.literals).toEqual(["A", "B"])
|
|
159
|
+
expect(S.decodeSync(schema.tags)("A")).toBe("A")
|
|
160
|
+
expect(S.decodeSync(schema.tags)("B")).toBe("B")
|
|
161
|
+
|
|
162
|
+
// decode from encoded (snake_case) to decoded (camelCase)
|
|
163
|
+
const decoded = S.decodeUnknownSync(schema)({ _tag: "A", first_name: "Alice" })
|
|
164
|
+
expect(decoded).toEqual({ _tag: "A", firstName: "Alice" })
|
|
165
|
+
|
|
166
|
+
const decoded2 = S.decodeUnknownSync(schema)({ _tag: "B", last_name: 42 })
|
|
167
|
+
expect(decoded2).toEqual({ _tag: "B", lastName: 42 })
|
|
168
|
+
|
|
169
|
+
// encode back to snake_case
|
|
170
|
+
type T = S.Schema.Type<typeof schema>
|
|
171
|
+
const encoded = S.encodeSync(schema)({ _tag: "A", firstName: "Alice" } as T)
|
|
172
|
+
expect(encoded).toEqual({ _tag: "A", first_name: "Alice" })
|
|
173
|
+
|
|
174
|
+
// guards work on decoded values
|
|
175
|
+
expect(schema.guards.A({ _tag: "A", firstName: "Alice" })).toBe(true)
|
|
176
|
+
expect(schema.guards.A({ _tag: "B", lastName: 42 })).toBe(false)
|
|
177
|
+
expect(schema.guards.B({ _tag: "B", lastName: 42 })).toBe(true)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test("TaggedUnion with TaggedClass members", () => {
|
|
181
|
+
class Foo extends S.TaggedClass<Foo>()("Foo", { name: S.String }) {}
|
|
182
|
+
class Bar extends S.TaggedClass<Bar>()("Bar", { count: S.Finite }) {}
|
|
183
|
+
|
|
184
|
+
const schema = S.TaggedUnion(Foo, Bar)
|
|
185
|
+
|
|
186
|
+
expect(schema.tags.literals).toEqual(["Foo", "Bar"])
|
|
187
|
+
expect(S.decodeSync(schema.tags)("Foo")).toBe("Foo")
|
|
188
|
+
expect(S.decodeSync(schema.tags)("Bar")).toBe("Bar")
|
|
189
|
+
expect(() => S.decodeUnknownSync(schema.tags)("Baz")).toThrow()
|
|
190
|
+
|
|
191
|
+
const decoded = S.decodeUnknownSync(schema)({ _tag: "Foo", name: "Alice" })
|
|
192
|
+
expect(decoded).toBeInstanceOf(Foo)
|
|
193
|
+
expect(decoded).toEqual(new Foo({ name: "Alice" }))
|
|
194
|
+
|
|
195
|
+
const decoded2 = S.decodeUnknownSync(schema)({ _tag: "Bar", count: 3 })
|
|
196
|
+
expect(decoded2).toBeInstanceOf(Bar)
|
|
197
|
+
expect(decoded2).toEqual(new Bar({ count: 3 }))
|
|
198
|
+
|
|
199
|
+
expect(schema.guards.Foo(new Foo({ name: "Alice" }))).toBe(true)
|
|
200
|
+
expect(schema.guards.Foo(new Bar({ count: 3 }))).toBe(false)
|
|
201
|
+
expect(schema.guards.Bar(new Bar({ count: 3 }))).toBe(true)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
describe("ReadonlySetFromArray", () => {
|
|
205
|
+
test("decodes an array of strings to a Set", () => {
|
|
206
|
+
const schema = S.ReadonlySetFromArray(S.String)
|
|
207
|
+
const decoded = S.decodeUnknownSync(schema)(["a", "b", "c"])
|
|
208
|
+
expect(decoded).toEqual(new Set(["a", "b", "c"]))
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test("encodes a Set back to an array", () => {
|
|
212
|
+
const schema = S.ReadonlySetFromArray(S.String)
|
|
213
|
+
const encoded = S.encodeSync(schema)(new Set(["a", "b"]))
|
|
214
|
+
expect(encoded).toEqual(["a", "b"])
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test("decodes with NumberFromString as value", () => {
|
|
218
|
+
const schema = S.ReadonlySetFromArray(S.NumberFromString)
|
|
219
|
+
const decoded = S.decodeUnknownSync(schema)(["1", "2", "3"])
|
|
220
|
+
expect(decoded).toEqual(new Set([1, 2, 3]))
|
|
221
|
+
expectTypeOf(decoded).toEqualTypeOf<ReadonlySet<number>>()
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
test("encodes with NumberFromString as value", () => {
|
|
225
|
+
const schema = S.ReadonlySetFromArray(S.NumberFromString)
|
|
226
|
+
const encoded = S.encodeSync(schema)(new Set([1, 2, 3]))
|
|
227
|
+
expect(encoded).toEqual(["1", "2", "3"])
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test("rejects invalid input", () => {
|
|
231
|
+
const schema = S.ReadonlySetFromArray(S.NumberFromString)
|
|
232
|
+
expect(() => S.decodeUnknownSync(schema)([1, 2])).toThrow()
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe("ReadonlyMapFromArray", () => {
|
|
237
|
+
test("decodes an array of tuples to a Map", () => {
|
|
238
|
+
const schema = S.ReadonlyMap({ key: S.String, value: S.Finite })
|
|
239
|
+
const decoded = S.decodeUnknownSync(schema)([["a", 1], ["b", 2]])
|
|
240
|
+
expect(decoded).toEqual(new Map([["a", 1], ["b", 2]]))
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
test("encodes a Map back to an array of tuples", () => {
|
|
244
|
+
const schema = S.ReadonlyMapFromArray({ key: S.String, value: S.Finite })
|
|
245
|
+
const encoded = S.encodeSync(schema)(new Map([["a", 1], ["b", 2]]))
|
|
246
|
+
expect(encoded).toEqual([["a", 1], ["b", 2]])
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
test("decodes with NumberFromString as key", () => {
|
|
250
|
+
const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.String })
|
|
251
|
+
const decoded = S.decodeUnknownSync(schema)([["1", "one"], ["2", "two"]])
|
|
252
|
+
expect(decoded).toEqual(new Map([[1, "one"], [2, "two"]]))
|
|
253
|
+
expectTypeOf(decoded).toEqualTypeOf<ReadonlyMap<number, string>>()
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test("encodes with NumberFromString as key", () => {
|
|
257
|
+
const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.String })
|
|
258
|
+
const encoded = S.encodeSync(schema)(new Map([[1, "one"], [2, "two"]]))
|
|
259
|
+
expect(encoded).toEqual([["1", "one"], ["2", "two"]])
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
test("decodes with NumberFromString as value", () => {
|
|
263
|
+
const schema = S.ReadonlyMapFromArray({ key: S.String, value: S.NumberFromString })
|
|
264
|
+
const decoded = S.decodeUnknownSync(schema)([["a", "10"], ["b", "20"]])
|
|
265
|
+
expect(decoded).toEqual(new Map([["a", 10], ["b", 20]]))
|
|
266
|
+
expectTypeOf(decoded).toEqualTypeOf<ReadonlyMap<string, number>>()
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test("encodes with NumberFromString as value", () => {
|
|
270
|
+
const schema = S.ReadonlyMapFromArray({ key: S.String, value: S.NumberFromString })
|
|
271
|
+
const encoded = S.encodeSync(schema)(new Map([["a", 10], ["b", 20]]))
|
|
272
|
+
expect(encoded).toEqual([["a", "10"], ["b", "20"]])
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test("decodes with NumberFromString as both key and value", () => {
|
|
276
|
+
const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.NumberFromString })
|
|
277
|
+
const decoded = S.decodeUnknownSync(schema)([["1", "10"], ["2", "20"]])
|
|
278
|
+
expect(decoded).toEqual(new Map([[1, 10], [2, 20]]))
|
|
279
|
+
expectTypeOf(decoded).toEqualTypeOf<ReadonlyMap<number, number>>()
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
test("rejects invalid input", () => {
|
|
283
|
+
const schema = S.ReadonlyMapFromArray({ key: S.NumberFromString, value: S.String })
|
|
284
|
+
expect(() => S.decodeUnknownSync(schema)([[1, "val"]])).toThrow()
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
describe("ReadonlySet (with withDefault)", () => {
|
|
289
|
+
test("makeUnsafe provides withDefault", () => {
|
|
290
|
+
const schema = S.ReadonlySet(S.NumberFromString)
|
|
291
|
+
const struct = S.Struct({ items: schema.withDefault })
|
|
292
|
+
const made = struct.makeUnsafe({})
|
|
293
|
+
expect(made.items).toEqual(new Set())
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test("decodes array with NumberFromString values", () => {
|
|
297
|
+
const schema = S.ReadonlySet(S.NumberFromString)
|
|
298
|
+
const decoded = S.decodeUnknownSync(schema)(["1", "2"])
|
|
299
|
+
expect(decoded).toEqual(new Set([1, 2]))
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe("ReadonlyMap (with withDefault)", () => {
|
|
304
|
+
test("makeUnsafe provides withDefault", () => {
|
|
305
|
+
const schema = S.ReadonlyMap({ key: S.NumberFromString, value: S.String })
|
|
306
|
+
const struct = S.Struct({ items: schema.withDefault })
|
|
307
|
+
const made = struct.makeUnsafe({})
|
|
308
|
+
expect(made.items).toEqual(new Map())
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test("decodes array of tuples with NumberFromString keys", () => {
|
|
312
|
+
const schema = S.ReadonlyMap({ key: S.NumberFromString, value: S.String })
|
|
313
|
+
const decoded = S.decodeUnknownSync(schema)([["1", "one"]])
|
|
314
|
+
expect(decoded).toEqual(new Map([[1, "one"]]))
|
|
315
|
+
})
|
|
316
|
+
})
|