effect-app 4.0.0-beta.12 → 4.0.0-beta.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +466 -0
  2. package/dist/Config/SecretURL.js +2 -2
  3. package/dist/Config.d.ts +7 -0
  4. package/dist/Config.d.ts.map +1 -0
  5. package/dist/Config.js +6 -0
  6. package/dist/ConfigProvider.d.ts +39 -0
  7. package/dist/ConfigProvider.d.ts.map +1 -0
  8. package/dist/ConfigProvider.js +42 -0
  9. package/dist/{ServiceMap.d.ts → Context.d.ts} +14 -18
  10. package/dist/Context.d.ts.map +1 -0
  11. package/dist/Context.js +66 -0
  12. package/dist/Effect.d.ts +8 -7
  13. package/dist/Effect.d.ts.map +1 -1
  14. package/dist/Effect.js +3 -2
  15. package/dist/Layer.d.ts +5 -4
  16. package/dist/Layer.d.ts.map +1 -1
  17. package/dist/Layer.js +1 -1
  18. package/dist/Operations.d.ts +104 -27
  19. package/dist/Operations.d.ts.map +1 -1
  20. package/dist/Pure.d.ts +2 -2
  21. package/dist/Pure.d.ts.map +1 -1
  22. package/dist/Pure.js +13 -13
  23. package/dist/Schema/Class.d.ts +48 -10
  24. package/dist/Schema/Class.d.ts.map +1 -1
  25. package/dist/Schema/Class.js +120 -16
  26. package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
  27. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  28. package/dist/Schema/SpecialJsonSchema.js +122 -0
  29. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  30. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  31. package/dist/Schema/SpecialOpenApi.js +123 -0
  32. package/dist/Schema/brand.d.ts +5 -1
  33. package/dist/Schema/brand.d.ts.map +1 -1
  34. package/dist/Schema/brand.js +1 -1
  35. package/dist/Schema/email.d.ts.map +1 -1
  36. package/dist/Schema/email.js +9 -4
  37. package/dist/Schema/ext.d.ts +111 -46
  38. package/dist/Schema/ext.d.ts.map +1 -1
  39. package/dist/Schema/ext.js +114 -53
  40. package/dist/Schema/moreStrings.d.ts +19 -7
  41. package/dist/Schema/moreStrings.d.ts.map +1 -1
  42. package/dist/Schema/moreStrings.js +14 -9
  43. package/dist/Schema/numbers.d.ts +11 -11
  44. package/dist/Schema/numbers.d.ts.map +1 -1
  45. package/dist/Schema/numbers.js +10 -9
  46. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  47. package/dist/Schema/phoneNumber.js +8 -3
  48. package/dist/Schema/strings.d.ts +4 -4
  49. package/dist/Schema/strings.d.ts.map +1 -1
  50. package/dist/Schema.d.ts +74 -55
  51. package/dist/Schema.d.ts.map +1 -1
  52. package/dist/Schema.js +85 -64
  53. package/dist/client/apiClientFactory.d.ts +11 -28
  54. package/dist/client/apiClientFactory.d.ts.map +1 -1
  55. package/dist/client/apiClientFactory.js +14 -15
  56. package/dist/client/clientFor.d.ts +6 -5
  57. package/dist/client/clientFor.d.ts.map +1 -1
  58. package/dist/client/errors.d.ts +18 -9
  59. package/dist/client/errors.d.ts.map +1 -1
  60. package/dist/client/errors.js +35 -10
  61. package/dist/client/makeClient.d.ts +20 -15
  62. package/dist/client/makeClient.d.ts.map +1 -1
  63. package/dist/client/makeClient.js +32 -23
  64. package/dist/http/Request.d.ts.map +1 -1
  65. package/dist/http/Request.js +5 -5
  66. package/dist/ids.d.ts +2 -2
  67. package/dist/ids.d.ts.map +1 -1
  68. package/dist/ids.js +3 -2
  69. package/dist/index.d.ts +3 -8
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +4 -9
  72. package/dist/middleware.d.ts +2 -2
  73. package/dist/middleware.d.ts.map +1 -1
  74. package/dist/middleware.js +3 -3
  75. package/dist/rpc/MiddlewareMaker.d.ts +4 -3
  76. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  77. package/dist/rpc/MiddlewareMaker.js +7 -6
  78. package/dist/rpc/RpcContextMap.d.ts +2 -2
  79. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  80. package/dist/rpc/RpcContextMap.js +4 -4
  81. package/dist/rpc/RpcMiddleware.d.ts +4 -3
  82. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  83. package/dist/rpc/RpcMiddleware.js +1 -1
  84. package/dist/utils/gen.d.ts +1 -1
  85. package/dist/utils/gen.d.ts.map +1 -1
  86. package/dist/utils/logger.d.ts +2 -2
  87. package/dist/utils/logger.d.ts.map +1 -1
  88. package/dist/utils/logger.js +3 -3
  89. package/dist/utils.d.ts +24 -0
  90. package/dist/utils.d.ts.map +1 -1
  91. package/dist/utils.js +28 -2
  92. package/package.json +29 -17
  93. package/src/Config/SecretURL.ts +1 -1
  94. package/src/Config.ts +14 -0
  95. package/src/ConfigProvider.ts +48 -0
  96. package/src/{ServiceMap.ts → Context.ts} +51 -60
  97. package/src/Effect.ts +11 -9
  98. package/src/Layer.ts +5 -4
  99. package/src/Pure.ts +17 -18
  100. package/src/Schema/Class.ts +157 -30
  101. package/src/Schema/SpecialJsonSchema.ts +137 -0
  102. package/src/Schema/SpecialOpenApi.ts +130 -0
  103. package/src/Schema/brand.ts +10 -3
  104. package/src/Schema/email.ts +10 -2
  105. package/src/Schema/ext.ts +191 -85
  106. package/src/Schema/moreStrings.ts +21 -11
  107. package/src/Schema/numbers.ts +9 -8
  108. package/src/Schema/phoneNumber.ts +8 -1
  109. package/src/Schema.ts +195 -104
  110. package/src/client/apiClientFactory.ts +24 -29
  111. package/src/client/clientFor.ts +6 -1
  112. package/src/client/errors.ts +34 -9
  113. package/src/client/makeClient.ts +121 -61
  114. package/src/http/Request.ts +7 -4
  115. package/src/ids.ts +2 -1
  116. package/src/index.ts +3 -11
  117. package/src/middleware.ts +2 -2
  118. package/src/rpc/MiddlewareMaker.ts +8 -7
  119. package/src/rpc/RpcContextMap.ts +6 -5
  120. package/src/rpc/RpcMiddleware.ts +5 -4
  121. package/src/utils/gen.ts +1 -1
  122. package/src/utils/logger.ts +2 -2
  123. package/src/utils.ts +30 -1
  124. package/test/dist/moreStrings.test.d.ts.map +1 -0
  125. package/test/dist/rpc.test.d.ts.map +1 -1
  126. package/test/dist/secretURL.test.d.ts.map +1 -0
  127. package/test/dist/special.test.d.ts.map +1 -0
  128. package/test/moreStrings.test.ts +17 -0
  129. package/test/rpc.test.ts +28 -6
  130. package/test/schema.test.ts +504 -4
  131. package/test/secretURL.test.ts +157 -0
  132. package/test/special.test.ts +862 -0
  133. package/test/utils.test.ts +2 -2
  134. package/tsconfig.base.json +0 -1
  135. package/tsconfig.json +0 -1
  136. package/dist/ServiceMap.d.ts.map +0 -1
  137. package/dist/ServiceMap.js +0 -91
  138. package/dist/Struct.d.ts +0 -44
  139. package/dist/Struct.d.ts.map +0 -1
  140. package/dist/Struct.js +0 -29
  141. package/src/Struct.ts +0 -54
