effect-app 4.0.0-beta.2 → 4.0.0-beta.20

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 (68) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/dist/Operations.d.ts +14 -14
  3. package/dist/Pure.js +1 -1
  4. package/dist/Schema/brand.d.ts +8 -5
  5. package/dist/Schema/brand.d.ts.map +1 -1
  6. package/dist/Schema/brand.js +1 -1
  7. package/dist/Schema/email.d.ts.map +1 -1
  8. package/dist/Schema/email.js +4 -3
  9. package/dist/Schema/ext.d.ts +26 -25
  10. package/dist/Schema/ext.d.ts.map +1 -1
  11. package/dist/Schema/ext.js +13 -20
  12. package/dist/Schema/moreStrings.d.ts +6 -6
  13. package/dist/Schema/moreStrings.d.ts.map +1 -1
  14. package/dist/Schema/moreStrings.js +6 -4
  15. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  16. package/dist/Schema/phoneNumber.js +3 -2
  17. package/dist/Schema.d.ts +27 -24
  18. package/dist/Schema.d.ts.map +1 -1
  19. package/dist/Schema.js +23 -22
  20. package/dist/ServiceMap.d.ts +3 -3
  21. package/dist/ServiceMap.d.ts.map +1 -1
  22. package/dist/ServiceMap.js +1 -1
  23. package/dist/client/apiClientFactory.d.ts.map +1 -1
  24. package/dist/client/apiClientFactory.js +8 -9
  25. package/dist/client/errors.d.ts.map +1 -1
  26. package/dist/client/errors.js +1 -1
  27. package/dist/client/makeClient.d.ts +6 -6
  28. package/dist/client/makeClient.d.ts.map +1 -1
  29. package/dist/client/makeClient.js +3 -14
  30. package/dist/ids.d.ts +6 -6
  31. package/dist/ids.d.ts.map +1 -1
  32. package/dist/ids.js +1 -1
  33. package/dist/index.d.ts +0 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -2
  36. package/dist/rpc/MiddlewareMaker.d.ts +1 -1
  37. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  38. package/dist/rpc/MiddlewareMaker.js +1 -1
  39. package/dist/rpc/RpcMiddleware.d.ts +1 -1
  40. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  41. package/dist/utils.d.ts +18 -0
  42. package/dist/utils.d.ts.map +1 -1
  43. package/dist/utils.js +24 -5
  44. package/package.json +3 -7
  45. package/src/Pure.ts +2 -2
  46. package/src/Schema/brand.ts +13 -7
  47. package/src/Schema/email.ts +4 -2
  48. package/src/Schema/ext.ts +46 -38
  49. package/src/Schema/moreStrings.ts +16 -13
  50. package/src/Schema/phoneNumber.ts +3 -1
  51. package/src/Schema.ts +56 -40
  52. package/src/ServiceMap.ts +7 -6
  53. package/src/client/apiClientFactory.ts +12 -15
  54. package/src/client/errors.ts +12 -3
  55. package/src/client/makeClient.ts +6 -19
  56. package/src/ids.ts +1 -1
  57. package/src/index.ts +0 -1
  58. package/src/rpc/MiddlewareMaker.ts +2 -1
  59. package/src/rpc/RpcMiddleware.ts +2 -2
  60. package/src/utils.ts +26 -4
  61. package/test/dist/moreStrings.test.d.ts.map +1 -0
  62. package/test/dist/rpc.test.d.ts.map +1 -1
  63. package/test/moreStrings.test.ts +17 -0
  64. package/test/schema.test.ts +32 -1
  65. package/dist/Struct.d.ts +0 -44
  66. package/dist/Struct.d.ts.map +0 -1
  67. package/dist/Struct.js +0 -29
  68. package/src/Struct.ts +0 -54
package/src/Schema/ext.ts CHANGED
@@ -1,11 +1,18 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-return */
3
- import { Effect, Option, pipe, Schema, type SchemaAST, SchemaGetter, SchemaIssue, SchemaParser, SchemaTransformation, type ServiceMap } from "effect"
3
+ import { Effect, Option, pipe, Schema, type SchemaAST, SchemaGetter, SchemaIssue, SchemaTransformation, ServiceMap } from "effect"
4
4
  import * as S from "effect/Schema"
5
5
  import { type NonEmptyReadonlyArray } from "../Array.js"
6
6
  import { extendM, typedKeysOf } from "../utils.js"
7
7
  import { type AST } from "./schema.js"
8
8
 
