effect-app 4.0.0-beta.9 → 4.0.0-beta.90

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 (142) hide show
  1. package/CHANGELOG.md +364 -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/Context.d.ts +40 -0
  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 +62 -25
  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 +39 -1
  24. package/dist/Schema/Class.d.ts.map +1 -1
  25. package/dist/Schema/Class.js +89 -12
  26. package/dist/Schema/SpecialJsonSchema.d.ts +21 -0
  27. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  28. package/dist/Schema/SpecialJsonSchema.js +59 -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 +8 -5
  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 +103 -46
  38. package/dist/Schema/ext.d.ts.map +1 -1
  39. package/dist/Schema/ext.js +110 -51
  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 +22 -55
  51. package/dist/Schema.d.ts.map +1 -1
  52. package/dist/Schema.js +43 -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 +17 -18
  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 +21 -16
  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 +3 -3
  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 +18 -0
  90. package/dist/utils.d.ts.map +1 -1
  91. package/dist/utils.js +24 -5
  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} +57 -64
  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 +114 -16
  101. package/src/Schema/SpecialJsonSchema.ts +69 -0
  102. package/src/Schema/SpecialOpenApi.ts +130 -0
  103. package/src/Schema/brand.ts +13 -7
  104. package/src/Schema/email.ts +10 -2
  105. package/src/Schema/ext.ts +185 -82
  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 +79 -103
  110. package/src/client/apiClientFactory.ts +30 -34
  111. package/src/client/clientFor.ts +6 -1
  112. package/src/client/errors.ts +46 -12
  113. package/src/client/makeClient.ts +122 -62
  114. package/src/http/Request.ts +7 -4
  115. package/src/ids.ts +3 -2
  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 +26 -4
  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 +397 -4
  131. package/test/secretURL.test.ts +157 -0
  132. package/test/special.test.ts +732 -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 +0 -44
  137. package/dist/ServiceMap.d.ts.map +0 -1
  138. package/dist/ServiceMap.js +0 -91
  139. package/dist/Struct.d.ts +0 -44
  140. package/dist/Struct.d.ts.map +0 -1
  141. package/dist/Struct.js +0 -29
  142. package/src/Struct.ts +0 -54
@@ -1,20 +1,16 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- /**
3
- * We're doing the long way around here with assignTag, TagBase & TagBaseTagged,
4
- * because there's a typescript compiler issue where it will complain about Equal.symbol, and Hash.symbol not being accessible.
5
- * https://github.com/microsoft/TypeScript/issues/52644
6
- */
7
2
 
8
3
  import { type Effect, Layer, type Scope, type Types } from "effect"
9
- import * as ServiceMap from "effect/ServiceMap"
10
- import { Yieldable } from "./Effect.js"
4
+ import * as CTX from "effect/Context"
5
+ import { type Yieldable } from "./Effect.js"
11
6
 