@@ -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 { withDefaultConstructor, withDefaultMake } from "./ext.js"
6
+ import { withDefaultMake } from "./ext.js"
6
7
  import { type B } from "./schema.js"
7
8
 
8
9
  export interface PositiveIntBrand
@@ -14,7 +15,7 @@ export const PositiveInt = extendM(
14
15
  fromBrand(nominal<PositiveInt>(), { identifier: "PositiveInt", title: "PositiveInt", jsonSchema: {} }),
15
16
  withDefaultMake
16
17
  ),
17
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(1))) })
18
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
18
19
  )
19
20
  export type PositiveInt = number & PositiveIntBrand
20
21
 
@@ -29,20 +30,20 @@ export const NonNegativeInt = extendM(
29
30
  }),
30
31
  withDefaultMake
31
32
  ),
32
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
33
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
33
34
  )
34
35
  export type NonNegativeInt = number & NonNegativeIntBrand
35
36
 
36
37
  export interface IntBrand extends Simplify<B.Brand<"Int">> {}
37
38
  export const Int = extendM(
38
39
  S.Int.pipe(fromBrand(nominal<Int>(), { identifier: "Int", title: "Int", jsonSchema: {} }), withDefaultMake),
39
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
40
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
40
41
  )
41
42
  export type Int = number & IntBrand
42
43
 
43
44
  export interface PositiveNumberBrand extends Simplify<B.Brand<"PositiveNumber"> & NonNegativeNumberBrand> {}