9
+ type ProvidedCodec<Self extends S.Top, R> = S.Codec<
10
+ Self["Type"],
11
+ Self["Encoded"],
12
+ Exclude<Self["DecodingServices"], R>,
13
+ Exclude<Self["EncodingServices"], R>
14
+ >
15
+
9
16
  // TODO: v4 migration — withConstructorDefault signature changed, propertySignature removed
10
17
  // Constraint relaxed from `Self extends S.Top & S.WithoutConstructorDefault` to `Self extends S.Top`
11
18
  // because `.pipe()` widens the schema type to `Top` which doesn't satisfy `WithoutConstructorDefault`.
@@ -69,7 +76,7 @@ export const Literal = <Literals extends NonEmptyReadonlyArray<AST.LiteralValue>
69
76
  /**
70
77
  * Like the default Schema `Array` but with `withDefault` => []
71
78
  */
72
- export function Array<Value extends S.Top>(value: Value) {
79
+ export function Array<ValueSchema extends S.Top>(value: ValueSchema) {
73
80
  return pipe(
74
81
  S.Array(value),
75
82
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => [])) })
@@ -79,7 +86,7 @@ export function Array<Value extends S.Top>(value: Value) {
79
86
  /**
80
87
  * Like the default Schema `Map` but with `withDefault` => []
81
88
  */
82
- function Map_<Key extends S.Top, Value extends S.Top>(input: { key: Key; value: Value }) {
89
+ function Map_<KeySchema extends S.Top, ValueSchema extends S.Top>(input: { key: KeySchema; value: ValueSchema }) {
83
90
  return pipe(
84
91
  S.ReadonlyMap(input.key, input.value),
85
92
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new global.Map())) })
@@ -91,18 +98,19 @@ export { Map_ as Map }
91
98
  /**
92
99
  * Like the default Schema `ReadonlySet` but with `withDefault` => new Set()
93
100
  */
94
- export const ReadonlySet = <Value extends S.Top>(value: Value) =>
101
+ export const ReadonlySet = <ValueSchema extends S.Top>(value: ValueSchema) =>
95
102
  pipe(
96
103
  S.ReadonlySet(value),
97
- (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<Value>>())) })
104
+ (s) =>
105
+ Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => new Set<S.Schema.Type<ValueSchema>>())) })
98
106
  )
99
107
 
100
108
  /**
101
109
  * Like the default Schema `ReadonlyMap` but with `withDefault` => new Map()
102
110
  */
103
- export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
104
- readonly key: K
105
- readonly value: V
111
+ export const ReadonlyMap = <KeySchema extends S.Top, ValueSchema extends S.Top>(pair: {
112
+ readonly key: KeySchema
113
+ readonly value: ValueSchema
106
114
  }) =>
107
115
  pipe(
108
116
  S.ReadonlyMap(pair.key, pair.value),
@@ -112,25 +120,24 @@ export const ReadonlyMap = <K extends S.Top, V extends S.Top>(pair: {
112
120
  /**
113
121
  * Like the default Schema `NullOr` but with `withDefault` => null
114
122
  */
115
- export const NullOr = <S extends S.Top>(self: S) =>
123
+ export const NullOr = <Schema extends S.Top>(self: Schema) =>
116
124
  pipe(
117
125
  S.NullOr(self),
118
126
  (s) => Object.assign(s, { withDefault: s.pipe(withDefaultConstructor(() => null)) })
119
127
  )
120
128
 
121
- export const defaultDate = (s: S.Top) => s.pipe(withDefaultConstructor(() => new global.Date()))
129
+ export const defaultDate = <Schema extends S.Top>(schema: Schema) =>
130
+ schema.pipe(withDefaultConstructor(() => new global.Date()))
122
131
 
123
- export const defaultBool = (s: S.Top) => s.pipe(withDefaultConstructor(() => false))
132
+ export const defaultBool = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => false))
124
133
 
125
- export const defaultNullable = (
126
- s: S.Top
127
- ) => s.pipe(withDefaultConstructor(() => null))
134
+ export const defaultNullable = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => null))
128
135
 
129
- export const defaultArray = (s: S.Top) => s.pipe(withDefaultConstructor(() => []))
136
+ export const defaultArray = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => []))
130
137
 
131
- export const defaultMap = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Map()))
138
+ export const defaultMap = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Map()))
132
139
 
133
- export const defaultSet = (s: S.Top) => s.pipe(withDefaultConstructor(() => new Set()))
140
+ export const defaultSet = <Schema extends S.Top>(schema: Schema) => schema.pipe(withDefaultConstructor(() => new Set()))
134
141
 