12
- export * from "effect/ServiceMap"
7
+ export * from "effect/Context"
13
8
 
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
17
- serviceMap(self: Shape): ServiceMap.ServiceMap<Self>
9
+ export interface Opaque<Self extends object, in out Shape extends object>
10
+ extends CTX.Key<Self, Self>, Yieldable<Opaque<Self, Shape>, Self, never, Self>
11
+ {
12
+ of(this: void, self: Shape): Self
13
+ serviceMap(self: Shape): CTX.Context<Self>
18
14
  // a version that leverages the Shape -> Self conversion
19
15
  toLayer: <E, R>(
20
16
  eff: Effect.Effect<Shape, E, R>
@@ -24,11 +20,11 @@ export interface Opaque<Self extends object, in out Shape extends object> extend
24
20
  }
25
21
 
26
22
  // export interface OpaqueMake<Self extends object, in out Shape extends object, E, R>
27
- // extends ServiceMap.Service<Self, Self>
23
+ // extends CTX.Service<Self, Self>
28
24
  // {
29
25
  // // temp while sorting out https://github.com/Effect-TS/effect-smol/pull/1534
30
26
  // of(self: Shape): Self
31
- // serviceMap2(self: Shape): ServiceMap.ServiceMap<Self>
27
+ // serviceMap2(self: Shape): CTX.Context<Self>
32
28
  // // a version that leverages the Shape -> Self conversion
33
29
  // toLayer: {
34
30
  // <E, R>(
@@ -43,7 +39,7 @@ export function assignTag<Identifier extends object, Shape extends object = Iden
43
39
  creationError?: Error
44
40
  ) {
45
41
  return <S extends object>(cls: S): S & Opaque<Identifier, Shape> => {
46
- const tag = ServiceMap.Service<Identifier, Shape>(key)
42
+ const tag = CTX.Service<Identifier, Shape>(key)
47
43
  let fields = tag
48
44
  if (Reflect.ownKeys(cls).includes("key")) {
49
45
  const { key, ...rest } = tag
@@ -66,54 +62,51 @@ export function assignTag<Identifier extends object, Shape extends object = Iden
66
62
  }
67
63
  }
68
64
 
69
- export type ServiceAcessorShape<Self, Type> = Type extends Record<PropertyKey, any> ? {
70
- [
71
- k in keyof Type as Type[k] extends ((...args: [...infer Args]) => infer Ret)
72
- ? ((...args: Readonly<Args>) => Ret) extends Type[k] ? k : never
73
- : k
74
- ]: Type[k] extends (...args: [...infer Args]) => Effect.Effect<infer A, infer E, infer R>
75
- ? (...args: Readonly<Args>) => Effect.Effect<A, E, Self | R>
76
- : Type[k] extends (...args: [...infer Args]) => infer A
77
- ? (...args: Readonly<Args>) => Effect.Effect<A, never, Self>
78
- : Type[k] extends Effect.Effect<infer A, infer E, infer R> ? Effect.Effect<A, E, Self | R>
79
- : Effect.Effect<Type[k], never, Self>
80
- }
81
- : {}
65
+ /** Accessor for a service method that returns a plain value. Wraps via `useSync`. */
66
+ export const accessFn = <
67
+ Self extends object,
68
+ Shape extends Record<PropertyKey, any>,
69
+ K extends keyof Shape
70
+ >(
71
+ Tag: Opaque<Self, Shape>,
72
+ key: K
73
+ ): Shape[K] extends (...args: [...infer Args]) => infer A ? (...args: Readonly<Args>) => Effect.Effect<A, never, Self>
74
+ : never => ((...args: Array<any>) => Tag.useSync((s: any) => s[key](...args))) as any
82
75
 
83
- /**
84
- * Only use this in very specific cases where using dependencies directly is prefered, like inside command handlers.
85
- */
86
- export const proxify = <T extends object>(Tag: T) =>
87
- <Self, Shape>():
88
- & T
89
- & ServiceAcessorShape<Self, Shape> =>
90
- {
91
- const cache = new Map()
92
- const done = new Proxy(Tag, {
93
- get(_target: any, prop: any, _receiver) {
94
- if (prop === "use") {
95
- // @ts-expect-error abc
96
- return (body) => (Tag as any).use(body)
97
- }
98
- if (prop in Tag) {
99
- return (Tag as any)[prop]
100
- }
101
- if (cache.has(prop)) {
102
- return cache.get(prop)
103
- }
104
- const fn = (...args: Array<any>) => (Tag as any).use((s: any) => s[prop](...args))
105
- const cn = (Tag as any).use((s: any) => s[prop])
106
- // @effect-diagnostics effect/floatingEffect:off
107
- Object.assign(fn, cn)
108
- Object.setPrototypeOf(fn, Object.getPrototypeOf(cn))
109
- cache.set(prop, fn)
110
- return fn
111
- }
112
- })
113
- return done
114
- }
76
+ /** Accessor for a service method that returns an Effect. Delegates via `use`. */
77
+ export const accessEffectFn = <
78
+ Self extends object,
79
+ Shape extends Record<PropertyKey, any>,
80
+ K extends keyof Shape
81
+ >(
82
+ Tag: Opaque<Self, Shape>,
83
+ key: K
84
+ ): Shape[K] extends (...args: [...infer Args]) => Effect.Effect<infer A, infer E, infer R>
85
+ ? (...args: Readonly<Args>) => Effect.Effect<A, E, Self | R>
86
+ : never => ((...args: Array<any>) => Tag.use((s: any) => s[key](...args))) as any
115
87
 
116
- export const TypeId = "~ServiceMap.Opaque"
88
+ /** Accessor for a service property (constant). Wraps via `useSync`. */
89
+ export const accessCn = <
90
+ Self extends object,
91
+ Shape extends Record<PropertyKey, any>,
92
+ K extends keyof Shape
93
+ >(
94
+ Tag: Opaque<Self, Shape>,
95
+ key: K
96
+ ): Effect.Effect<Shape[K], never, Self> => Tag.useSync((s) => s[key]) as any
97
+
98
+ /** Accessor for a service property that is an Effect. Delegates via `use`. */
99
+ export const accessEffectCn = <
100
+ Self extends object,
101
+ Shape extends Record<PropertyKey, any>,
102
+ K extends keyof Shape
103
+ >(
104
+ Tag: Opaque<Self, Shape>,
105
+ key: K
106
+ ): Shape[K] extends Effect.Effect<infer A, infer E, infer R> ? Effect.Effect<A, E, Self | R>
107
+ : never => Tag.use((s: any) => s[key]) as any
108
+
109
+ export const TypeId = "~Context.Opaque"
117
110
 
118
111
  // export function Opaque<const Key extends string>(key: Key) {
119
112
  // return <Identifier extends object, Shape extends object>() => {
@@ -151,7 +144,7 @@ export const Opaque: {
151
144
  id: Identifier,
152
145
  options?: {
153
146
  readonly make: ((...args: Args) => Effect.Effect<Shape, E, R>) | Effect.Effect<Shape, E, R> | undefined
154
- } | undefined
147
+ }
155
148
  ) =>