44
45
  export const PositiveNumber = extendM(
45
- S.Number.pipe(
46
+ S.Finite.pipe(
46
47
  S.check(S.isGreaterThan(0)),
47
48
  fromBrand(nominal<PositiveNumber>(), {
48
49
  identifier: "PositiveNumber",
@@ -51,14 +52,14 @@ export const PositiveNumber = extendM(
51
52
  }),
52
53
  withDefaultMake
53
54
  ),
54
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(1))) })
55
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(1)))) })
55
56
  )
56
57
  export type PositiveNumber = number & PositiveNumberBrand
57
58
 
58
59
  export interface NonNegativeNumberBrand extends Simplify<B.Brand<"NonNegativeNumber">> {}
59
60
  export const NonNegativeNumber = extendM(
60
61
  S
61
- .Number
62
+ .Finite
62
63
  .pipe(
63
64
  S.check(S.isGreaterThanOrEqualTo(0)),
64
65
  fromBrand(nominal<NonNegativeNumber>(), {
@@ -68,7 +69,7 @@ export const NonNegativeNumber = extendM(
68
69
  }),
69
70
  withDefaultMake
70
71
  ),
71
- (s) => ({ withDefault: s.pipe(withDefaultConstructor(() => s(0))) })
72
+ (s) => ({ withDefault: s.pipe(S.withConstructorDefault(Effect.sync(() => s(0)))) })
72
73
  )
73
74
  export type NonNegativeNumber = number & NonNegativeNumberBrand
74
75
 
@@ -13,12 +13,19 @@ 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
23
  title: "PhoneNumber",
19
24
  description: "a phone number with at least 7 digits",
20
- arbitrary: () => (fc: any) => Numbers(7, 10)(fc).map((_: any) => _ as PhoneNumber),
21
25
  jsonSchema: { format: "phone" }
22
26
  }),
27
+ S.annotate({
28
+ toArbitrary: () => (fc) => Numbers(7, 10)(fc).map((_) => _ as PhoneNumber)
29
+ }),
23
30
  withDefaultMake
24
31
  )
package/src/Schema.ts CHANGED
@@ -1,22 +1,19 @@
1
- import { Array, Option, pipe, SchemaAST, type Tracer } from "effect"
1
+ import { SchemaAST, type Tracer } from "effect"
2
2
  import * as S from "effect/Schema"
3
3
  import type { NonEmptyReadonlyArray } from "./Array.js"
4
4
  import { fakerArb } from "./faker.js"
5
- import { Email as EmailT } from "./Schema/email.js"
6
- import { withDefaultMake } from "./Schema/ext.js"
7
- import { PhoneNumber as PhoneNumberT } from "./Schema/phoneNumber.js"
8
- import type { AST } from "./Schema/schema.js"
5
+ import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
6
+ import { concurrencyUnbounded, withDefaultMake } from "./Schema/ext.js"
7
+ import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
9
8
  import { extendM } from "./utils.js"
10
9
 
11
10
  export * from "effect/Schema"
12
- // v4: TaggedError renamed to TaggedErrorClass
13
- export { TaggedErrorClass as TaggedError } from "effect/Schema"
14
11
 
15
12
  export * from "./Schema/Class.js"
16
13
  export { Class, TaggedClass } from "./Schema/Class.js"
17
14
 
18
15
  export { fromBrand, nominal } from "./Schema/brand.js"