135
142
  export const withDefaultMake = <Self extends S.Top>(s: Self) => {
136
143
  const a = Object.assign(S.decodeSync(s as any) as WithDefaults<Self>, s)
@@ -240,29 +247,30 @@ export const transformToOrFail = <To extends S.Top, From extends S.Top, RD>(
240
247
  )
241
248
  )
242
249
 
243
- // TODO: v4 migration — S.declare API changed (no [self] + decode/encode pattern)
244
- // Need to find v4 equivalent for contextual schema wrapping
245
250
  export const provide = <Self extends S.Top, R>(
246
251
  self: Self,
247
252
  context: ServiceMap.ServiceMap<R>
248
- ): any => {
253
+ ): ProvidedCodec<Self, R> => {
249
254
  const prov = Effect.provide(context)
250
- return S
251
- .declare((_u: unknown): _u is unknown => true) // placeholder — needs proper v4 declare
252
- .pipe(
253
- S.decodeTo(
254
- self,
255
- SchemaTransformation.transformOrFail({
256
- decode: (n: any) => prov(SchemaParser.decodeUnknownEffect(self)(n)),
257
- encode: (n: any) => prov(SchemaParser.encodeUnknownEffect(self)(n))
258
- }) as any
259
- ) as any
260
- )
261
- }
262
- // TODO: v4 migration — ServiceMap.pick and S.declare pattern removed
263
- export const contextFromServices = <Self extends S.Top, Tags extends readonly any[]>(
264
- _self: Self,
265
- ..._services: Tags
266
- ): any => {
267
- throw new Error("contextFromServices: not yet migrated to v4")
255
+ return self.pipe(
256
+ S.middlewareDecoding((effect) => prov(effect)),
257
+ S.middlewareEncoding((effect) => prov(effect))
258
+ ) as ProvidedCodec<Self, R>
268
259
  }
260
+ export const contextFromServices = <
261
+ Self extends S.Top,
262
+ Tags extends ReadonlyArray<ServiceMap.Key<any, any>>
263
+ >(
264
+ self: Self,
265
+ ...services: Tags
266
+ ): Effect.Effect<
267
+ ProvidedCodec<Self, ServiceMap.Service.Identifier<Tags[number]>>,
268
+ never,
269
+ ServiceMap.Service.Identifier<Tags[number]>
270
+ > =>
271
+ Effect.gen(function*() {
272
+ const context: ServiceMap.ServiceMap<ServiceMap.Service.Identifier<Tags[number]>> = ServiceMap.pick(...services)(
273
+ yield* Effect.services<ServiceMap.Service.Identifier<Tags[number]>>()
274
+ )
275
+ return provide(self, context)
276
+ })
@@ -140,11 +140,10 @@ const minLength = 6
140
140
  const maxLength = 50
141
141
  const size = 21
142
142
  const length = 10 * size
143
- const StringIdArb = (): any => (fc: any) =>
143
+ const StringIdArb = (): S.LazyArbitrary<StringId> => (fc) =>
144
144
  fc
145
145
  .uint8Array({ minLength: length, maxLength: length })
146
- .map((_: any) => customRandom(urlAlphabet, size, (size: number) => _.subarray(0, size))())
147
-
146
+ .map((_) => customRandom(urlAlphabet, size, (size) => _.subarray(0, size))() as StringId)
148
147
  /**
149
148
  * A string that is at least 6 characters long and a maximum of 50.
150
149
  */
@@ -155,7 +154,7 @@ export const StringId = extendM(
155
154
  fromBrand(nominal<StringId>(), {
156
155
  identifier: "StringId",
157
156
  title: "StringId",
158
- arbitrary: StringIdArb,
157
+ toArbitrary: () => (fc) => StringIdArb()(fc),
159
158
  jsonSchema: {}
160
159
  })
161
160
  ),
@@ -178,19 +177,21 @@ export function prefixedStringId<Brand extends StringId>() {
178
177
  ) => {
179
178
  type FullPrefix = `${Prefix}${Separator}`
180
179
  const pref = `${prefix}${separator ?? "-"}` as FullPrefix
181
- const arb = (): any => (fc: any) =>
180
+ const arb = (): S.LazyArbitrary<string & Brand> => (fc) =>
182
181
  StringIdArb()(fc).map(
183
- (x: any) => (pref + x.substring(0, 50 - pref.length)) as Brand
182
+ (x) => (pref + x.substring(0, 50 - pref.length)) as Brand
184
183
  )
185
184
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
186
185
  const s: S.Codec<string & Brand, string> = StringId
187
186
  .pipe(
188
187
  S.refine((x: string): x is string & Brand => x.startsWith(pref), {
189
- arbitrary: arb,
190
188
  identifier: name,
191
189
  title: name
190
+ }),
191
+ S.annotate({
192
+ toArbitrary: () => (fc) => arb()(fc)
192
193
  })
193
- ) as any
194
+ ) as S.Codec<string & Brand, string>
194
195
  const schema = s.pipe(withDefaultMake)
