effect-app 4.0.0-beta.21 → 4.0.0-beta.210
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 +952 -0
- package/dist/Array.d.ts +1 -1
- package/dist/Chunk.d.ts +1 -1
- package/dist/Chunk.d.ts.map +1 -1
- package/dist/Config/SecretURL.d.ts +1 -1
- package/dist/Config/SecretURL.d.ts.map +1 -1
- package/dist/Config/SecretURL.js +2 -2
- package/dist/Config/internal/configSecretURL.d.ts +1 -1
- package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
- package/dist/Config.d.ts +7 -0
- package/dist/Config.d.ts.map +1 -0
- package/dist/Config.js +6 -0
- package/dist/ConfigProvider.d.ts +39 -0
- package/dist/ConfigProvider.d.ts.map +1 -0
- package/dist/ConfigProvider.js +42 -0
- package/dist/Context.d.ts +40 -0
- package/dist/Context.d.ts.map +1 -0
- package/dist/Context.js +67 -0
- package/dist/Effect.d.ts +9 -10
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -6
- package/dist/Function.d.ts +1 -1
- package/dist/Function.d.ts.map +1 -1
- package/dist/Inputify.type.d.ts +1 -1
- package/dist/Layer.d.ts +7 -6
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +1 -1
- package/dist/NonEmptySet.d.ts +1 -1
- package/dist/NonEmptySet.d.ts.map +1 -1
- package/dist/Option.d.ts +1 -1
- package/dist/Option.d.ts.map +1 -1
- package/dist/Pure.d.ts +5 -5
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +13 -13
- package/dist/Schema/Class.d.ts +66 -20
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +189 -22
- package/dist/Schema/FastCheck.d.ts +1 -1
- package/dist/Schema/FastCheck.d.ts.map +1 -1
- package/dist/Schema/Methods.d.ts +1 -1
- package/dist/Schema/SchemaParser.d.ts +5 -0
- package/dist/Schema/SchemaParser.d.ts.map +1 -0
- package/dist/Schema/SchemaParser.js +6 -0
- package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +122 -0
- package/dist/Schema/SpecialOpenApi.d.ts +32 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +123 -0
- package/dist/Schema/brand.d.ts +4 -2
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +1 -1
- package/dist/Schema/email.d.ts +1 -1
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +7 -4
- package/dist/Schema/ext.d.ts +117 -45
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +131 -42
- package/dist/Schema/moreStrings.d.ts +37 -25
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +15 -16
- package/dist/Schema/numbers.d.ts +15 -15
- package/dist/Schema/numbers.d.ts.map +1 -1
- package/dist/Schema/numbers.js +10 -12
- package/dist/Schema/phoneNumber.d.ts +1 -1
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +6 -3
- package/dist/Schema/schema.d.ts +1 -1
- package/dist/Schema/strings.d.ts +5 -5
- package/dist/Schema/strings.d.ts.map +1 -1
- package/dist/Schema/strings.js +1 -5
- package/dist/Schema.d.ts +147 -15
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +131 -16
- package/dist/Set.d.ts +1 -1
- package/dist/Set.d.ts.map +1 -1
- package/dist/TypeTest.d.ts +1 -1
- package/dist/Types.d.ts +1 -1
- package/dist/Widen.type.d.ts +1 -1
- package/dist/_ext/Array.d.ts +1 -1
- package/dist/_ext/Array.d.ts.map +1 -1
- package/dist/_ext/date.d.ts +1 -1
- package/dist/_ext/misc.d.ts +1 -1
- package/dist/_ext/ord.ext.d.ts +1 -1
- package/dist/_ext/ord.ext.d.ts.map +1 -1
- package/dist/builtin.d.ts +1 -1
- package/dist/builtin.d.ts.map +1 -1
- package/dist/client/InvalidationKeys.d.ts +29 -0
- package/dist/client/InvalidationKeys.d.ts.map +1 -0
- package/dist/client/InvalidationKeys.js +33 -0
- package/dist/client/apiClientFactory.d.ts +20 -32
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +95 -32
- package/dist/client/clientFor.d.ts +51 -17
- package/dist/client/clientFor.d.ts.map +1 -1
- package/dist/client/clientFor.js +9 -1
- package/dist/client/errors.d.ts +49 -25
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +43 -17
- package/dist/client/makeClient.d.ts +481 -33
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +66 -24
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -1
- package/dist/faker.d.ts +1 -1
- package/dist/faker.d.ts.map +1 -1
- package/dist/http/Request.d.ts +2 -2
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/internal/lib.d.ts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/ids.d.ts +12 -12
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +3 -2
- package/dist/index.d.ts +5 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -8
- package/dist/logger.d.ts +1 -1
- package/dist/middleware.d.ts +14 -8
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +14 -8
- package/dist/rpc/Invalidation.d.ts +402 -0
- package/dist/rpc/Invalidation.d.ts.map +1 -0
- package/dist/rpc/Invalidation.js +150 -0
- package/dist/rpc/MiddlewareMaker.d.ts +5 -4
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +57 -37
- package/dist/rpc/RpcContextMap.d.ts +3 -3
- package/dist/rpc/RpcContextMap.d.ts.map +1 -1
- package/dist/rpc/RpcContextMap.js +4 -4
- package/dist/rpc/RpcMiddleware.d.ts +5 -4
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.js +1 -1
- package/dist/rpc.d.ts +2 -2
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +2 -2
- package/dist/transform.d.ts +1 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +3 -3
- package/dist/utils/effectify.d.ts +1 -1
- package/dist/utils/extend.d.ts +1 -1
- package/dist/utils/extend.d.ts.map +1 -1
- package/dist/utils/gen.d.ts +2 -2
- package/dist/utils/gen.d.ts.map +1 -1
- package/dist/utils/logLevel.d.ts +2 -2
- package/dist/utils/logLevel.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +3 -3
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -3
- package/dist/utils.d.ts +31 -38
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +12 -25
- package/dist/validation/validators.d.ts +1 -1
- package/dist/validation/validators.d.ts.map +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/package.json +46 -24
- package/src/Config/SecretURL.ts +2 -1
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/{ServiceMap.ts → Context.ts} +52 -59
- package/src/Effect.ts +12 -14
- package/src/Layer.ts +6 -5
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +268 -62
- package/src/Schema/SchemaParser.ts +12 -0
- package/src/Schema/SpecialJsonSchema.ts +137 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +21 -1
- package/src/Schema/email.ts +7 -2
- package/src/Schema/ext.ts +204 -72
- package/src/Schema/moreStrings.ts +40 -37
- package/src/Schema/numbers.ts +14 -16
- package/src/Schema/phoneNumber.ts +5 -1
- package/src/Schema/strings.ts +4 -8
- package/src/Schema.ts +314 -20
- package/src/client/InvalidationKeys.ts +50 -0
- package/src/client/apiClientFactory.ts +223 -129
- package/src/client/clientFor.ts +95 -29
- package/src/client/errors.ts +52 -26
- package/src/client/makeClient.ts +572 -71
- package/src/client.ts +1 -0
- package/src/ids.ts +3 -2
- package/src/index.ts +5 -10
- package/src/middleware.ts +13 -9
- package/src/rpc/Invalidation.ts +226 -0
- package/src/rpc/MiddlewareMaker.ts +65 -60
- package/src/rpc/README.md +2 -2
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +5 -4
- package/src/rpc.ts +1 -1
- package/src/transform.ts +2 -2
- package/src/utils/gen.ts +1 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils.ts +50 -132
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/dist/secretURL.test.d.ts.map +1 -0
- package/test/dist/special.test.d.ts.map +1 -0
- package/test/dist/stream-error.types.d.ts +2 -0
- package/test/dist/stream-error.types.d.ts.map +1 -0
- package/test/dist/stream-error.types.js +27 -0
- package/test/rpc.test.ts +45 -6
- package/test/schema.test.ts +581 -7
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +1023 -0
- package/test/utils.test.ts +6 -6
- package/tsconfig.base.json +3 -4
- package/tsconfig.json +0 -1
- package/tsconfig.json.bak +2 -2
- package/tsconfig.src.json +29 -29
- package/tsconfig.test.json +2 -2
- package/dist/Operations.d.ts +0 -123
- package/dist/Operations.d.ts.map +0 -1
- package/dist/Operations.js +0 -29
- package/dist/ServiceMap.d.ts +0 -44
- package/dist/ServiceMap.d.ts.map +0 -1
- package/dist/ServiceMap.js +0 -91
- package/eslint.config.mjs +0 -26
- package/src/Operations.ts +0 -55
package/src/Schema/numbers.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Effect } from "effect"
|
|
1
2
|
import { extendM } from "effect-app/utils"
|
|
2
3
|
import * as S from "effect/Schema"
|
|
3
4
|
import type { Simplify } from "effect/Types"
|
|
4
5
|
import { fromBrand, nominal } from "./brand.js"
|
|
5
|
-
import {
|
|
6
|
+
import { withDefaultMake } from "./ext.js"
|
|
6
7
|
import { type B } from "./schema.js"
|
|
7
8
|
|
|
8
9
|
export interface PositiveIntBrand
|
|
@@ -11,10 +12,10 @@ export interface PositiveIntBrand
|
|
|
11
12
|
export const PositiveInt = extendM(
|
|
12
13
|
S.Int.pipe(
|
|
13
14
|
S.check(S.isGreaterThan(0)),
|
|
14
|
-
fromBrand(nominal<PositiveInt>(), { identifier: "PositiveInt",
|
|
15
|
+
fromBrand<PositiveInt>(nominal<PositiveInt>(), { identifier: "PositiveInt", jsonSchema: {} }),
|
|
15
16
|
withDefaultMake
|
|
16
17
|
),
|
|
17
|
-
(s) => ({ withDefault: s.pipe(
|
|
18
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
|
|
18
19
|
)
|
|
19
20
|
export type PositiveInt = number & PositiveIntBrand
|
|
20
21
|
|
|
@@ -22,53 +23,50 @@ export interface NonNegativeIntBrand extends Simplify<B.Brand<"NonNegativeInt">
|
|
|
22
23
|
export const NonNegativeInt = extendM(
|
|
23
24
|
S.Int.pipe(
|
|
24
25
|
S.check(S.isGreaterThanOrEqualTo(0)),
|
|
25
|
-
fromBrand(nominal<NonNegativeInt>(), {
|
|
26
|
+
fromBrand<NonNegativeInt>(nominal<NonNegativeInt>(), {
|
|
26
27
|
identifier: "NonNegativeInt",
|
|
27
|
-
title: "NonNegativeInt",
|
|
28
28
|
jsonSchema: {}
|
|
29
29
|
}),
|
|
30
30
|
withDefaultMake
|
|
31
31
|
),
|
|
32
|
-
(s) => ({ withDefault: s.pipe(
|
|
32
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
|
|
33
33
|
)
|
|
34
34
|
export type NonNegativeInt = number & NonNegativeIntBrand
|
|
35
35
|
|
|
36
36
|
export interface IntBrand extends Simplify<B.Brand<"Int">> {}
|
|
37
37
|
export const Int = extendM(
|
|
38
|
-
S.Int.pipe(fromBrand(nominal<Int>(), { identifier: "Int",
|
|
39
|
-
(s) => ({ withDefault: s.pipe(
|
|
38
|
+
S.Int.pipe(fromBrand<Int>(nominal<Int>(), { identifier: "Int", jsonSchema: {} }), withDefaultMake),
|
|
39
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
|
|
40
40
|
)
|
|
41
41
|
export type Int = number & IntBrand
|
|
42
42
|
|
|
43
43
|
export interface PositiveNumberBrand extends Simplify<B.Brand<"PositiveNumber"> & NonNegativeNumberBrand> {}
|
|
44
44
|
export const PositiveNumber = extendM(
|
|
45
|
-
S.
|
|
45
|
+
S.Finite.pipe(
|
|
46
46
|
S.check(S.isGreaterThan(0)),
|
|
47
|
-
fromBrand(nominal<PositiveNumber>(), {
|
|
47
|
+
fromBrand<PositiveNumber>(nominal<PositiveNumber>(), {
|
|
48
48
|
identifier: "PositiveNumber",
|
|
49
|
-
title: "PositiveNumber",
|
|
50
49
|
jsonSchema: {}
|
|
51
50
|
}),
|
|
52
51
|
withDefaultMake
|
|
53
52
|
),
|
|
54
|
-
(s) => ({ withDefault: s.pipe(
|
|
53
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
|
|
55
54
|
)
|
|
56
55
|
export type PositiveNumber = number & PositiveNumberBrand
|
|
57
56
|
|
|
58
57
|
export interface NonNegativeNumberBrand extends Simplify<B.Brand<"NonNegativeNumber">> {}
|
|
59
58
|
export const NonNegativeNumber = extendM(
|
|
60
59
|
S
|
|
61
|
-
.
|
|
60
|
+
.Finite
|
|
62
61
|
.pipe(
|
|
63
62
|
S.check(S.isGreaterThanOrEqualTo(0)),
|
|
64
|
-
fromBrand(nominal<NonNegativeNumber>(), {
|
|
63
|
+
fromBrand<NonNegativeNumber>(nominal<NonNegativeNumber>(), {
|
|
65
64
|
identifier: "NonNegativeNumber",
|
|
66
|
-
title: "NonNegativeNumber",
|
|
67
65
|
jsonSchema: {}
|
|
68
66
|
}),
|
|
69
67
|
withDefaultMake
|
|
70
68
|
),
|
|
71
|
-
(s) => ({ withDefault: s.pipe(
|
|
69
|
+
(s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
|
|
72
70
|
)
|
|
73
71
|
export type NonNegativeNumber = number & NonNegativeNumberBrand
|
|
74
72
|
|
|
@@ -13,9 +13,13 @@ export type PhoneNumber = string & PhoneNumberBrand
|
|
|
13
13
|
export const PhoneNumber = S
|
|
14
14
|
.String
|
|
15
15
|
.pipe(
|
|
16
|
+
S.annotate({
|
|
17
|
+
title: "PhoneNumber",
|
|
18
|
+
description: "a phone number with at least 7 digits",
|
|
19
|
+
format: "phone"
|
|
20
|
+
}),
|
|
16
21
|
S.refine(isValidPhone as Refinement<string, PhoneNumber>, {
|
|
17
22
|
identifier: "PhoneNumber",
|
|
18
|
-
title: "PhoneNumber",
|
|
19
23
|
description: "a phone number with at least 7 digits",
|
|
20
24
|
jsonSchema: { format: "phone" }
|
|
21
25
|
}),
|
package/src/Schema/strings.ts
CHANGED
|
@@ -9,9 +9,8 @@ export type NonEmptyString = string & NonEmptyStringBrand
|
|
|
9
9
|
export const NonEmptyString = S
|
|
10
10
|
.NonEmptyString
|
|
11
11
|
.pipe(
|
|
12
|
-
fromBrand(nominal<NonEmptyString>(), {
|
|
12
|
+
fromBrand<NonEmptyString>(nominal<NonEmptyString>(), {
|
|
13
13
|
identifier: "NonEmptyString",
|
|
14
|
-
title: "NonEmptyString",
|
|
15
14
|
jsonSchema: {}
|
|
16
15
|
}),
|
|
17
16
|
withDefaultMake
|
|
@@ -23,9 +22,8 @@ export const NonEmptyString64k = S
|
|
|
23
22
|
.NonEmptyString
|
|
24
23
|
.pipe(
|
|
25
24
|
S.check(S.isMaxLength(64 * 1024)),
|
|
26
|
-
fromBrand(nominal<NonEmptyString64k>(), {
|
|
25
|
+
fromBrand<NonEmptyString64k>(nominal<NonEmptyString64k>(), {
|
|
27
26
|
identifier: "NonEmptyString64k",
|
|
28
|
-
title: "NonEmptyString64k",
|
|
29
27
|
jsonSchema: {}
|
|
30
28
|
}),
|
|
31
29
|
withDefaultMake
|
|
@@ -37,9 +35,8 @@ export const NonEmptyString2k = S
|
|
|
37
35
|
.NonEmptyString
|
|
38
36
|
.pipe(
|
|
39
37
|
S.check(S.isMaxLength(2 * 1024)),
|
|
40
|
-
fromBrand(nominal<NonEmptyString2k>(), {
|
|
38
|
+
fromBrand<NonEmptyString2k>(nominal<NonEmptyString2k>(), {
|
|
41
39
|
identifier: "NonEmptyString2k",
|
|
42
|
-
title: "NonEmptyString2k",
|
|
43
40
|
jsonSchema: {}
|
|
44
41
|
}),
|
|
45
42
|
withDefaultMake
|
|
@@ -51,9 +48,8 @@ export const NonEmptyString255 = S
|
|
|
51
48
|
.NonEmptyString
|
|
52
49
|
.pipe(
|
|
53
50
|
S.check(S.isMaxLength(255)),
|
|
54
|
-
fromBrand(nominal<NonEmptyString255>(), {
|
|
51
|
+
fromBrand<NonEmptyString255>(nominal<NonEmptyString255>(), {
|
|
55
52
|
identifier: "NonEmptyString255",
|
|
56
|
-
title: "NonEmptyString255",
|
|
57
53
|
jsonSchema: {}
|
|
58
54
|
}),
|
|
59
55
|
withDefaultMake
|
package/src/Schema.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { SchemaAST, type Tracer } from "effect"
|
|
2
2
|
import * as S from "effect/Schema"
|
|
3
|
+
import { type Simplify } from "effect/Struct"
|
|
4
|
+
import type { RequiredKeys } from "effect/Types"
|
|
3
5
|
import type { NonEmptyReadonlyArray } from "./Array.js"
|
|
4
6
|
import { fakerArb } from "./faker.js"
|
|
5
7
|
import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
|
|
6
|
-
import { withDefaultMake } from "./Schema/ext.js"
|
|
8
|
+
import { concurrencyUnbounded, withDefaultMake, withDefaultParseOptions } from "./Schema/ext.js"
|
|
7
9
|
import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
|
|
8
|
-
import {
|
|
10
|
+
import { type AST } from "./Schema/schema.js"
|
|
11
|
+
import { copy, extendM, type StructuralCopyOrigin } from "./utils.js"
|
|
9
12
|
|
|
10
13
|
export * from "effect/Schema"
|
|
11
|
-
// v4: TaggedError renamed to TaggedErrorClass
|
|
12
|
-
export { TaggedErrorClass as TaggedError } from "effect/Schema"
|
|
13
14
|
|
|
14
15
|
export * from "./Schema/Class.js"
|
|
15
|
-
export { Class, TaggedClass } from "./Schema/Class.js"
|
|
16
|
+
export { Class, ErrorClass, Opaque, TaggedClass, TaggedErrorClass } from "./Schema/Class.js"
|
|
16
17
|
|
|
17
18
|
export { fromBrand, nominal } from "./Schema/brand.js"
|
|
18
|
-
export { Array, Boolean, Date,
|
|
19
|
+
export { Array, Boolean, Date, DateFromString, DateValid, Finite, Literals, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
|
|
19
20
|
export { Int, NonNegativeInt } from "./Schema/numbers.js"
|
|
20
21
|
|
|
21
22
|
export * from "./Schema/email.js"
|
|
@@ -24,14 +25,217 @@ export * from "./Schema/moreStrings.js"
|
|
|
24
25
|
export * from "./Schema/numbers.js"
|
|
25
26
|
export * from "./Schema/phoneNumber.js"
|
|
26
27
|
export * from "./Schema/schema.js"
|
|
28
|
+
export * from "./Schema/SpecialJsonSchema.js"
|
|
29
|
+
export * from "./Schema/SpecialOpenApi.js"
|
|
27
30
|
export * from "./Schema/strings.js"
|
|
28
31
|
export { NonEmptyString } from "./Schema/strings.js"
|
|
29
32
|
|
|
30
33
|
export * as SchemaIssue from "effect/SchemaIssue"
|
|
31
|
-
|
|
34
|
+
|
|
35
|
+
export const decodeEffectConcurrently: typeof S.decodeEffect = withDefaultParseOptions(S.decodeEffect)
|
|
36
|
+
export const decodeUnknownEffectConcurrently: typeof S.decodeUnknownEffect = withDefaultParseOptions(
|
|
37
|
+
S.decodeUnknownEffect
|
|
38
|
+
)
|
|
39
|
+
export * as SchemaParser from "./Schema/SchemaParser.js"
|
|
32
40
|
|
|
33
41
|
export { Void as Void_ } from "effect/Schema"
|
|
34
42
|
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Struct / NonEmptyArray / Record
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
export function Struct<const Fields extends S.Struct.Fields>(
|
|
48
|
+
fields: Fields
|
|
49
|
+
): Struct<Fields> {
|
|
50
|
+
const result = S.Struct(fields).annotate(concurrencyUnbounded)
|
|
51
|
+
const allowVoidMake = (schema: any): any => {
|
|
52
|
+
// Normalize omitted input to an empty object so optional/default-only structs can be constructed with make().
|
|
53
|
+
const origMake: any = schema.make
|
|
54
|
+
const origMakeOption: any = schema.makeOption
|
|
55
|
+
const origMakeEffect: any = schema.makeEffect
|
|
56
|
+
schema.make = function(this: any, input: any, options?: any) {
|
|
57
|
+
return origMake.call(this, input === undefined ? {} : input, options)
|
|
58
|
+
}
|
|
59
|
+
schema.makeOption = function(this: any, input: any, options?: any) {
|
|
60
|
+
return origMakeOption.call(this, input === undefined ? {} : input, options)
|
|
61
|
+
}
|
|
62
|
+
schema.makeEffect = function(this: any, input: any, options?: any) {
|
|
63
|
+
return origMakeEffect.call(this, input === undefined ? {} : input, options)
|
|
64
|
+
}
|
|
65
|
+
return schema
|
|
66
|
+
}
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
|
|
68
|
+
const origMapFields: any = result.mapFields
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
|
|
70
|
+
const origAnnotate: any = result.annotate
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
|
|
72
|
+
const origAnnotateKey: any = result.annotateKey
|
|
73
|
+
|
|
74
|
+
const preserveCopyAndMethods = (schema: any): any => {
|
|
75
|
+
schema.copy = copy
|
|
76
|
+
schema.mapFields = function(this: any, f: any, options?: any) {
|
|
77
|
+
return (result as any).mapFields.call(this, f, options)
|
|
78
|
+
}
|
|
79
|
+
schema.annotate = function(this: any, annotations?: any) {
|
|
80
|
+
return (result as any).annotate.call(this, annotations)
|
|
81
|
+
}
|
|
82
|
+
schema.annotateKey = function(this: any, annotations?: any) {
|
|
83
|
+
return (result as any).annotateKey.call(this, annotations)
|
|
84
|
+
}
|
|
85
|
+
return allowVoidMake(schema)
|
|
86
|
+
}
|
|
87
|
+
;(result as any).mapFields = function(this: any, f: any, options?: any) {
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
89
|
+
const mapped = origMapFields.call(this, f, options).annotate(concurrencyUnbounded)
|
|
90
|
+
return preserveCopyAndMethods(mapped)
|
|
91
|
+
}
|
|
92
|
+
;(result as any).annotate = function(this: any, annotations?: any) {
|
|
93
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
94
|
+
const annotated = origAnnotate.call(this, annotations)
|
|
95
|
+
return preserveCopyAndMethods(annotated)
|
|
96
|
+
}
|
|
97
|
+
;(result as any).annotateKey = function(this: any, annotations?: any) {
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
99
|
+
const annotated = origAnnotateKey.call(this, annotations)
|
|
100
|
+
return preserveCopyAndMethods(annotated)
|
|
101
|
+
}
|
|
102
|
+
;(result as any).copy = copy
|
|
103
|
+
allowVoidMake(result)
|
|
104
|
+
return result as Struct<Fields>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface Struct<Fields extends S.Struct.Fields> extends
|
|
108
|
+
S.Bottom<
|
|
109
|
+
Struct.Type<Fields>,
|
|
110
|
+
Struct.Encoded<Fields>,
|
|
111
|
+
Struct.DecodingServices<Fields>,
|
|
112
|
+
Struct.EncodingServices<Fields>,
|
|
113
|
+
AST.Objects,
|
|
114
|
+
// Rebuild is what's returned from annotate etc
|
|
115
|
+
Struct<Fields>,
|
|
116
|
+
Struct.MakeIn<Fields>,
|
|
117
|
+
Struct.Iso<Fields>
|
|
118
|
+
>
|
|
119
|
+
{
|
|
120
|
+
/**
|
|
121
|
+
* The field definitions of this struct. Spread them into a new struct to
|
|
122
|
+
* reuse fields across schemas.
|
|
123
|
+
*
|
|
124
|
+
* **Example** (Reusing fields across structs)
|
|
125
|
+
*
|
|
126
|
+
* ```ts
|
|
127
|
+
* import { Schema } from "effect"
|
|
128
|
+
*
|
|
129
|
+
* const Timestamped = Schema.Struct({
|
|
130
|
+
* createdAt: Schema.Date,
|
|
131
|
+
* updatedAt: Schema.Date
|
|
132
|
+
* })
|
|
133
|
+
*
|
|
134
|
+
* const User = Schema.Struct({
|
|
135
|
+
* ...Timestamped.fields,
|
|
136
|
+
* name: Schema.String,
|
|
137
|
+
* email: Schema.String
|
|
138
|
+
* })
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
readonly fields: Fields
|
|
142
|
+
/**
|
|
143
|
+
* Returns a new struct with the fields modified by the provided function.
|
|
144
|
+
*
|
|
145
|
+
* **Options**
|
|
146
|
+
*
|
|
147
|
+
* - `unsafePreserveChecks` - if `true`, keep any `.check(...)` constraints
|
|
148
|
+
* that were attached to the original union. Defaults to `false`.
|
|
149
|
+
*
|
|
150
|
+
* **Warning**: This is an unsafe operation. Since `mapFields`
|
|
151
|
+
* transformations change the schema type, the original refinement functions
|
|
152
|
+
* may no longer be valid or safe to apply to the transformed schema. Only
|
|
153
|
+
* use this option if you have verified that your refinements remain correct
|
|
154
|
+
* after the transformation.
|
|
155
|
+
*/
|
|
156
|
+
mapFields<To extends Struct.Fields>(
|
|
157
|
+
f: (fields: Fields) => To,
|
|
158
|
+
options?: {
|
|
159
|
+
readonly unsafePreserveChecks?: boolean | undefined
|
|
160
|
+
} | undefined
|
|
161
|
+
): Struct<Simplify<Readonly<To>>>
|
|
162
|
+
|
|
163
|
+
// added copy
|
|
164
|
+
readonly copy: StructuralCopyOrigin<Struct.Type<Fields>>
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export declare namespace Struct {
|
|
168
|
+
export type Fields = S.Struct.Fields
|
|
169
|
+
export type Type<F extends S.Struct.Fields> = S.Struct.Type<F>
|
|
170
|
+
export type Encoded<F extends S.Struct.Fields> = S.Struct.Encoded<F>
|
|
171
|
+
export type DecodingServices<F extends S.Struct.Fields> = S.Struct.DecodingServices<F>
|
|
172
|
+
export type EncodingServices<F extends S.Struct.Fields> = S.Struct.EncodingServices<F>
|
|
173
|
+
// changed; all optional allows void
|
|
174
|
+
export type MakeIn<F extends S.Struct.Fields> = RequiredKeys<S.Struct.MakeIn<F>> extends never
|
|
175
|
+
? void | S.Struct.MakeIn<F>
|
|
176
|
+
: S.Struct.MakeIn<F>
|
|
177
|
+
export type Iso<F extends S.Struct.Fields> = S.Struct.Iso<F>
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export type StructNestedEncodedError<T> = {
|
|
181
|
+
readonly _tag: "StructNestedEncodedError"
|
|
182
|
+
readonly message: "Expected a Struct schema or a schema with from.Encoded"
|
|
183
|
+
readonly schema: T
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export type StructNestedEncoded<T> = T extends { fields: infer Fields extends S.Struct.Fields } ? Struct.Encoded<Fields>
|
|
187
|
+
: T extends { readonly from: { readonly Encoded: infer Encoded } } ? Encoded
|
|
188
|
+
: StructNestedEncodedError<T>
|
|
189
|
+
|
|
190
|
+
export function NonEmptyArray<Value extends S.Top>(value: Value): S.NonEmptyArray<Value> {
|
|
191
|
+
return S.NonEmptyArray(value).annotate(concurrencyUnbounded)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function TaggedStruct<const Tag extends SchemaAST.LiteralValue, const Fields extends S.Struct.Fields>(
|
|
195
|
+
value: Tag,
|
|
196
|
+
fields: Fields
|
|
197
|
+
): TaggedStruct<Tag, Fields> {
|
|
198
|
+
return Struct({ _tag: S.tag(value), ...fields }) as any
|
|
199
|
+
}
|
|
200
|
+
export interface TaggedStruct<Tag extends SchemaAST.LiteralValue, Fields extends S.Struct.Fields>
|
|
201
|
+
extends Struct<{ readonly _tag: S.tag<Tag> } & Fields>
|
|
202
|
+
{}
|
|
203
|
+
export declare namespace TaggedStruct {
|
|
204
|
+
export type Fields = S.Struct.Fields
|
|
205
|
+
export type Type<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.Type<
|
|
206
|
+
{ readonly _tag: S.tag<Tag> } & F
|
|
207
|
+
>
|
|
208
|
+
export type Encoded<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.Encoded<
|
|
209
|
+
{ readonly _tag: S.tag<Tag> } & F
|
|
210
|
+
>
|
|
211
|
+
export type DecodingServices<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> =
|
|
212
|
+
S.Struct.DecodingServices<
|
|
213
|
+
{ readonly _tag: S.tag<Tag> } & F
|
|
214
|
+
>
|
|
215
|
+
export type EncodingServices<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> =
|
|
216
|
+
S.Struct.EncodingServices<
|
|
217
|
+
{ readonly _tag: S.tag<Tag> } & F
|
|
218
|
+
>
|
|
219
|
+
export type MakeIn<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.MakeIn<
|
|
220
|
+
{ readonly _tag: S.tag<Tag> } & F
|
|
221
|
+
>
|
|
222
|
+
export type Iso<Tag extends SchemaAST.LiteralValue, F extends S.Struct.Fields> = S.Struct.Iso<
|
|
223
|
+
{ readonly _tag: S.tag<Tag> } & F
|
|
224
|
+
>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function Record<Key extends S.Record.Key, Value extends S.Top>(
|
|
228
|
+
key: Key,
|
|
229
|
+
value: Value
|
|
230
|
+
): S.$Record<Key, Value> {
|
|
231
|
+
return S.Record(key, value).annotate(concurrencyUnbounded)
|
|
232
|
+
}
|
|
233
|
+
export declare namespace Record {
|
|
234
|
+
export type Key = S.Record.Key
|
|
235
|
+
export type Type<K extends S.Record.Key, V extends S.Top> = S.Record.Type<K, V>
|
|
236
|
+
export type Encoded<K extends S.Record.Key, V extends S.Top> = S.Record.Encoded<K, V>
|
|
237
|
+
}
|
|
238
|
+
|
|
35
239
|
export const SpanId = Symbol()
|
|
36
240
|
export type SpanId = typeof SpanId
|
|
37
241
|
|
|
@@ -65,46 +269,136 @@ export const PhoneNumber = PhoneNumberT
|
|
|
65
269
|
|
|
66
270
|
export type PhoneNumber = PhoneNumberType
|
|
67
271
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
272
|
+
// Copied from SchemaAST.collectSentinels (marked @internal in effect).
|
|
273
|
+
// Returns all { key, literal } pairs that can discriminate a union member.
|
|
274
|
+
const getTagFromAST = (schema: S.Top): string => {
|
|
275
|
+
const sentinels = collectSentinelsFromAST(schema.ast)
|
|
276
|
+
const sentinel = sentinels.find((s) => s.key === "_tag")
|
|
277
|
+
if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
|
|
278
|
+
throw new Error("No _tag literal found on schema member")
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function collectSentinelsFromAST(
|
|
282
|
+
ast: SchemaAST.AST
|
|
283
|
+
): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
|
|
284
|
+
switch (ast._tag) {
|
|
285
|
+
case "Declaration": {
|
|
286
|
+
const s = ast.annotations?.["~sentinels"]
|
|
287
|
+
return Array.isArray(s) ? s : []
|
|
288
|
+
}
|
|
289
|
+
case "Objects":
|
|
290
|
+
return ast.propertySignatures.flatMap(
|
|
291
|
+
(ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
|
|
292
|
+
const type = ps.type
|
|
293
|
+
if (!SchemaAST.isOptional(type)) {
|
|
294
|
+
if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
|
|
295
|
+
if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
|
|
296
|
+
}
|
|
297
|
+
return []
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
case "Suspend":
|
|
301
|
+
return collectSentinelsFromAST(ast.thunk())
|
|
302
|
+
default:
|
|
303
|
+
return []
|
|
71
304
|
}
|
|
72
|
-
return schema.ast.literal as Tag
|
|
73
305
|
}
|
|
74
306
|
|
|
75
307
|
export const tags = <
|
|
76
|
-
|
|
77
|
-
Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
|
|
308
|
+
Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
|
|
78
309
|
>(
|
|
79
310
|
self: Members
|
|
80
311
|
) =>
|
|
81
312
|
S.Literals(
|
|
82
|
-
self.map(
|
|
83
|
-
[Index in keyof Members]:
|
|
313
|
+
self.map(getTagFromAST) as {
|
|
314
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
84
315
|
}
|
|
85
316
|
) as S.Literals<
|
|
86
317
|
{
|
|
87
|
-
[Index in keyof Members]:
|
|
318
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
88
319
|
}
|
|
89
320
|
>
|
|
90
321
|
|
|
91
322
|
type TaggedUnionMembers = NonEmptyReadonlyArray<
|
|
92
|
-
S.Top & { readonly Type: { readonly _tag: string }
|
|
323
|
+
S.Top & { readonly Type: { readonly _tag: string } }
|
|
93
324
|
>
|
|
94
325
|
|
|
95
326
|
type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
|
|
96
327
|
{
|
|
97
|
-
[Index in keyof Members]:
|
|
328
|
+
[Index in keyof Members]: Members[Index]["Type"]["_tag"]
|
|
98
329
|
}
|
|
99
330
|
>
|
|
100
331
|
|
|
332
|
+
type TaggedPropertyKeys<A, Members extends TaggedUnionMembers> = {
|
|
333
|
+
[K in keyof A & string]: A[K] extends Members[number]["Type"] ? K : never
|
|
334
|
+
}[keyof A & string]
|
|
335
|
+
|
|
336
|
+
type PropertyGuardsFor<
|
|
337
|
+
Members extends TaggedUnionMembers,
|
|
338
|
+
K extends string,
|
|
339
|
+
A
|
|
340
|
+
> =
|
|
341
|
+
& {
|
|
342
|
+
readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: (
|
|
343
|
+
target: A
|
|
344
|
+
) => target is A & { readonly [P in K]: M["Type"] }
|
|
345
|
+
}
|
|
346
|
+
& {
|
|
347
|
+
readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
|
|
348
|
+
tags: Tags
|
|
349
|
+
) => (
|
|
350
|
+
target: A
|
|
351
|
+
) => target is A & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
type PropertyGuards<
|
|
355
|
+
Members extends TaggedUnionMembers,
|
|
356
|
+
K extends string
|
|
357
|
+
> =
|
|
358
|
+
& {
|
|
359
|
+
readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: <
|
|
360
|
+
T extends { readonly [P in K]: Members[number]["Type"] }
|
|
361
|
+
>(target: T) => target is T & { readonly [P in K]: M["Type"] }
|
|
362
|
+
}
|
|
363
|
+
& {
|
|
364
|
+
readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
|
|
365
|
+
tags: Tags
|
|
366
|
+
) => <T extends { readonly [P in K]: Members[number]["Type"] }>(
|
|
367
|
+
target: T
|
|
368
|
+
) => target is T & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
|
|
369
|
+
}
|
|
370
|
+
|
|
101
371
|
type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
|
|
102
372
|
readonly tags: TaggedUnionTags<Members>
|
|
373
|
+
readonly generateGuards: <K extends string>(property: K) => PropertyGuards<Members, K>
|
|
374
|
+
readonly generateGuardsFor: <A>() => <K extends TaggedPropertyKeys<A, Members>>(
|
|
375
|
+
property: K
|
|
376
|
+
) => PropertyGuardsFor<Members, K, A>
|
|
103
377
|
}
|
|
104
378
|
|
|
105
379
|
const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
|
|
106
380
|
schema: S.Union<Members>
|
|
107
|
-
): TaggedUnionWithTags<Members> =>
|
|
381
|
+
): TaggedUnionWithTags<Members> =>
|
|
382
|
+
extendM(schema.pipe(S.toTaggedUnion("_tag")), (tagged) => {
|
|
383
|
+
const makeGuards = (property: string) => {
|
|
384
|
+
const result: any = {}
|
|
385
|
+
const guards: Record<string, (u: unknown) => boolean> = tagged.guards
|
|
386
|
+
for (const tag of Object.keys(guards)) {
|
|
387
|
+
const guard = guards[tag]!
|
|
388
|
+
result[`is${tag}`] = (target: any) => guard(target[property])
|
|
389
|
+
}
|
|
390
|
+
result.isAnyOf = (memberTags: Array<string>) => {
|
|
391
|
+
const check = tagged.isAnyOf(memberTags)
|
|
392
|
+
return (target: any) => check(target[property])
|
|
393
|
+
}
|
|
394
|
+
return result
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
tags: tags(schema.members),
|
|
398
|
+
generateGuards: makeGuards,
|
|
399
|
+
generateGuardsFor: () => makeGuards
|
|
400
|
+
}
|
|
401
|
+
})
|
|
108
402
|
|
|
109
403
|
export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
|
|
110
404
|
schema: S.Union<Members>
|
|
@@ -112,4 +406,4 @@ export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
|
|
|
112
406
|
|
|
113
407
|
export const TaggedUnion = <
|
|
114
408
|
Members extends TaggedUnionMembers
|
|
115
|
-
>(
|
|
409
|
+
>(members: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(members))
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as Ref from "effect/Ref"
|
|
2
|
+
import * as Context from "../Context.js"
|
|
3
|
+
import * as Effect from "../Effect.js"
|
|
4
|
+
import type { InvalidationKey } from "../rpc/Invalidation.js"
|
|
5
|
+
|
|
6
|
+
export type { InvalidationKey }
|
|
7
|
+
/** Shape of the per-mutation service that accumulates server-provided invalidation keys. */
|
|
8
|
+
export interface InvalidationKeysService {
|
|
9
|
+
readonly add: (key: InvalidationKey) => Effect.Effect<void>
|
|
10
|
+
readonly get: Effect.Effect<ReadonlyArray<InvalidationKey>>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Context.Reference that accumulates invalidation keys received from the server via the
|
|
15
|
+
* `x-invalidate` HTTP response header.
|
|
16
|
+
*
|
|
17
|
+
* The default is a no-op: when not explicitly provided (e.g. outside a mutation wrapper)
|
|
18
|
+
* all calls are ignored. The mutation wrapper in `@effect-app/vue` provides a real
|
|
19
|
+
* implementation backed by a `Ref`.
|
|
20
|
+
*/
|
|
21
|
+
export const InvalidationKeysFromServer = Context.Reference<InvalidationKeysService>(
|
|
22
|
+
"effect-app/client/InvalidationKeysFromServer",
|
|
23
|
+
{
|
|
24
|
+
defaultValue: () => ({
|
|
25
|
+
add: (_key: InvalidationKey) => Effect.void,
|
|
26
|
+
get: Effect.succeed([] as ReadonlyArray<InvalidationKey>)
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
export type InvalidationKeysFromServer = typeof InvalidationKeysFromServer
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a fresh `InvalidationKeysService` implementation backed by a `Ref`.
|
|
34
|
+
*
|
|
35
|
+
* @param ref - The `Ref` that stores the accumulated keys.
|
|
36
|
+
* @param onAdded - V3: Optional Effect run after a key is added. Use to trigger mid-stream
|
|
37
|
+
* query invalidation without waiting for the stream to complete.
|
|
38
|
+
*/
|
|
39
|
+
export const makeInvalidationKeysService = (
|
|
40
|
+
ref: Ref.Ref<ReadonlyArray<InvalidationKey>>,
|
|
41
|
+
onAdded?: (key: InvalidationKey) => Effect.Effect<void>
|
|
42
|
+
): InvalidationKeysService => ({
|
|
43
|
+
// When onAdded is set, fire it immediately without accumulating in the ref —
|
|
44
|
+
// the key is handled on arrival and must not be re-processed at stream end.
|
|
45
|
+
add: (key) =>
|
|
46
|
+
onAdded
|
|
47
|
+
? onAdded(key)
|
|
48
|
+
: Ref.update(ref, (keys) => [...keys, key]),
|
|
49
|
+
get: Ref.get(ref)
|
|
50
|
+
})
|