19
- export { Array, Boolean, Date, Literal, Map, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
16
+ export { Array, Boolean, Date, DateFromString, DateValid, Finite, Literals, NullOr, Number, ReadonlyMap, ReadonlySet } from "./Schema/ext.js"
20
17
  export { Int, NonNegativeInt } from "./Schema/numbers.js"
21
18
 
22
19
  export * from "./Schema/email.js"
@@ -25,6 +22,8 @@ export * from "./Schema/moreStrings.js"
25
22
  export * from "./Schema/numbers.js"
26
23
  export * from "./Schema/phoneNumber.js"
27
24
  export * from "./Schema/schema.js"
25
+ export * from "./Schema/SpecialJsonSchema.js"
26
+ export * from "./Schema/SpecialOpenApi.js"
28
27
  export * from "./Schema/strings.js"
29
28
  export { NonEmptyString } from "./Schema/strings.js"
30
29
 
@@ -33,6 +32,58 @@ export * as SchemaParser from "effect/SchemaParser"
33
32
 
34
33
  export { Void as Void_ } from "effect/Schema"
35
34
 
35
+ // ---------------------------------------------------------------------------
36
+ // Struct / NonEmptyArray / Record — with concurrency: "unbounded"
37
+ // ---------------------------------------------------------------------------
38
+
39
+ export function Struct<const Fields extends S.Struct.Fields>(fields: Fields): S.Struct<Fields> {
40
+ const result = S.Struct(fields).annotate(concurrencyUnbounded)
41
+ // eslint-disable-next-line @typescript-eslint/unbound-method, @typescript-eslint/no-unsafe-assignment
42
+ const origMapFields: any = result.mapFields
43
+ ;(result as any).mapFields = function(this: any, f: any, options?: any) {
44
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
45
+ return origMapFields.call(this, f, options).annotate(concurrencyUnbounded)
46
+ }
47
+ return result
48
+ }
49
+ export interface Struct<Fields extends S.Struct.Fields> extends S.Struct<Fields> {}
50
+ export declare namespace Struct {
51
+ export type Fields = S.Struct.Fields
52
+ export type Type<F extends S.Struct.Fields> = S.Struct.Type<F>
53
+ export type Encoded<F extends S.Struct.Fields> = S.Struct.Encoded<F>
54
+ export type DecodingServices<F extends S.Struct.Fields> = S.Struct.DecodingServices<F>
55
+ export type EncodingServices<F extends S.Struct.Fields> = S.Struct.EncodingServices<F>
56
+ export type MakeIn<F extends S.Struct.Fields> = S.Struct.MakeIn<F>
57
+ export type Iso<F extends S.Struct.Fields> = S.Struct.Iso<F>
58
+ }
59
+
60
+ export function NonEmptyArray<Value extends S.Top>(value: Value): S.NonEmptyArray<Value> {
61
+ return S.NonEmptyArray(value).annotate(concurrencyUnbounded)
62
+ }
63
+
64
+ export function TaggedStruct<const Tag extends SchemaAST.LiteralValue, const Fields extends S.Struct.Fields>(
65
+ value: Tag,
66
+ fields: Fields
67
+ ): S.TaggedStruct<Tag, Fields> {
68
+ return Struct({ _tag: S.tag(value), ...fields }) as any
69
+ }
70
+ export type TaggedStruct<Tag extends SchemaAST.LiteralValue, Fields extends S.Struct.Fields> = S.TaggedStruct<
71
+ Tag,
72
+ Fields
73
+ >
74
+
75
+ export function Record<Key extends S.Record.Key, Value extends S.Top>(
76
+ key: Key,
77
+ value: Value
78
+ ): S.$Record<Key, Value> {
79
+ return S.Record(key, value).annotate(concurrencyUnbounded)
80
+ }
81
+ export declare namespace Record {
82
+ export type Key = S.Record.Key
83
+ export type Type<K extends S.Record.Key, V extends S.Top> = S.Record.Type<K, V>
84
+ export type Encoded<K extends S.Record.Key, V extends S.Top> = S.Record.Encoded<K, V>
85
+ }
86
+
36
87
  export const SpanId = Symbol()
37
88
  export type SpanId = typeof SpanId
38
89
 
@@ -40,127 +91,167 @@ export interface WithOptionalSpan {
40
91
  [SpanId]?: Tracer.Span
41
92
  }
42
93
 
94
+ const makeEmail = S.decodeSync(EmailT as any) as (value: string) => EmailType
95
+ const makePhoneNumber = S.decodeSync(PhoneNumberT as any) as (value: string) => PhoneNumberType
96
+
43
97
  export const Email = EmailT
44
98
  .pipe(
45
99
  S.annotate({
46
100
  // eslint-disable-next-line @typescript-eslint/unbound-method
47
- arbitrary: (): any => (fc: any) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(Email)
101
+ toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail)
48
102
  }),
49
103
  withDefaultMake
50
104
  )
51
105
 
52
- export type Email = EmailT
106
+ export type Email = EmailType
53
107
 
54
108
  export const PhoneNumber = PhoneNumberT
55
109
  .pipe(
56
110
  S.annotate({
57
- arbitrary: (): any => (fc: any) =>
111
+ toArbitrary: () => (fc) =>
58
112
  // eslint-disable-next-line @typescript-eslint/unbound-method
59
- fakerArb((faker) => faker.phone.number)(fc).map(PhoneNumber)
113
+ fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber)
60
114
  }),
61
115
  withDefaultMake
62
116
  )
63
117
 