195
196
  const make = () => (pref + StringId.make().substring(0, 50 - pref.length)) as Brand
196
197
 
@@ -217,10 +218,10 @@ export const brandedStringId = <
217
218
  Brand extends StringIdBrand
218
219
  >() =>
219
220
  withDefaultMake(
220
- Object.assign(Object.create(StringId), StringId) as S.Codec<string & Brand> & {
221
+ Object.assign(Object.create(StringId), StringId) as S.Codec<string & Brand, string> & {
221
222
  make: () => string & Brand
222
- withDefault: any
223
- } & WithDefaults<S.Codec<string & Brand>>
223
+ withDefault: S.withConstructorDefault<S.Codec<string & Brand, string> & S.WithoutConstructorDefault>
224
+ } & WithDefaults<S.Codec<string & Brand, string>>
224
225
  )
225
226
 
226
227
  export interface PrefixedStringUtils<
@@ -232,7 +233,7 @@ export interface PrefixedStringUtils<
232
233
  readonly unsafeFrom: (str: string) => Brand
233
234
  prefixSafe: <REST extends string>(str: `${Prefix}${Separator}${REST}`) => Brand
234
235
  readonly prefix: Prefix
235
- readonly withDefault: any
236
+ readonly withDefault: S.withConstructorDefault<S.Codec<Brand, string> & S.WithoutConstructorDefault>
236
237
  }
237
238
 
238
239
  export interface UrlBrand extends Simplify<B.Brand<"Url"> & NonEmptyStringBrand> {}
@@ -247,10 +248,12 @@ export const Url = S
247
248
  .String
248
249
  .pipe(
249
250
  S.refine(isUrl, {
250
- arbitrary: (): any => (fc: any) => fc.webUrl().map((_: any) => _ as Url),
251
251
  identifier: "Url",
252
252
  title: "Url",
253
253
  jsonSchema: { format: "uri" }
254
254
  }),
255
+ S.annotate({
256
+ toArbitrary: () => (fc) => fc.webUrl().map((_) => _ as Url)
257
+ }),
255
258
  withDefaultMake
256
259
  )
@@ -17,8 +17,10 @@ export const PhoneNumber = S
17
17
  identifier: "PhoneNumber",
18
18
  title: "PhoneNumber",
19
19
  description: "a phone number with at least 7 digits",
20
- arbitrary: () => (fc: any) => Numbers(7, 10)(fc).map((_: any) => _ as PhoneNumber),
21
20
  jsonSchema: { format: "phone" }
22
21
  }),
22
+ S.annotate({
23
+ toArbitrary: () => (fc) => Numbers(7, 10)(fc).map((_) => _ as PhoneNumber)
24
+ }),
23
25
  withDefaultMake
24
26
  )