156
149
  & OpaqueClass<Self, Identifier, Shape>
157
150
  & ([Types.unassigned] extends [R] ? unknown
@@ -178,10 +171,10 @@ export const Opaque: {
178
171
  >
179
172
  & { readonly make: Make }
180
173
  } = () => (id: string, options: any) => {
181
- const svc = ServiceMap.Service()(id, options) as any
174
+ const svc = CTX.Service()(id, options) as any
182
175
  return Object.assign(svc, {
183
176
  toLayer: (eff: Effect.Effect<any, any, any>) => {
184
- return Layer.effect(svc as any, eff)
177
+ return Layer.effect(svc, eff)
185
178
  }
186
179
  })
187
180
  }
package/src/Effect.ts CHANGED
@@ -2,11 +2,12 @@
2
2
  /* eslint-disable prefer-destructuring */
3
3
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
4
 
5
- import { Effect, Option, Ref, type ServiceMap } from "effect"
5
+ import { Effect, Option, Ref } from "effect"
6
6
  import * as Def from "effect/Deferred"
7
7
  import * as Fiber from "effect/Fiber"
8
8
  import type { Scope } from "effect/Scope"
9
9
  import type { Semaphore } from "effect/Semaphore"
10
+ import type * as Context from "./Context.js"
10
11
  import { curry } from "./Function.js"
11
12
  import { typedKeysOf } from "./utils.js"
12
13
 
@@ -116,10 +117,10 @@ export function joinAll<E, A>(fibers: Iterable<Fiber.Fiber<A, E>>): Effect.Effec
116
117
  }
117
118
 
118
119
  type ServiceA<T> = T extends Effect.Effect<infer S, any, any> ? S
119
- : T extends ServiceMap.Service<any, infer S> ? S
120
+ : T extends Context.Service<any, infer S> ? S
120
121
  : never
121
122
  type ServiceR<T> = T extends Effect.Effect<any, any, infer R> ? R
122
- : T extends ServiceMap.Service<infer I, any> ? I
123
+ : T extends Context.Service<infer I, any> ? I
123
124
  : never
124
125
  type ServiceE<T> = T extends Effect.Effect<any, infer E, any> ? E : never
125
126
  // type Values<T> = T extends { [s: string]: infer S } ? ServiceA<S> : never
@@ -144,24 +145,25 @@ export interface EffectUnunified<R, E, A> extends Effect.Effect<R, E, A> {}
144
145
 
145
146
  export type LowerFirst<S extends PropertyKey> = S extends `${infer First}${infer Rest}` ? `${Lowercase<First>}${Rest}`
146
147
  : S