64
- export const makeIs = <A extends { _tag: string }, I, R>(
65
- schema: S.Codec<A, I, R>
66
- ) => {
67
- // In v4, transformations are stored as encoding on nodes, not as wrapper nodes.
68
- // Union member ASTs are directly Objects (TypeLiteral equivalent).
69
- if (SchemaAST.isUnion(schema.ast)) {
70
- return schema.ast.types.reduce((acc: any, t: AST.AST) => {
71
- if (!SchemaAST.isObjects(t)) return acc
72
- const tag = Array.findFirst(t.propertySignatures, (_: any) => {
73
- if (_.name === "_tag" && SchemaAST.isLiteral(_.type)) {
74
- return Option.some(_.type)
75
- }
76
- return Option.none()
77
- })
78
- const ast = Option.getOrUndefined(tag)
79
- if (!ast) {
80
- return acc
81
- }
82
- return {
83
- ...acc,
84
- [String((ast as SchemaAST.Literal).literal)]: (x: { _tag: string }) =>
85
- x._tag === (ast as SchemaAST.Literal).literal
86
- }
87
- }, {} as Is<A>)
88
- }
89
- throw new Error("Unsupported")
90
- }
118
+ export type PhoneNumber = PhoneNumberType
91
119
 
92
- export const makeIsAnyOf = <A extends { _tag: string }, I, R>(
93
- schema: S.Codec<A, I, R>
94
- ): IsAny<A> => {
95
- if (SchemaAST.isUnion(schema.ast)) {
96
- return <Keys extends A["_tag"][]>(...keys: Keys) => (a: A): a is ExtractUnion<A, ElemType<Keys>> =>
97
- keys.includes(a._tag)
98
- }
99
- throw new Error("Unsupported")
120
+ // Copied from SchemaAST.collectSentinels (marked @internal in effect).
121
+ // Returns all { key, literal } pairs that can discriminate a union member.
122
+ const getTagFromAST = (schema: S.Top): string => {
123
+ const sentinels = collectSentinelsFromAST(schema.ast)
124
+ const sentinel = sentinels.find((s) => s.key === "_tag")
125
+ if (sentinel !== undefined && typeof sentinel.literal === "string") return sentinel.literal
126
+ throw new Error("No _tag literal found on schema member")
100
127
  }
101
128
 
102
- export type ExtractUnion<A extends { _tag: string }, Tags extends A["_tag"]> = Extract<A, Record<"_tag", Tags>>
103
- export type Is<A extends { _tag: string }> = { [K in A as K["_tag"]]: (a: A) => a is K }
104
- export type ElemType<A> = A extends Array<infer E> ? E : never
105
- export interface IsAny<A extends { _tag: string }> {
106
- <Keys extends A["_tag"][]>(...keys: Keys): (a: A) => a is ExtractUnion<A, ElemType<Keys>>
129
+ function collectSentinelsFromAST(
130
+ ast: SchemaAST.AST
131
+ ): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> {
132
+ switch (ast._tag) {
133
+ case "Declaration": {
134
+ const s = ast.annotations?.["~sentinels"]
135
+ return Array.isArray(s) ? s : []
136
+ }
137
+ case "Objects":
138
+ return ast.propertySignatures.flatMap(
139
+ (ps): Array<{ key: PropertyKey; literal: SchemaAST.LiteralValue | symbol }> => {
140
+ const type = ps.type
141
+ if (!SchemaAST.isOptional(type)) {
142
+ if (SchemaAST.isLiteral(type)) return [{ key: ps.name, literal: type.literal }]
143
+ if (SchemaAST.isUniqueSymbol(type)) return [{ key: ps.name, literal: type.symbol }]
144
+ }
145
+ return []
146
+ }
147
+ )
148
+ case "Suspend":
149
+ return collectSentinelsFromAST(ast.thunk())
150
+ default:
151
+ return []
152
+ }
107
153
  }
108
154
 
109
- export const taggedUnionMap = <
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]
112
- >(
113
- self: Members
114
- ) =>
115
- self.reduce((acc, key) => {
116
- // TODO: v4 migration — PropertySignatureDeclaration removed, need v4 AST traversal
117
- const ast = key.fields._tag.ast as any
118
- const tag = ((ast.type ?? ast) as SchemaAST.Literal).literal as string // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
119
- acc[tag] = key as any
120
- return acc
121
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- }, {} as any)
123
-
124
155
  export const tags = <
125
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
- Members extends NonEmptyReadonlyArray<(S.Top & { fields: { _tag: S.tag<string> } })>
156
+ Members extends NonEmptyReadonlyArray<(S.Top & { readonly Type: { readonly _tag: string } })>
127
157
  >(
128
158
  self: Members
129
159
  ) =>
130
- S.Literals(self.map((key) => {
131
- // TODO: v4 migration — PropertySignatureDeclaration removed, need v4 AST traversal
132
- const ast = key.fields._tag.ast as any
133
- const tag = ((ast.type ?? ast) as SchemaAST.Literal).literal
134
- return tag
135
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
136
- })) as any
137
-
138
- export const ExtendTaggedUnion = <A extends { _tag: string }, I, R>(
139
- schema: S.Codec<A, I, R>
140
- ) =>
141
- extendM(
142
- schema,
143
- (_) => ({
144
- is: S.is(schema as any),
145
- isA: makeIs(_ as any),
146
- isAnyOf: makeIsAnyOf(_ as any) /*, map: taggedUnionMap(a) */
147
- })
148
- )
160
+ S.Literals(
161
+ self.map(getTagFromAST) as {
162
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
163
+ }
164
+ ) as S.Literals<
165
+ {
166
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
167
+ }
168
+ >
149
169
 
150
- export const TaggedUnion = <
151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
- Members extends readonly (S.Top & { fields: { _tag: S.tag<any> } })[]
153
- >(...a: Members) =>
154
- pipe(
155
- S.Union(a),
156
- (_) =>
157
- extendM(_, (_) => ({
158
- is: S.is(_ as any),
159
- isA: makeIs(_ as any),
160
- isAnyOf: makeIsAnyOf(_ as any),
161
- tagMap: taggedUnionMap(a),
162
- tags: tags(a as any)
163
- }))
164
- )
170
+ type TaggedUnionMembers = NonEmptyReadonlyArray<
171
+ S.Top & { readonly Type: { readonly _tag: string } }
172
+ >
173
+
174
+ type TaggedUnionTags<Members extends TaggedUnionMembers> = S.Literals<
175
+ {
176
+ [Index in keyof Members]: Members[Index]["Type"]["_tag"]
177
+ }
178
+ >
179
+
180
+ type TaggedPropertyKeys<A, Members extends TaggedUnionMembers> = {
181
+ [K in keyof A & string]: A[K] extends Members[number]["Type"] ? K : never
182
+ }[keyof A & string]
183
+
184
+ type PropertyGuardsFor<
185
+ Members extends TaggedUnionMembers,
186
+ K extends string,
187
+ A
188
+ > =
189
+ & {
190
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: (
191
+ target: A
192
+ ) => target is A & { readonly [P in K]: M["Type"] }
193
+ }
194
+ & {
195
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
196
+ tags: Tags
197
+ ) => (
198
+ target: A
199
+ ) => target is A & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
200
+ }
201
+
202
+ type PropertyGuards<
203
+ Members extends TaggedUnionMembers,
204
+ K extends string
205
+ > =
206
+ & {
207
+ readonly [M in Members[number] as `is${M["Type"]["_tag"]}`]: <
208
+ T extends { readonly [P in K]: Members[number]["Type"] }
209
+ >(target: T) => target is T & { readonly [P in K]: M["Type"] }
210
+ }
211
+ & {
212
+ readonly isAnyOf: <const Tags extends ReadonlyArray<Members[number]["Type"]["_tag"]>>(
213
+ tags: Tags
214
+ ) => <T extends { readonly [P in K]: Members[number]["Type"] }>(
215
+ target: T
216
+ ) => target is T & { readonly [P in K]: Extract<Members[number]["Type"], { readonly _tag: Tags[number] }> }
217
+ }
218
+
219
+ type TaggedUnionWithTags<Members extends TaggedUnionMembers> = S.toTaggedUnion<"_tag", Members> & {
220
+ readonly tags: TaggedUnionTags<Members>
221
+ readonly generateGuards: <K extends string>(property: K) => PropertyGuards<Members, K>
222
+ readonly generateGuardsFor: <A>() => <K extends TaggedPropertyKeys<A, Members>>(
223
+ property: K
224
+ ) => PropertyGuardsFor<Members, K, A>
225
+ }
165
226
 
