effect-app 4.0.0-beta.8 → 4.0.0-beta.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +354 -0
- package/dist/Config/SecretURL.js +2 -2
- package/dist/Config.d.ts +7 -0
- package/dist/Config.d.ts.map +1 -0
- package/dist/Config.js +6 -0
- package/dist/ConfigProvider.d.ts +39 -0
- package/dist/ConfigProvider.d.ts.map +1 -0
- package/dist/ConfigProvider.js +42 -0
- package/dist/Context.d.ts +40 -0
- package/dist/Context.d.ts.map +1 -0
- package/dist/Context.js +66 -0
- package/dist/Effect.d.ts +8 -7
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +3 -2
- package/dist/Layer.d.ts +5 -4
- package/dist/Layer.d.ts.map +1 -1
- package/dist/Layer.js +1 -1
- package/dist/Operations.d.ts +62 -25
- package/dist/Operations.d.ts.map +1 -1
- package/dist/Pure.d.ts +2 -2
- package/dist/Pure.d.ts.map +1 -1
- package/dist/Pure.js +13 -13
- package/dist/Schema/Class.d.ts +39 -1
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +89 -12
- package/dist/Schema/SpecialJsonSchema.d.ts +21 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +59 -0
- package/dist/Schema/SpecialOpenApi.d.ts +32 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +123 -0
- package/dist/Schema/brand.d.ts +8 -5
- package/dist/Schema/brand.d.ts.map +1 -1
- package/dist/Schema/brand.js +1 -1
- package/dist/Schema/email.d.ts.map +1 -1
- package/dist/Schema/email.js +9 -4
- package/dist/Schema/ext.d.ts +103 -46
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +110 -51
- package/dist/Schema/moreStrings.d.ts +19 -7
- package/dist/Schema/moreStrings.d.ts.map +1 -1
- package/dist/Schema/moreStrings.js +14 -9
- package/dist/Schema/numbers.d.ts +11 -11
- package/dist/Schema/numbers.d.ts.map +1 -1
- package/dist/Schema/numbers.js +10 -9
- package/dist/Schema/phoneNumber.d.ts.map +1 -1
- package/dist/Schema/phoneNumber.js +8 -3
- package/dist/Schema/strings.d.ts +4 -4
- package/dist/Schema/strings.d.ts.map +1 -1
- package/dist/Schema.d.ts +22 -55
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +43 -64
- package/dist/client/apiClientFactory.d.ts +11 -28
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +18 -19
- package/dist/client/clientFor.d.ts +6 -5
- package/dist/client/clientFor.d.ts.map +1 -1
- package/dist/client/errors.d.ts +18 -9
- package/dist/client/errors.d.ts.map +1 -1
- package/dist/client/errors.js +35 -10
- package/dist/client/makeClient.d.ts +21 -16
- package/dist/client/makeClient.d.ts.map +1 -1
- package/dist/client/makeClient.js +32 -23
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +5 -5
- package/dist/ids.d.ts +3 -3
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +3 -2
- package/dist/index.d.ts +3 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -9
- package/dist/middleware.d.ts +2 -2
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +3 -3
- package/dist/rpc/MiddlewareMaker.d.ts +4 -3
- package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
- package/dist/rpc/MiddlewareMaker.js +7 -6
- package/dist/rpc/RpcContextMap.d.ts +2 -2
- package/dist/rpc/RpcContextMap.d.ts.map +1 -1
- package/dist/rpc/RpcContextMap.js +4 -4
- package/dist/rpc/RpcMiddleware.d.ts +4 -3
- package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
- package/dist/rpc/RpcMiddleware.js +1 -1
- package/dist/utils/gen.d.ts +1 -1
- package/dist/utils/gen.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +2 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -3
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +24 -5
- package/package.json +29 -17
- package/src/Config/SecretURL.ts +1 -1
- package/src/Config.ts +14 -0
- package/src/ConfigProvider.ts +48 -0
- package/src/{ServiceMap.ts → Context.ts} +57 -64
- package/src/Effect.ts +11 -9
- package/src/Layer.ts +5 -4
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +114 -16
- package/src/Schema/SpecialJsonSchema.ts +69 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +13 -7
- package/src/Schema/email.ts +10 -2
- package/src/Schema/ext.ts +185 -82
- package/src/Schema/moreStrings.ts +21 -11
- package/src/Schema/numbers.ts +9 -8
- package/src/Schema/phoneNumber.ts +8 -1
- package/src/Schema.ts +79 -103
- package/src/client/apiClientFactory.ts +31 -35
- package/src/client/clientFor.ts +6 -1
- package/src/client/errors.ts +46 -12
- package/src/client/makeClient.ts +122 -62
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +3 -2
- package/src/index.ts +3 -11
- package/src/middleware.ts +2 -2
- package/src/rpc/MiddlewareMaker.ts +8 -7
- package/src/rpc/RpcContextMap.ts +6 -5
- package/src/rpc/RpcMiddleware.ts +5 -4
- package/src/utils/gen.ts +1 -1
- package/src/utils/logger.ts +2 -2
- package/src/utils.ts +26 -4
- package/test/dist/moreStrings.test.d.ts.map +1 -0
- package/test/dist/rpc.test.d.ts.map +1 -1
- package/test/dist/secretURL.test.d.ts.map +1 -0
- package/test/dist/special.test.d.ts.map +1 -0
- package/test/moreStrings.test.ts +17 -0
- package/test/rpc.test.ts +28 -6
- package/test/schema.test.ts +397 -4
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +732 -0
- package/test/utils.test.ts +2 -2
- package/tsconfig.base.json +0 -1
- package/tsconfig.json +0 -1
- package/dist/ServiceMap.d.ts +0 -44
- package/dist/ServiceMap.d.ts.map +0 -1
- package/dist/ServiceMap.js +0 -91
- package/dist/Struct.d.ts +0 -44
- package/dist/Struct.d.ts.map +0 -1
- package/dist/Struct.js +0 -29
- package/src/Struct.ts +0 -54
|
@@ -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
|
|
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/
|
|
7
|
+
export * from "effect/Context"
|
|
13
8
|
|
|
14
|
-
export interface Opaque<Self extends object, in out Shape extends object>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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):
|
|
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 =
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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>] =
|
|
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,
|
|
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,
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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 =
|
|
89
|
+
const tagg = Context.Service<{ env: PureEnv<never, unknown, never> }>("PureEnv")
|
|
90
90
|
function castTag<W, S, S2>() {
|
|
91
|
-
return tagg as any as
|
|
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
|
|
103
|
+
export type ServiceShape<T extends Context.ServiceClass.Shape<any, any>> = Omit<
|
|
104
104
|
T,
|
|
105
|
-
keyof
|
|
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>()
|
|
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>()
|
|
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>()
|
|
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>()
|
|
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>()
|
|
154
|
-
.
|
|
155
|
-
({ env: _ }
|
|
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 }
|
|
159
|
+
({ log, state }) => tuple(log, Result.succeed(tuple(state, x)))
|
|
161
160
|
)
|
|
162
161
|
))
|
|
163
|
-
.pipe(Effect.catch((err: any) =>
|
|
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>()
|
|
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>()
|
|
212
|
+
(castTag<W, S3, S2>()).useSync((_) => _),
|
|
214
213
|
(_: any) =>
|
|
215
214
|
Effect.map(mod(_.env.state), ([s, a]: any) => {
|
|
216
|
-
_.env.state = s
|
|
215
|
+
_.env.state = s
|
|
217
216
|
return a
|
|
218
217
|
})
|
|
219
218
|
) as any
|
package/src/Schema/Class.ts
CHANGED
|
@@ -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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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<"
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
}
|