147
- export type LowerServices<T extends Record<string, ServiceMap.Service<any, any> | Effect.Effect<any, any, any>>> = {
148
+ export type LowerServices<T extends Record<string, Context.Service<any, any> | Effect.Effect<any, any, any>>> = {
148
149
  [key in keyof T as LowerFirst<key>]: ServiceA<T[key]>
149
150
  }
150
151
 
151
- export function allLower<T extends Record<string, ServiceMap.Service<any, any> | Effect.Effect<any, any, any>>>(
152
+ export function allLower<T extends Record<string, Context.Service<any, any> | Effect.Effect<any, any, any>>>(
152
153
  services: T
153
154
  ) {
154
155
  return Effect.all(
155
156
  typedKeysOf(services).reduce((prev, cur) => {
156
- const svc = services[cur]
157
- prev[((cur as string)[0]!.toLowerCase() + (cur as string).slice(1)) as unknown as LowerFirst<typeof cur>] = svc // "_id" in svc && svc._id === TagTypeId ? svc : svc
157
+ const svc = services[cur]!
158
+ prev[((cur as string)[0]!.toLowerCase() + (cur as string).slice(1)) as unknown as LowerFirst<typeof cur>] =
159
+ "asEffect" in svc ? svc.asEffect() : svc
158
160
  return prev
159
161
  }, {} as any),
160
162
  { concurrency: "inherit" }
161
163
  ) as any as Effect.Effect<LowerServices<T>, ValuesE<T>, ValuesR<T>>
162
164
  }
163
165
 
164
- export function allLowerWith<T extends Record<string, ServiceMap.Service<any, any> | Effect.Effect<any, any, any>>, A>(
166
+ export function allLowerWith<T extends Record<string, Context.Service<any, any> | Effect.Effect<any, any, any>>, A>(
165
167
  services: T,
166
168
  fn: (services: LowerServices<T>) => A
167
169
  ) {
@@ -169,7 +171,7 @@ export function allLowerWith<T extends Record<string, ServiceMap.Service<any, an
169
171
  }
170
172
 
171
173
  export function allLowerWithEffect<
172
- T extends Record<string, ServiceMap.Service<any, any> | Effect.Effect<any, any, any>>,
174
+ T extends Record<string, Context.Service<any, any> | Effect.Effect<any, any, any>>,
173
175
  R,
174
176
  E,
175
177
  A
package/src/Layer.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { type Array, Effect, Layer, type Scope, type ServiceMap, type Types } from "effect"
1
+ import { type Array, Effect, Layer, type Scope, type Types } from "effect"
2
2
  import { type Yieldable } from "effect/Effect"
3
3
  import { dual } from "effect/Function"
4
+ import type * as Context from "./Context.js"
4
5
  import { type EffectGenUtils } from "./utils/gen.js"
5
6
 
6
7
  export * from "effect/Layer"
@@ -17,7 +18,7 @@ type MakeGenNo<S> = {
17
18
  readonly make: () => Generator<unknown, S>
18
19
  }
19
20
  type MakeErr<Opts> = Opts extends { make: () => any } ? EffectGenUtils.Error<Opts["make"]> : never
20
- type MakeContext<Opts> = Opts extends { make: () => any } ? EffectGenUtils.ServiceMap<Opts["make"]> : never
21
+ type MakeContext<Opts> = Opts extends { make: () => any } ? EffectGenUtils.Context<Opts["make"]> : never
21
22
 
22
23
  type DependenciesOpt = { dependencies?: Array.NonEmptyReadonlyArray<Layer.Any> }
23
24
  type Dependencies = { dependencies: Array.NonEmptyReadonlyArray<Layer.Any> }
@@ -41,12 +42,12 @@ type PackedOrUnpackedLayer<I, Opts> = Opts extends Dependencies ? PackedLayers<I
41
42
 
42
43
  export const make: {
43
44
  <I, S>(
44
- tag: ServiceMap.Service<I, S>
45
+ tag: Context.Service<I, S>
45
46
  ): <Opts extends Make<Types.NoInfer<S>, any, any>>(
46
47
  options: Opts
47
48
  ) => PackedOrUnpackedLayer<I, Opts>
48
49
  <I, S, Opts extends Make<Types.NoInfer<S>, any, any>>(
49
- tag: ServiceMap.Service<I, S>,
50
+ tag: Context.Service<I, S>,
50
51
  options: Opts
51
52
  ): PackedOrUnpackedLayer<I, Opts>
52
53
  } = dual(2, (tag, options) => {
package/src/Pure.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { Chunk, Effect, Layer, Result } from "effect"
3
+ import * as Context from "./Context.js"
3
4
  import { tuple } from "./Function.js"
4
- import * as ServiceMap from "./ServiceMap.js"
5
5
 
6
6
  const S1 = Symbol()
7
7
  const S2 = Symbol()
@@ -86,9 +86,9 @@ export function GMU<W, S, S2, GA, MR, ME>(modify: (i: GA) => Pure<W, S, S2, MR,
86
86
  ) => GMU_(get, modify, update)
87
87
  }
88
88
 
89
- const tagg = ServiceMap.Service<{ env: PureEnv<never, unknown, never> }>("PureEnv")
89
+ const tagg = Context.Service<{ env: PureEnv<never, unknown, never> }>("PureEnv")
90
90
  function castTag<W, S, S2>() {
91
- return tagg as any as ServiceMap.Service<PureEnvEnv<W, S, S2>, PureEnvEnv<W, S, S2>>
91
+ return tagg as any as Context.Service<PureEnvEnv<W, S, S2>, PureEnvEnv<W, S, S2>>
92
92
  }
93
93
 
94
94
  export const ServiceTag = Symbol()
@@ -100,9 +100,9 @@ export abstract class PhantomTypeParameter<Identifier extends keyof any, Instant
100
100
  }
101
101
  }
102
102
 
103
- export type ServiceShape<T extends ServiceMap.ServiceClass.Shape<any, any>> = Omit<
103
+ export type ServiceShape<T extends Context.ServiceClass.Shape<any, any>> = Omit<
104
104
  T,
105
- keyof ServiceMap.ServiceClass.Shape<any, any>
105
+ keyof Context.ServiceClass.Shape<any, any>
106
106
  >
107
107
 
108
108
  export abstract class ServiceTagged<ServiceKey> extends PhantomTypeParameter<string, ServiceKey> {}
@@ -117,11 +117,11 @@ export interface PureEnvEnv<W, S, S2> extends ServiceTagged<typeof PureEnvEnv> {
117
117
  }
118
118
 
119
119
  export function get<S>(): Pure<never, S, S, never, never, S> {
120
- return (castTag<never, S, S>() as any).use((_: any) => _.env.state)
120
+ return (castTag<never, S, S>()).useSync((_) => _.env.state)
121
121
  }
122
122
 
123
123
  export function set<S>(s: S): Pure<never, S, S, never, never, void> {
124
- return (castTag<never, S, S>() as any).use((_: any) => {
124
+ return (castTag<never, S, S>()).useSync((_) => {
125
125
  _.env.state = s
126
126
  })
127
127
  }
@@ -129,13 +129,13 @@ export function set<S>(s: S): Pure<never, S, S, never, never, void> {
129
129
  export type PureLogT<W> = Pure<W, unknown, never, never, never, void>
130
130
 
131
131
  export function log<W>(w: W): PureLogT<W> {
132
- return (castTag<W, unknown, never>() as any).use((_: any) => {
132
+ return (castTag<W, unknown, never>()).useSync((_) => {
133
133
  _.env.log = Chunk.append(_.env.log, w)
134
134
  })
135
135
  }
136
136
 
137
137
  export function logMany<W>(w: Iterable<W>): PureLogT<W> {
138
- return (castTag<W, unknown, never>() as any).use((_: any) => {
138
+ return (castTag<W, unknown, never>()).useSync((_) => {
139
139
  _.env.log = Chunk.appendAll(_.env.log, Chunk.fromIterable(w))
140
140
  })
141
141
  }
@@ -150,17 +150,16 @@ export function runAll<R, E, A, W3, S1, S3, S4 extends S1>(
150
150
  > {
151
151
  const a = Effect
152
152
  .flatMap(self, (x) =>
153
- (castTag<W3, S1, S3>() as any)
154
- .use(
155
- ({ env: _ }: any) => Effect.sync(() => ({ log: _.log, state: _.state }))
153
+ (castTag<W3, S1, S3>())
154
+ .useSync(
155
+ ({ env: _ }) => ({ log: _.log, state: _.state })
156
156
  )
157
157
  .pipe(
158
- Effect.flatMap((_: any) => Effect.succeed(_)),
159
158
  Effect.map(
160
- ({ log, state }: any) => tuple(log, Result.succeed(tuple(state, x)))
159
+ ({ log, state }) => tuple(log, Result.succeed(tuple(state, x)))
161
160
  )
162
161
  ))
163
- .pipe(Effect.catch((err: any) => (tagg as any).use((env: any) => tuple(env.env.log, Result.fail(err)))))
162
+ .pipe(Effect.catch((err: any) => tagg.useSync((env) => tuple(env.env.log, Result.fail(err)))))
164
163
  return Effect.provide(a, Layer.succeed(tagg, { env: makePureEnv<W3, S3, S4>(s) as any }) as any) as any
165
164
  }
166
165
 
@@ -198,7 +197,7 @@ export function runA<R, E, A, W3, S1, S3, S4 extends S1>(
198
197
  export function modify<S2, A, S3>(
199
198
  mod: (s: S2) => readonly [S3, A]
200
199
  ): Effect.Effect<A, never, { env: PureEnv<never, S2, S3> }> {
201
- return (castTag<never, S3, S2>() as any).use((_: any) => {
200
+ return (castTag<never, S3, S2>()).useSync((_) => {
202
201
  const [s, a] = mod(_.env.state)
203
202
  _.env.state = s as any
204
203
  return a
@@ -210,10 +209,10 @@ export function modifyM<W, R, E, A, S2, S3>(
210
209
  ): Effect.Effect<A, E, FixEnv<R, W, S2, S3>> {
211
210
  // return serviceWithEffect(_ => Ref.modifyM_(_.state, mod))
212
211
  return Effect.flatMap(
213
- (castTag<W, S3, S2>() as any).use((_: any) => _),
212
+ (castTag<W, S3, S2>()).useSync((_) => _),
214
213
  (_: any) =>
215
214
  Effect.map(mod(_.env.state), ([s, a]: any) => {
216
- _.env.state = s as any
215
+ _.env.state = s
217
216
  return a
218
217
  })
219
218
  ) as any
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { pipe, Struct as Struct2 } from "effect"
2
+ import { Effect, Option, pipe, Schema, SchemaAST, SchemaIssue, Struct as Struct2 } from "effect"
3
3
  import type { Struct } from "effect/Schema"
4
4
  import * as S from "effect/Schema"
5
5
 
@@ -13,9 +13,6 @@ type MissingSelfGeneric<Usage extends string, Params extends string = ""> =
13
13
  `Missing \`Self\` generic - use \`class Self extends ${Usage}<Self>()(${Params}{ ... })\``
14
14
 
15
15
  export interface PropsExtensions<Fields> {
16
- // include: <NewProps extends S.Struct.Fields>(
17
- // fnc: (fields: Fields) => NewProps
18
- // ) => NewProps
19
16
  pick: <P extends keyof Fields>(...keys: readonly P[]) => Pick<Fields, P>
20
17
  omit: <P extends keyof Fields>(...keys: readonly P[]) => Omit<Fields, P>
21
18
  }
@@ -26,6 +23,57 @@ type HasFields<Fields extends Struct.Fields> = {
26
23
  readonly from: HasFields<Fields>
27
24
  }
28
25
 
26
+ export type Class<Self, S extends S.Top & { readonly fields: Struct.Fields }, Inherited> = S.Class<Self, S, Inherited>
27
+
28
+ /**
29
+ * Build a modified Declaration that accepts struct-matching values during
30
+ * encoding, given the original Declaration and the class's fields.
31
+ */
32
+ function makeRelaxedDeclaration(
33
+ ast: SchemaAST.Declaration,
34
+ fields: Schema.Struct.Fields,
35
+ cls: any
36
+ ): SchemaAST.Declaration {
37
+ const structSchema = Schema.Struct(fields)
38
+ const isStructValue = Schema.is(structSchema)
39
+ return new SchemaAST.Declaration(
40
+ ast.typeParameters,
41
+ () => (input: unknown, self: SchemaAST.Declaration) => {
42
+ if (input instanceof cls || isStructValue(input)) {
43
+ return Effect.succeed(input)
44
+ }
45
+ return Effect.fail(new SchemaIssue.InvalidType(self, Option.some(input)))
46
+ },
47
+ ast.annotations,
48
+ ast.checks,
49
+ ast.encoding,
50
+ ast.context
51
+ )
52
+ }
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Class — like Schema.Class but with relaxed encoding
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /**
59
+ * Like `Schema.Class`, but the resulting class accepts plain objects matching
60
+ * the struct schema during encoding — not only `instanceof` or type-id
61
+ * checks.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * import { Schema } from "effect"
66
+ * import { Class } from "./Class.js"
67
+ *
68
+ * class A extends Class<A>("A")({ a: Schema.String }) {}
69
+ *
70
+ * // Construction works as normal:
71
+ * new A({ a: "hello" })
72
+ *
73
+ * // Encoding accepts plain objects:
74
+ * Schema.encodeUnknownSync(A)({ a: "hello" }) // { a: "hello" }
75
+ * ```
76
+ */
29
77
  export const Class: <Self = never>(identifier: string) => <Fields extends S.Struct.Fields>(
30
78
  fieldsOr: Fields | HasFields<Fields>,
31
79
  annotations?: ClassAnnotations<Self>
@@ -35,38 +83,84 @@ export const Class: <Self = never>(identifier: string) => <Fields extends S.Stru
35
83
  S.Struct<Fields>,
36
84
  {}
37
85
  > = (identifier) => (fields, annotations) => {
38
- const cls = S.Class as any
39
- return class extends cls(identifier)(fields, annotations) {
40
- constructor(a: any, b = true) {
41
- super(a, b)
86
+ // Build the original Schema.Class
87
+ const Base = (S.Class as any)(identifier)(fields, annotations)
88
+ // Get the original ast getter from the base class
89
+ const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")!
90
+
91
+ // Cache per-class to avoid recomputing
92
+ const astCache = new WeakMap<any, SchemaAST.Declaration>()
93
+
94
+ return class extends Base {
95
+ static get ast(): SchemaAST.Declaration {
96
+ let cached = astCache.get(this)
97
+ if (cached !== undefined) return cached
98
+ // Call the original getter with `this` bound to the actual user class,
99
+ // so getClassSchema(this) creates a schema that uses `new this(...)`.
100
+ const originalAst = originalAstDescriptor.get!.call(this) as SchemaAST.Declaration
101
+ cached = makeRelaxedDeclaration(originalAst, Base.fields, this)
102
+ astCache.set(this, cached)
103
+ return cached
42
104
  }
43
- // static readonly include = include(fields)
44
105
  static readonly pick = (...selection: any[]) => pipe(this["fields"], Struct2.pick(selection))
45
106
  static readonly omit = (...selection: any[]) => pipe(this["fields"], Struct2.omit(selection))
46
107
  } as any
47
108
  }
48
109
 
49
- export const TaggedClass: <Self = never>(identifier?: string) => <Tag extends string, Fields extends S.Struct.Fields>(
110
+ // ---------------------------------------------------------------------------
111
+ // TaggedClass — like Schema.TaggedClass but with relaxed encoding
112
+ // ---------------------------------------------------------------------------
113
+
114
+ /**
115
+ * Like `Schema.TaggedClass`, but the resulting class accepts plain objects
116
+ * matching the struct schema during encoding.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * import { Schema } from "effect"
121
+ * import { TaggedClass } from "./Class.js"
122
+ *
123
+ * class Circle extends TaggedClass<Circle>()("Circle", {
124
+ * radius: Schema.Number
125
+ * }) {}
126
+ *
127
+ * Schema.encodeUnknownSync(Circle)({ _tag: "Circle", radius: 5 })
128
+ * ```
129
+ */
130
+ export const TaggedClass: <Self = never>(
131
+ identifier?: string
132
+ ) => <Tag extends string, Fields extends S.Struct.Fields>(
50
133
  tag: Tag,
51
134
  fieldsOr: Fields | HasFields<Fields>,
52
135
  annotations?: ClassAnnotations<Self>
53
- ) => [Self] extends [never] ? MissingSelfGeneric<"Class">
136
+ ) => [Self] extends [never] ? MissingSelfGeneric<"TaggedClass">
54
137
  : EnhancedClass<
55
138
  Self,
56
139
  S.Struct<{ readonly _tag: S.tag<Tag> } & Fields>,
57
140
  {}
58
141
  > = (identifier) => (tag, fields, annotations) => {
59
- const cls = S.TaggedClass as any
60
- return class extends cls(identifier)(tag, fields, annotations) {
61
- constructor(a: any, b = true) {
62
- super(a, b)
142
+ const Base = (S.TaggedClass as any)(identifier)(tag, fields, annotations)
143
+ const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")!
144
+ const astCache = new WeakMap<any, SchemaAST.Declaration>()
145
+
146
+ return class extends Base {
147
+ static get ast(): SchemaAST.Declaration {
148
+ let cached = astCache.get(this)
149
+ if (cached !== undefined) return cached
150
+ const originalAst = originalAstDescriptor.get!.call(this) as SchemaAST.Declaration
151
+ cached = makeRelaxedDeclaration(originalAst, Base.fields, this)
152
+ astCache.set(this, cached)
153
+ return cached
63
154
  }
64
- // static readonly include = include(fields)
65
155
  static readonly pick = (...selection: any[]) => pipe(this["fields"], Struct2.pick(selection))
66
156
  static readonly omit = (...selection: any[]) => pipe(this["fields"], Struct2.omit(selection))
67
157
  } as any
68
158
  }
69
159
 
160
+ // ---------------------------------------------------------------------------
161
+ // ExtendedClass — like Class but with extra type parameter for hierarchies
162
+ // ---------------------------------------------------------------------------
163
+
70
164
  export const ExtendedClass: <Self, _SelfFrom>(identifier: string) => <Fields extends S.Struct.Fields>(
71
165
  fieldsOr: Fields | HasFields<Fields>,
72
166
  annotations?: ClassAnnotations<Self>
@@ -76,6 +170,10 @@ export const ExtendedClass: <Self, _SelfFrom>(identifier: string) => <Fields ext
76
170
  {}
77
171
  > = Class as any
78
172
 
173
+ // ---------------------------------------------------------------------------
174
+ // ExtendedTaggedClass — like TaggedClass but with extra type parameter for hierarchies
175
+ // ---------------------------------------------------------------------------
176
+
79
177
  export interface EnhancedTaggedClass<Self, Tag extends string, Fields extends Struct.Fields, SelfFrom>
80
178
  extends
81
179
  EnhancedClass<
@@ -0,0 +1,69 @@
1
+ /**
2
+ * SpecialJsonSchema — A variant of Schema.toJsonSchemaDocument that
3
+ * post-processes the output (e.g. flattens simple allOf).
4
+ */
5
+ import { type JsonSchema, type Schema, SchemaRepresentation } from "effect"
6
+
7
+ /**
8
+ * Converts a schema to a JSON Schema Document (draft-2020-12), with
9
+ * post-processing that flattens simple allOf entries.
10
+ */
11
+ export function specialJsonSchemaDocument(
12
+ schema: Schema.Top,
13
+ options?: Schema.ToJsonSchemaOptions
14
+ ): JsonSchema.Document<"draft-2020-12"> {
15
+ const doc = SchemaRepresentation.fromAST(schema.ast)
16
+ const jd = SchemaRepresentation.toJsonSchemaDocument(doc, options)
17
+ const processedDefs: JsonSchema.Definitions = {}
18
+ for (const [key, def] of Object.entries(jd.definitions)) {
19
+ processedDefs[key] = postProcessJsonSchema(def)
20
+ }
21
+ return {
22
+ dialect: "draft-2020-12",
23
+ schema: postProcessJsonSchema(jd.schema),
24
+ definitions: processedDefs
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Flattens `allOf` entries into the parent when the parent already has a
30
+ * `type` and every `allOf` entry is a plain constraint object (no `$ref`,
31
+ * no `type`). Merged properties from `allOf` entries win on conflict.
32
+ */
33
+ export function flattenSimpleAllOf(obj: unknown): unknown {
34
+ if (obj === null || typeof obj !== "object") return obj
35
+
36
+ if (globalThis.Array.isArray(obj)) {
37
+ return obj.map(flattenSimpleAllOf)
38
+ }
39
+
40
+ const record = obj as Record<string, unknown>
41
+ const result: Record<string, unknown> = {}
42
+ for (const [key, value] of Object.entries(record)) {
43
+ result[key] = flattenSimpleAllOf(value)
44
+ }
45
+
46
+ if (result["type"] && globalThis.Array.isArray(result["allOf"])) {
47
+ const allOf = result["allOf"] as Array<Record<string, unknown>>
48
+ const canFlatten = allOf.every((entry) =>
49
+ typeof entry === "object" && entry !== null && !("$ref" in entry) && !("type" in entry)
50
+ )
51
+ if (canFlatten) {
52
+ const { allOf: _, ...rest } = result
53
+ let merged: Record<string, unknown> = { ...rest }
54
+ for (const entry of allOf) {
55
+ merged = { ...merged, ...entry }
56
+ }
57
+ return merged
58
+ }
59
+ }
60
+
61
+ return result
62
+ }
63
+
64
+ /**
65
+ * Applies JSON Schema post-processing: flattens simple allOf.
66
+ */
67
+ export function postProcessJsonSchema(obj: JsonSchema.JsonSchema): JsonSchema.JsonSchema {
68
+ return flattenSimpleAllOf(obj) as JsonSchema.JsonSchema
69
+ }