166
- export type PhoneNumber = PhoneNumberT
227
+ const extendTaggedUnionWithTags = <Members extends TaggedUnionMembers>(
228
+ schema: S.Union<Members>
229
+ ): TaggedUnionWithTags<Members> =>
230
+ extendM(schema.pipe(S.toTaggedUnion("_tag")), (tagged) => {
231
+ const makeGuards = (property: string) => {
232
+ const result: any = {}
233
+ const guards: Record<string, (u: unknown) => boolean> = tagged.guards
234
+ for (const tag of Object.keys(guards)) {
235
+ const guard = guards[tag]!
236
+ result[`is${tag}`] = (target: any) => guard(target[property])
237
+ }
238
+ result.isAnyOf = (memberTags: Array<string>) => {
239
+ const check = tagged.isAnyOf(memberTags)
240
+ return (target: any) => check(target[property])
241
+ }
242
+ return result
243
+ }
244
+ return {
245
+ tags: tags(schema.members),
246
+ generateGuards: makeGuards,
247
+ generateGuardsFor: () => makeGuards
248
+ }
249
+ })
250
+
251
+ export const ExtendTaggedUnion = <Members extends TaggedUnionMembers>(
252
+ schema: S.Union<Members>
253
+ ): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(schema)
254
+
255
+ export const TaggedUnion = <
256
+ Members extends TaggedUnionMembers
257
+ >(...a: Members): TaggedUnionWithTags<Members> => extendTaggedUnionWithTags(S.Union(a))
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import * as Config from "effect/Config"
3
2
  import { flow } from "effect/Function"