package/src/Schema.ts CHANGED
@@ -2,9 +2,9 @@ import { Array, Option, pipe, 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"
5
+ import { Email as EmailT, type Email as EmailType } from "./Schema/email.js"
6
6
  import { withDefaultMake } from "./Schema/ext.js"
7
- import { PhoneNumber as PhoneNumberT } from "./Schema/phoneNumber.js"
7
+ import { PhoneNumber as PhoneNumberT, type PhoneNumber as PhoneNumberType } from "./Schema/phoneNumber.js"
8
8
  import type { AST } from "./Schema/schema.js"
9
9
  import { extendM } from "./utils.js"
10
10
 
@@ -40,34 +40,39 @@ export interface WithOptionalSpan {
40
40
  [SpanId]?: Tracer.Span
41
41
  }
42
42
 
43
+ const makeEmail = S.decodeSync(EmailT as any) as (value: string) => EmailType
44
+ const makePhoneNumber = S.decodeSync(PhoneNumberT as any) as (value: string) => PhoneNumberType
45
+
43
46
  export const Email = EmailT
44
47
  .pipe(
45
48
  S.annotate({
46
49
  // eslint-disable-next-line @typescript-eslint/unbound-method
47
- arbitrary: (): any => (fc: any) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(Email)
50
+ toArbitrary: () => (fc) => fakerArb((faker) => faker.internet.exampleEmail)(fc).map(makeEmail)
48
51
  }),
49
52
  withDefaultMake
50
53
  )
51
54
 
52
- export type Email = EmailT
55
+ export type Email = EmailType
53
56
 
54
57
  export const PhoneNumber = PhoneNumberT
55
58
  .pipe(
56
59
  S.annotate({
57
- arbitrary: (): any => (fc: any) =>
60
+ toArbitrary: () => (fc) =>
58
61
  // eslint-disable-next-line @typescript-eslint/unbound-method
59
- fakerArb((faker) => faker.phone.number)(fc).map(PhoneNumber)
62
+ fakerArb((faker) => faker.phone.number)(fc).map(makePhoneNumber)
60
63
  }),
61
64
  withDefaultMake
62
65
  )
63
66
 
64
- export const makeIs = <A extends { _tag: string }, I, R>(
65
- schema: S.Codec<A, I, R>
67
+ export type PhoneNumber = PhoneNumberType
68
+
69
+ export const makeIs = <A extends { _tag: string }, I, RD, RE>(
70
+ schema: S.Codec<A, I, RD, RE>
66
71
  ) => {
67
72
  // In v4, transformations are stored as encoding on nodes, not as wrapper nodes.
68
73
  // Union member ASTs are directly Objects (TypeLiteral equivalent).
69
74
  if (SchemaAST.isUnion(schema.ast)) {
70
- return schema.ast.types.reduce((acc: any, t: AST.AST) => {
75
+ return schema.ast.types.reduce((acc, t: AST.AST) => {
71
76
  if (!SchemaAST.isObjects(t)) return acc
72
77
  const tag = Array.findFirst(t.propertySignatures, (_: any) => {
73
78
  if (_.name === "_tag" && SchemaAST.isLiteral(_.type)) {
@@ -89,8 +94,8 @@ export const makeIs = <A extends { _tag: string }, I, R>(
89
94
  throw new Error("Unsupported")
90
95
  }
91
96
 
92
- export const makeIsAnyOf = <A extends { _tag: string }, I, R>(
93
- schema: S.Codec<A, I, R>
97
+ export const makeIsAnyOf = <A extends { _tag: string }, I, RD, RE>(
98
+ schema: S.Codec<A, I, RD, RE>
94
99
  ): IsAny<A> => {
95
100
  if (SchemaAST.isUnion(schema.ast)) {
96
101
  return <Keys extends A["_tag"][]>(...keys: Keys) => (a: A): a is ExtractUnion<A, ElemType<Keys>> =>
@@ -106,20 +111,30 @@ export interface IsAny<A extends { _tag: string }> {
106
111
  <Keys extends A["_tag"][]>(...keys: Keys): (a: A) => a is ExtractUnion<A, ElemType<Keys>>
107
112
  }
108
113
 
114
+ const getTagLiteral = <Tag extends string>(schema: S.tag<Tag>): Tag => {
115
+ if (!SchemaAST.isLiteral(schema.ast)) {
116
+ throw new Error("Unsupported _tag schema: expected a literal AST")
117
+ }
118
+ return schema.ast.literal as Tag
119
+ }
120
+
121
+ type TaggedUnionMap<Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]> = {
122
+ [Key in Members[number] as Key["fields"]["_tag"]["Type"]]: Key
123
+ }
124
+
109
125
  export const taggedUnionMap = <
110
126
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
127
  Members extends readonly (S.Top & { fields: { _tag: S.tag<string> } })[]
112
128
  >(
113
129
  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 as any)[tag] = key as any
120
- return acc
121
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
- }, {} as any)
130
+ ) => {
131
+ const out = {} as TaggedUnionMap<Members>
132
+ for (const key of self) {
133
+ const tag = getTagLiteral(key.fields._tag) as keyof TaggedUnionMap<Members>
134
+ out[tag] = key as TaggedUnionMap<Members>[typeof tag]
135
+ }
136
+ return out
137
+ }
123
138
 
124
139
  export const tags = <
125
140
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -127,40 +142,41 @@ export const tags = <
127
142
  >(
128
143
  self: Members
129
144
  ) =>
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>(
145
+ S.Literals(
146
+ self.map((key) => getTagLiteral(key.fields._tag)) as {
147
+ [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
148
+ }
149
+ ) as S.Literals<
150
+ {
151
+ [Index in keyof Members]: S.Schema.Type<Members[Index]["fields"]["_tag"]>
152
+ }
153
+ >
154
+
155
+ export const ExtendTaggedUnion = <A extends { readonly _tag: string }, I, R>(
139
156
  schema: S.Codec<A, I, R>
140
157
  ) =>
141
158
  extendM(
142
159
  schema,
143
160
  (_) => ({
144
- is: S.is(schema as any),
145
- isA: makeIs(_ as any),
146
- isAnyOf: makeIsAnyOf(_ as any) /*, map: taggedUnionMap(a) */
161
+ // is: S.is(schema), // only works with never DecodingServices
162
+ isA: makeIs(_),
163
+ isAnyOf: makeIsAnyOf(_) /*, map: taggedUnionMap(a) */
147
164
  })
148
165
  )
149
166
 
150
167
  export const TaggedUnion = <
151
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
- Members extends readonly (S.Top & { fields: { _tag: S.tag<any> } })[]
168
+ Members extends NonEmptyReadonlyArray<
169
+ S.Codec<{ readonly _tag: string }, any, any, any> & { fields: { _tag: S.tag<string> } }
170
+ >
153
171
  >(...a: Members) =>
154
172
  pipe(
155
173
  S.Union(a),
156
174
  (_) =>
157
175
  extendM(_, (_) => ({
158
- is: S.is(_ as any),
159
- isA: makeIs(_ as any),
160
- isAnyOf: makeIsAnyOf(_ as any),
176
+ // is: S.is(_), // only works with never DecodingServices
177
+ isA: makeIs(_),
178
+ isAnyOf: makeIsAnyOf(_),
161
179
  tagMap: taggedUnionMap(a),
162
- tags: tags(a as any)
180
+ tags: tags(a)
163
181
  }))
164
182
  )
165
-
166
- export type PhoneNumber = PhoneNumberT
package/src/ServiceMap.ts CHANGED
@@ -7,13 +7,14 @@
7
7
 
8
8
  import { type Effect, Layer, type Scope, type Types } from "effect"
9
9
  import * as ServiceMap from "effect/ServiceMap"
10
- import { Yieldable } from "./Effect.js"
10
+ import { type Yieldable } from "./Effect.js"
11
11
 
12
12
  export * from "effect/ServiceMap"
13
13
 
14
- export interface Opaque<Self extends object, in out Shape extends object> extends ServiceMap.Key<Self, Self>, Yieldable<Opaque<Self, Shape>, Self, never, Self> {
15
- // temp while sorting out https://github.com/Effect-TS/effect-smol/pull/1534
16
- of(self: Shape): Self
14
+ export interface Opaque<Self extends object, in out Shape extends object>
15
+ extends ServiceMap.Key<Self, Self>, Yieldable<Opaque<Self, Shape>, Self, never, Self>
16
+ {
17
+ of(this: void, self: Shape): Self
17
18
  serviceMap(self: Shape): ServiceMap.ServiceMap<Self>
18
19
  // a version that leverages the Shape -> Self conversion
19
20
  toLayer: <E, R>(
@@ -151,7 +152,7 @@ export const Opaque: {
151
152
  id: Identifier,
152
153
  options?: {
153
154
  readonly make: ((...args: Args) => Effect.Effect<Shape, E, R>) | Effect.Effect<Shape, E, R> | undefined
154
- } | undefined
155
+ }
155
156
  ) =>
156
157
  & OpaqueClass<Self, Identifier, Shape>
157
158
  & ([Types.unassigned] extends [R] ? unknown
@@ -181,7 +182,7 @@ export const Opaque: {
181
182
  const svc = ServiceMap.Service()(id, options) as any
182
183
  return Object.assign(svc, {
183
184
  toLayer: (eff: Effect.Effect<any, any, any>) => {
184
- return Layer.effect(svc as any, eff)
185
+ return Layer.effect(svc, eff)
185
186
  }
186
187
  })
187
188
  }
@@ -50,17 +50,17 @@ export const HttpClientLayer = (config: ApiConfig) =>
50
50
  Effect
51
51
  .gen(function*() {
52
52
  const baseClient = yield* HttpClient.HttpClient
53
- const ctx = yield* RequestName
54
53
  const client = baseClient.pipe(
55
54
  HttpClient.mapRequest(HttpClientRequest.prependUrl(config.url + "/rpc")),
56
55
  HttpClient.mapRequest(
57
56
  HttpClientRequest.setHeaders(config.headers.pipe(Option.getOrElse(() => ({}))))
58
57
  ),
59
- HttpClient.mapRequest((req) =>
60
- flow(
61
- HttpClientRequest.appendUrlParam("action", ctx.requestName),
62
- HttpClientRequest.appendUrl("/" + ctx.moduleName)
63
- )(req)
58
+ HttpClient.mapRequestEffect((req) =>
59
+ Effect.map(RequestName.asEffect(), (ctx) =>
60
+ flow(
61
+ HttpClientRequest.appendUrlParam("action", ctx.requestName),
62
+ HttpClientRequest.appendUrl("/" + ctx.moduleName)
63
+ )(req))
64
64
  )
65
65
  )
66
66
  return client
@@ -76,7 +76,7 @@ export const HttpClientFromConfigLayer = Layer.unwrap(
76
76
 
77
77
  export const RpcSerializationLayer = (config: ApiConfig) =>
78
78
  Layer.mergeAll(
79
- RpcSerialization.layerJson,
79
+ RpcSerialization.layerNdjson,
80
80
  HttpClientLayer(config)
81
81
  )
82
82
 
@@ -91,7 +91,7 @@ const getFiltered = <M extends Requests>(resource: M) => {
91
91
  // TODO: Record.filter
92
92
  const filtered = typedKeysOf(resource).reduce((acc, cur) => {
93
93
  if (
94
- Predicate.isObject(resource[cur])
94
+ Predicate.isObjectKeyword(resource[cur])
95
95
  && (resource[cur].success)
96
96
  ) {
97
97
  acc[cur as keyof Filtered] = resource[cur] as any
@@ -146,10 +146,7 @@ const makeRpcTag = <M extends Requests>(resource: M) => {
146
146
  // Use Layer.effect directly (not TheClient.toLayer) so TypeScript properly excludes Scope
147
147
  const layer = Layer.effect(
148
148
  TheClient,
149
- Effect.map(
150
- RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName }),
151
- (cl) => (cl as any)[meta.moduleName]
152
- )
149
+ RpcClient.make(rpcs, { spanPrefix: "RpcClient." + meta.moduleName })
153
150
  )
154
151
  return Object.assign(TheClient, { layer })
155
152
  }
@@ -188,7 +185,7 @@ const makeApiClientFactory = Effect
188
185
  const filtered = getFiltered(resource)
189
186
  return {
190
187
  mr,
191
- client: (typedKeysOf(filtered)
188
+ client: typedKeysOf(filtered)
192
189
  .reduce((prev, cur) => {
193
190
  const h = filtered[cur]!
194
191
 
@@ -211,7 +208,7 @@ const makeApiClientFactory = Effect
211
208
  const layers = requestLevelLayers.pipe(Layer.provideMerge(requestNameLayer))
212
209
 
213
210
  const fields = Struct.omit(Request.fields, ["_tag"] as const)
214
- const requestAttr = h._tag
211
+ const requestAttr = `${meta.moduleName}.${h._tag}`
215
212
  // @ts-expect-error doc
216
213
  prev[cur] = Object.keys(fields).length === 0
217
214
  ? {
@@ -246,7 +243,7 @@ const makeApiClientFactory = Effect
246
243
  }
247
244
 
248
245
  return prev
249
- }, {} as Client<M, M["meta"]["moduleName"]>))
246
+ }, {} as Client<M, M["meta"]["moduleName"]>)
250
247
  }
251
248
  })
252
249
 
@@ -43,7 +43,10 @@ export class InvalidStateError extends TaggedError<InvalidStateError>()("Invalid
43
43
  message: S.String
44
44
  }) {
45
45
  constructor(messageOrObject: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
46
- super(typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any, disableValidation as any)
46
+ super(
47
+ typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any,
48
+ disableValidation as any
49
+ )
47
50
  }
48
51
  }
49
52
 
@@ -51,7 +54,10 @@ export class ServiceUnavailableError extends TaggedError<ServiceUnavailableError
51
54
  message: S.String
52
55
  }) {
53
56
  constructor(messageOrObject: string | { message: string; cause?: unknown }, disableValidation?: boolean) {
54
- super(typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any, disableValidation as any)
57
+ super(
58
+ typeof messageOrObject === "object" ? messageOrObject : { message: messageOrObject } as any,
59
+ disableValidation as any
60
+ )
55
61
  }
56
62
  }
57
63
 
@@ -116,7 +122,10 @@ export class OptimisticConcurrencyException extends TaggedError<OptimisticConcur
116
122
  | ({ message: string; cause?: unknown; raw?: unknown }),
117
123
  disableValidation?: boolean
118
124
  ) {
119
- super("message" in args ? args : { message: `Existing ${args.type} ${args.id} record changed` } as any, disableValidation as any)
125
+ super(
126
+ "message" in args ? args : { message: `Existing ${args.type} ${args.id} record changed` } as any,
127
+ disableValidation as any
128
+ )
120
129
  if (!("message" in args)) {
121
130
  this.details = args
122
131
  }
@@ -1,4 +1,4 @@
1
- import { type GetContextConfig, type RequestContextMapTagAny } from "../rpc/RpcContextMap.js"
1
+ import { type GetContextConfig, type GetEffectError, type RequestContextMapTagAny } from "../rpc/RpcContextMap.js"
2
2
  import * as S from "../Schema.js"
3
3
  import { AST } from "../Schema.js"
4
4
 
@@ -44,8 +44,8 @@ export const makeRpcClient = <
44
44
 
45
45
  type MergeError<E> = [GeneralErrors] extends [never] ? SchemaOrFields<E> : S.Union<[SchemaOrFields<E>, GeneralErrors]>
46
46
  type ErrorResult<C> = C extends { error: infer E } ? MergeError<E>
47
- : [GeneralErrors] extends [never] ? S.Void
48
- : GeneralErrors
47
+ : [GeneralErrors] extends [never] ? GetEffectError<RequestContextMap["config"], C>
48
+ : MergeError<GetEffectError<RequestContextMap["config"], C>>
49
49
 
50
50
  function TaggedRequest<_Self>(): {
51
51
  <Tag extends string, Payload extends S.Struct.Fields, C extends ServiceMap>(
@@ -71,7 +71,7 @@ export const makeRpcClient = <
71
71
  <Tag extends string, Payload extends S.Struct.Fields>(
72
72
  tag: Tag,
73
73
  fields: Payload
74
- ): TaggedRequestResult<Tag, Payload, S.Codec<void>, ErrorResult<never>, Record<string, never>>
74
+ ): TaggedRequestResult<Tag, Payload, S.Codec<void>, ErrorResult<{}>, Record<string, never>>
75
75
  } {
76
76
  // TODO: filter errors based on config + take care of inversion
77
77
  const errorSchemas = Object.values(rcs.config).map((_) => _.error)
@@ -92,22 +92,9 @@ export const makeRpcClient = <
92
92
  : S.Struct(config.success)
93
93
  : ForceVoid
94
94
 
95
- const payloadSchema = S.Struct({ _tag: S.tag(tag), ...fields })
96
-
97
- const taggedFields = { _tag: S.tag(tag), ...fields }
98
-
99
- const RequestClass = class {
100
- constructor(payload?: any) {
101
- if (payload) {
102
- Object.assign(this, payload)
103
- }
104
- ;(this as any)._tag = tag
105
- }
106
- }
107
-
108
- Object.assign(RequestClass, payloadSchema, {
95
+ const RequestClass = S.TaggedClass<any>()(tag, fields)
96
+ Object.assign(RequestClass, {
109
97
  _tag: tag,
110
- fields: taggedFields,
111
98
  success: successSchema,
112
99
  error: failureSchema,
113
100
  config
package/src/ids.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { brandedStringId, NonEmptyString255, StringId, type StringIdBrand, withDefaultMake, Codec } from "effect-app/Schema"
1
+ import { brandedStringId, type Codec, NonEmptyString255, StringId, type StringIdBrand, withDefaultMake } from "effect-app/Schema"
2
2
  import type { B } from "effect-app/Schema/schema"
3
3
  import type { Simplify } from "effect/Types"
4
4
  import { S } from "./index.js"
package/src/index.ts CHANGED
@@ -23,7 +23,6 @@ export { type NonEmptyArray, type NonEmptyReadonlyArray } from "./Array.js"
23
23
 
24
24
  export * from "effect"
25
25
 
26
- export * as Struct from "./Struct.js"
27
26
  export type * as Types from "./Types.js"
28
27
 
29
28
  export * as SecretURL from "./Config/SecretURL.js"
@@ -14,7 +14,8 @@ import { type AddMiddleware, type AnyDynamic, type RpcDynamic, type RpcMiddlewar
14
14
  import * as RpcMiddlewareX from "./RpcMiddleware.js"
15
15
 
16
16
  // adapter for effect/rpc v3 middleware provides. (in effect-smol (v4), it's just a Service Identifier, no tags.)
17
- type MakeTags<A> = ServiceMap.Service<A, A>
17
+ // hm?
18
+ type MakeTags<A> = A
18
19
 
19
20
  export interface MiddlewareMaker<
20
21
  Self,
@@ -226,8 +226,8 @@ export type ExtractProvides<R extends Rpc.Any, Tag extends string> = R extends
226
226
  Rpc.Rpc<Tag, infer _Payload, infer _Success, infer _Error, infer _Middleware, infer _Requires> ? _Middleware extends {
227
227
  readonly provides: infer _P
228
228
  } ? [_P] extends [never] ? never
229
- : _P extends ServiceMap.Service<infer _I, infer _S> ? _I
230
- : never
229
+ : _P /*_P extends ServiceMap.Service<infer _I, infer _S> ? _I
230
+ : never */
231
231
  : never
232
232
  : never
233
233