4
3
  import * as Layer from "effect/Layer"
5
4
  import * as ManagedRuntime from "effect/ManagedRuntime"
@@ -7,13 +6,14 @@ import * as Predicate from "effect/Predicate"
7
6
  import * as Schema from "effect/Schema"
8
7
  import * as Struct from "effect/Struct"
9
8
  import { Rpc, RpcClient, RpcGroup, RpcSerialization } from "effect/unstable/rpc"
9
+ import * as Config from "../Config.js"
10
+ import * as Context from "../Context.js"
10
11
  import * as Effect from "../Effect.js"
11
12
  import { HttpClient, HttpClientRequest } from "../http.js"
12
13
  import * as Option from "../Option.js"
13
14
  import type * as S from "../Schema.js"
14
- import * as ServiceMap from "../ServiceMap.js"
15
15
  import { typedKeysOf, typedValuesOf } from "../utils.js"
16
- import type { Client, ClientForOptions, Requests, RequestsAny } from "./clientFor.js"
16
+ import type { Client, ClientForOptions, ExtractModuleName, RequestsAny } from "./clientFor.js"
17
17
 
18
18
  export interface ApiConfig {
19
19
  url: string
@@ -37,10 +37,12 @@ export type Req = S.Top & {
37
37
  success: S.Top
38
38
  error: S.Top
39
39
  config?: Record<string, any>
40
+ readonly id: string
41
+ readonly moduleName: string
40
42
  readonly "~decodingServices"?: unknown
41
43
  }
42
44
 
43
- class RequestName extends ServiceMap.Reference("RequestName", {
45
+ class RequestName extends Context.Reference("RequestName", {
44
46
  defaultValue: () => ({ requestName: "Unspecified", moduleName: "Error" })
45
47
  }) {}
46
48
 
@@ -84,7 +86,7 @@ type RpcHandlers<M extends RequestsAny> = {
84
86
  [K in keyof M]: Rpc.Rpc<M[K]["_tag"], M[K], M[K]["success"], M[K]["error"]>
85
87
  }
86
88
 
87
- const getFiltered = <M extends Requests>(resource: M) => {
89
+ const getFiltered = <M extends RequestsAny>(resource: M) => {
88
90
  type Filtered = {
89
91
  [K in keyof M as M[K] extends Req ? K : never]: M[K] extends Req ? M[K] : never
90
92
  }
@@ -102,13 +104,13 @@ const getFiltered = <M extends Requests>(resource: M) => {
102
104
  return filtered as unknown as Filtered
103
105
  }
104
106
 
105
- export const getMeta = <M extends Requests>(resource: M) => {
106
- const meta = (resource as any).meta as { moduleName: string }
107
- if (!meta) throw new Error("No meta defined in Resource!")
108
- return meta as M["meta"]
107
+ export const getMeta = <M extends RequestsAny>(resource: M): { moduleName: ExtractModuleName<M> } => {
108
+ const first = typedValuesOf(getFiltered(resource))[0]
109
+ if (first && "moduleName" in first) return { moduleName: first.moduleName } as any
110
+ throw new Error("No moduleName on requests!")
109
111
  }
110
112
 
111
- export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const ModuleName extends string>(
113
+ export const makeRpcGroupFromRequestsAndModuleName = <M extends RequestsAny, const ModuleName extends string>(
112
114
  resource: M,
113
115
  moduleName: ModuleName
114
116
  ) => {
@@ -126,20 +128,13 @@ export const makeRpcGroupFromRequestsAndModuleName = <M extends Requests, const
126
128
  return rpcs
127
129
  }
128
130
 
129
- export const makeRpcGroup = <
130
- M extends Requests,
131
- const ModuleName extends string
132
- >(
133
- resource: M & { meta: { moduleName: ModuleName } }
134
- ) => makeRpcGroupFromRequestsAndModuleName(resource, resource.meta.moduleName)
135
-
136
- const makeRpcTag = <M extends Requests>(resource: M) => {
131
+ const makeRpcTag = <M extends RequestsAny>(resource: M) => {
137
132
  const meta = getMeta(resource)
138
133
  const rpcs = makeRpcGroupFromRequestsAndModuleName(resource, meta.moduleName)
139
134
 
140
135
  // Use Object.assign instead of class extension to avoid TS2509 with complex generic return types.
141
136
  // The first type arg is `any` because this is a dynamically created tag — its identity is the string key.
142
- const TheClient = ServiceMap.Opaque<
137
+ const TheClient = Context.Opaque<
143
138
  any,
144
139
  RpcClient.RpcClient<RpcGroup.Rpcs<typeof rpcs>>
145
140
  >()(`RpcClient.${meta.moduleName}`)
@@ -153,8 +148,8 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
153
148
 
154
149
  const makeApiClientFactory = Effect
155
150
  .gen(function*() {
156
- const ctx = yield* Effect.services<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
157
- const makeClientFor = <M extends Requests>(
151
+ const ctx = yield* Effect.context<RpcSerialization.RpcSerialization | HttpClient.HttpClient>()
152
+ const makeClientFor = <M extends RequestsAny>(
158
153
  resource: M,
159
154
  requestLevelLayers = Layer.empty,
160
155
  options?: ClientForOptions
@@ -176,7 +171,7 @@ const makeApiClientFactory = Effect
176
171
  url: "" // why not here set meta.moduleName as root?
177
172
  })
178
173
  .pipe(
179
- Layer.provideMerge(Layer.succeedServices(ctx))
174
+ Layer.provideMerge(Layer.succeedContext(ctx))
180
175
  )
181
176
  )
182
177
  )
@@ -212,7 +207,7 @@ const makeApiClientFactory = Effect
212
207
  // @ts-expect-error doc
213
208
  prev[cur] = Object.keys(fields).length === 0
214
209
  ? {
215
- handler: mr.servicesEffect.pipe(
210
+ handler: mr.contextEffect.pipe(
216
211
  Effect.flatMap((svcs) =>
217
212
  TheClient
218
213
  .use((client) => (client as any)[requestAttr]!(new Request()) as Effect.Effect<any, any, never>)
@@ -226,7 +221,7 @@ const makeApiClientFactory = Effect
226
221
  }
227
222
  : {
228
223
  handler: (req: any) =>
229
- mr.servicesEffect.pipe(
224
+ mr.contextEffect.pipe(
230
225
  Effect.flatMap((svcs) =>
231
226
  TheClient
232
227
  .use((client) =>
@@ -243,7 +238,7 @@ const makeApiClientFactory = Effect
243
238
  }
244
239
 
245
240
  return prev
246
- }, {} as Client<M, M["meta"]["moduleName"]>)
241
+ }, {} as Client<M, ExtractModuleName<M>>)
247
242
  }
248
243
  })
249
244
 
@@ -259,9 +254,9 @@ const makeApiClientFactory = Effect
259
254
  cacheL.set(requestLevelLayers, cache)
260
255
  }
261
256
 
262
- return <M extends Requests>(
257
+ return <M extends RequestsAny>(
263
258
  models: M
264
- ): Effect.Effect<Client<M, M["meta"]["moduleName"]>> =>
259
+ ): Effect.Effect<Client<M, ExtractModuleName<M>>> =>
265
260
  Effect.gen(function*() {
266
261
  const found = cache.get(models)
267
262
  if (found) {
@@ -281,7 +276,7 @@ const makeApiClientFactory = Effect
281
276
  * Used to create clients for resource modules.
282
277
  */
283
278
  export class ApiClientFactory
284
- extends ServiceMap.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
279
+ extends Context.Opaque<ApiClientFactory, Effect.Success<typeof makeApiClientFactory>>()("ApiClientFactory")
285
280
  {
286
281
  static readonly layer = (config: ApiConfig) =>
287
282
  ApiClientFactory.toLayer(makeApiClientFactory).pipe(Layer.provide(RpcSerializationLayer(config)))
@@ -294,7 +289,7 @@ export class ApiClientFactory
294
289
 
295
290
  static readonly makeFor =
296
291
  (requestLevelLayers: Layer.Layer<never, never, never>, options?: ClientForOptions) =>
297
- <M extends Requests>(
292
+ <M extends RequestsAny>(
298
293
  resource: M
299
294
  ) =>
300
295
  ApiClientFactory.use((apiClientFactory) => {
@@ -48,9 +48,14 @@ export function makePathWithBody(
48
48
  return path.build(pars, { ignoreSearch: true, ignoreConstraints: true })
49
49
  }
50
50
 
51
- export type Requests<ModuleName extends string = string> = { meta: { moduleName: ModuleName } } & RequestsAny
51
+ export type Requests = RequestsAny
52
52
  export type RequestsAny = Record<string, any>
53
53
 
54
+ export type ExtractModuleName<M extends RequestsAny> =
55
+ { [K in keyof M]: M[K] extends { moduleName: infer N extends string } ? N : never }[keyof M] extends
56
+ infer R extends string ? R
57
+ : string
58
+
54
59
  export type Client<M extends RequestsAny, ModuleName extends string> = RequestHandlers<
55
60
  never,
56
61
  never,