effect-app 4.0.0-beta.12 → 4.0.0-beta.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +466 -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/{ServiceMap.d.ts → Context.d.ts} +14 -18
- 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 +104 -27
- 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 +48 -10
- package/dist/Schema/Class.d.ts.map +1 -1
- package/dist/Schema/Class.js +120 -16
- package/dist/Schema/SpecialJsonSchema.d.ts +33 -0
- package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
- package/dist/Schema/SpecialJsonSchema.js +122 -0
- package/dist/Schema/SpecialOpenApi.d.ts +32 -0
- package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
- package/dist/Schema/SpecialOpenApi.js +123 -0
- package/dist/Schema/brand.d.ts +5 -1
- 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 +111 -46
- package/dist/Schema/ext.d.ts.map +1 -1
- package/dist/Schema/ext.js +114 -53
- 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 +74 -55
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +85 -64
- package/dist/client/apiClientFactory.d.ts +11 -28
- package/dist/client/apiClientFactory.d.ts.map +1 -1
- package/dist/client/apiClientFactory.js +14 -15
- 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 +20 -15
- 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 +2 -2
- 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 +24 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +28 -2
- 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} +51 -60
- package/src/Effect.ts +11 -9
- package/src/Layer.ts +5 -4
- package/src/Pure.ts +17 -18
- package/src/Schema/Class.ts +157 -30
- package/src/Schema/SpecialJsonSchema.ts +137 -0
- package/src/Schema/SpecialOpenApi.ts +130 -0
- package/src/Schema/brand.ts +10 -3
- package/src/Schema/email.ts +10 -2
- package/src/Schema/ext.ts +191 -85
- 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 +195 -104
- package/src/client/apiClientFactory.ts +24 -29
- package/src/client/clientFor.ts +6 -1
- package/src/client/errors.ts +34 -9
- package/src/client/makeClient.ts +121 -61
- package/src/http/Request.ts +7 -4
- package/src/ids.ts +2 -1
- 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 +30 -1
- 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 +504 -4
- package/test/secretURL.test.ts +157 -0
- package/test/special.test.ts +862 -0
- package/test/utils.test.ts +2 -2
- package/tsconfig.base.json +0 -1
- package/tsconfig.json +0 -1
- 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
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,11 +197,11 @@ 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
|
|
205
|
-
})
|
|
204
|
+
}) as any
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
export function modifyM<W, R, E, A, S2, S3>(
|
|
@@ -210,7 +209,7 @@ 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
215
|
_.env.state = s
|
package/src/Schema/Class.ts
CHANGED
|
@@ -1,31 +1,88 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import {
|
|
3
|
-
import type { Struct } from "effect/Schema"
|
|
2
|
+
import { Effect, Option, Schema, SchemaAST, SchemaIssue } from "effect"
|
|
4
3
|
import * as S from "effect/Schema"
|
|
4
|
+
import { copyOrigin } from "../utils.js"
|
|
5
|
+
import { concurrencyUnbounded } from "./ext.js"
|
|
5
6
|
|
|
6
7
|
type ClassAnnotations<Self> = S.Annotations.Declaration<Self, readonly [any]>
|
|
7
8
|
|
|
8
|
-
export interface EnhancedClass<Self, SchemaS extends S.Top & { readonly fields: Struct.Fields }, Inherited>
|
|
9
|
-
extends S.Class<Self, SchemaS, Inherited
|
|
9
|
+
export interface EnhancedClass<Self, SchemaS extends S.Top & { readonly fields: S.Struct.Fields }, Inherited>
|
|
10
|
+
extends S.Class<Self, SchemaS, Inherited>
|
|
10
11
|
{
|
|
12
|
+
/**
|
|
13
|
+
* See `copyOrigin` docs in `utils.ts` for return-type design details.
|
|
14
|
+
*/
|
|
15
|
+
readonly copy: ReturnType<typeof copyOrigin<new(_: any) => Self>>
|
|
11
16
|
}
|
|
12
17
|
type MissingSelfGeneric<Usage extends string, Params extends string = ""> =
|
|
13
18
|
`Missing \`Self\` generic - use \`class Self extends ${Usage}<Self>()(${Params}{ ... })\``
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
// include: <NewProps extends S.Struct.Fields>(
|
|
17
|
-
// fnc: (fields: Fields) => NewProps
|
|
18
|
-
// ) => NewProps
|
|
19
|
-
pick: <P extends keyof Fields>(...keys: readonly P[]) => Pick<Fields, P>
|
|
20
|
-
omit: <P extends keyof Fields>(...keys: readonly P[]) => Omit<Fields, P>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type HasFields<Fields extends Struct.Fields> = {
|
|
20
|
+
type HasFields<Fields extends S.Struct.Fields> = {
|
|
24
21
|
readonly fields: Fields
|
|
25
22
|
} | {
|
|
26
23
|
readonly from: HasFields<Fields>
|
|
27
24
|
}
|
|
28
25
|
|
|
26
|
+
export type Class<Self, S extends S.Top & { readonly fields: S.Struct.Fields }, Inherited> = EnhancedClass<
|
|
27
|
+
Self,
|
|
28
|
+
S,
|
|
29
|
+
Inherited
|
|
30
|
+
>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build a modified Declaration that accepts struct-matching values during
|
|
34
|
+
* encoding, given the original Declaration and the class's fields.
|
|
35
|
+
*/
|
|
36
|
+
function makeRelaxedDeclaration(
|
|
37
|
+
ast: SchemaAST.Declaration,
|
|
38
|
+
fields: Schema.Struct.Fields,
|
|
39
|
+
cls: any
|
|
40
|
+
): SchemaAST.Declaration {
|
|
41
|
+
const structSchema = Schema.Struct(fields)
|
|
42
|
+
const isStructValue = Schema.is(structSchema)
|
|
43
|
+
const existingParseOptions = ast.annotations?.["parseOptions"] as SchemaAST.ParseOptions | undefined
|
|
44
|
+
const annotations = {
|
|
45
|
+
...ast.annotations,
|
|
46
|
+
parseOptions: { ...existingParseOptions, concurrency: "unbounded" as const }
|
|
47
|
+
}
|
|
48
|
+
return new SchemaAST.Declaration(
|
|
49
|
+
ast.typeParameters,
|
|
50
|
+
() => (input: unknown, self: SchemaAST.Declaration) => {
|
|
51
|
+
if (input instanceof cls || isStructValue(input)) {
|
|
52
|
+
return Effect.succeed(input)
|
|
53
|
+
}
|
|
54
|
+
return Effect.fail(new SchemaIssue.InvalidType(self, Option.some(input)))
|
|
55
|
+
},
|
|
56
|
+
annotations,
|
|
57
|
+
ast.checks,
|
|
58
|
+
ast.encoding,
|
|
59
|
+
ast.context
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Class — like Schema.Class but with relaxed encoding
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Like `Schema.Class`, but the resulting class accepts plain objects matching
|
|
69
|
+
* the struct schema during encoding — not only `instanceof` or type-id
|
|
70
|
+
* checks.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { Schema } from "effect"
|
|
75
|
+
* import { Class } from "./Class.js"
|
|
76
|
+
*
|
|
77
|
+
* class A extends Class<A>("A")({ a: Schema.String }) {}
|
|
78
|
+
*
|
|
79
|
+
* // Construction works as normal:
|
|
80
|
+
* new A({ a: "hello" })
|
|
81
|
+
*
|
|
82
|
+
* // Encoding accepts plain objects:
|
|
83
|
+
* Schema.encodeUnknownSync(A)({ a: "hello" }) // { a: "hello" }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
29
86
|
export const Class: <Self = never>(identifier: string) => <Fields extends S.Struct.Fields>(
|
|
30
87
|
fieldsOr: Fields | HasFields<Fields>,
|
|
31
88
|
annotations?: ClassAnnotations<Self>
|
|
@@ -35,38 +92,104 @@ export const Class: <Self = never>(identifier: string) => <Fields extends S.Stru
|
|
|
35
92
|
S.Struct<Fields>,
|
|
36
93
|
{}
|
|
37
94
|
> = (identifier) => (fields, annotations) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
95
|
+
// Build the original Schema.Class
|
|
96
|
+
const Base = (S.Class as any)(identifier)(fields, annotations)
|
|
97
|
+
// Get the original ast getter from the base class
|
|
98
|
+
const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")!
|
|
99
|
+
|
|
100
|
+
// Cache per-class to avoid recomputing
|
|
101
|
+
const astCache = new WeakMap<any, SchemaAST.Declaration>()
|
|
102
|
+
const copyCache = new WeakMap<any, ReturnType<typeof copyOrigin>>()
|
|
103
|
+
|
|
104
|
+
return class extends Base {
|
|
105
|
+
static get copy() {
|
|
106
|
+
let cached = copyCache.get(this)
|
|
107
|
+
if (cached === undefined) {
|
|
108
|
+
cached = copyOrigin(this)
|
|
109
|
+
copyCache.set(this, cached)
|
|
110
|
+
}
|
|
111
|
+
return cached
|
|
112
|
+
}
|
|
113
|
+
static get ast(): SchemaAST.Declaration {
|
|
114
|
+
let cached = astCache.get(this)
|
|
115
|
+
if (cached !== undefined) return cached
|
|
116
|
+
// Call the original getter with `this` bound to the actual user class,
|
|
117
|
+
// so getClassSchema(this) creates a schema that uses `new this(...)`.
|
|
118
|
+
const originalAst = originalAstDescriptor.get!.call(this) as SchemaAST.Declaration
|
|
119
|
+
cached = makeRelaxedDeclaration(originalAst, Base.fields, this)
|
|
120
|
+
astCache.set(this, cached)
|
|
121
|
+
return cached
|
|
122
|
+
}
|
|
123
|
+
static mapFields(f: any, options?: any) {
|
|
124
|
+
return Base.mapFields(f, options).annotate(concurrencyUnbounded)
|
|
42
125
|
}
|
|
43
|
-
// static readonly include = include(fields)
|
|
44
|
-
static readonly pick = (...selection: any[]) => pipe(this["fields"], Struct2.pick(selection))
|
|
45
|
-
static readonly omit = (...selection: any[]) => pipe(this["fields"], Struct2.omit(selection))
|
|
46
126
|
} as any
|
|
47
127
|
}
|
|
48
128
|
|
|
49
|
-
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// TaggedClass — like Schema.TaggedClass but with relaxed encoding
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Like `Schema.TaggedClass`, but the resulting class accepts plain objects
|
|
135
|
+
* matching the struct schema during encoding.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* import { Schema } from "effect"
|
|
140
|
+
* import { TaggedClass } from "./Class.js"
|
|
141
|
+
*
|
|
142
|
+
* class Circle extends TaggedClass<Circle>()("Circle", {
|
|
143
|
+
* radius: Schema.Number
|
|
144
|
+
* }) {}
|
|
145
|
+
*
|
|
146
|
+
* Schema.encodeUnknownSync(Circle)({ _tag: "Circle", radius: 5 })
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export const TaggedClass: <Self = never>(
|
|
150
|
+
identifier?: string
|
|
151
|
+
) => <Tag extends string, Fields extends S.Struct.Fields>(
|
|
50
152
|
tag: Tag,
|
|
51
153
|
fieldsOr: Fields | HasFields<Fields>,
|
|
52
154
|
annotations?: ClassAnnotations<Self>
|
|
53
|
-
) => [Self] extends [never] ? MissingSelfGeneric<"
|
|
155
|
+
) => [Self] extends [never] ? MissingSelfGeneric<"TaggedClass">
|
|
54
156
|
: EnhancedClass<
|
|
55
157
|
Self,
|
|
56
158
|
S.Struct<{ readonly _tag: S.tag<Tag> } & Fields>,
|
|
57
159
|
{}
|
|
58
160
|
> = (identifier) => (tag, fields, annotations) => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
161
|
+
const Base = (S.TaggedClass as any)(identifier)(tag, fields, annotations)
|
|
162
|
+
const originalAstDescriptor = Object.getOwnPropertyDescriptor(Base, "ast")!
|
|
163
|
+
const astCache = new WeakMap<any, SchemaAST.Declaration>()
|
|
164
|
+
const copyCache = new WeakMap<any, ReturnType<typeof copyOrigin>>()
|
|
165
|
+
|
|
166
|
+
return class extends Base {
|
|
167
|
+
static get copy() {
|
|
168
|
+
let cached = copyCache.get(this)
|
|
169
|
+
if (cached === undefined) {
|
|
170
|
+
cached = copyOrigin(this)
|
|
171
|
+
copyCache.set(this, cached)
|
|
172
|
+
}
|
|
173
|
+
return cached
|
|
174
|
+
}
|
|
175
|
+
static get ast(): SchemaAST.Declaration {
|
|
176
|
+
let cached = astCache.get(this)
|
|
177
|
+
if (cached !== undefined) return cached
|
|
178
|
+
const originalAst = originalAstDescriptor.get!.call(this) as SchemaAST.Declaration
|
|
179
|
+
cached = makeRelaxedDeclaration(originalAst, Base.fields, this)
|
|
180
|
+
astCache.set(this, cached)
|
|
181
|
+
return cached
|
|
182
|
+
}
|
|
183
|
+
static mapFields(f: any, options?: any) {
|
|
184
|
+
return Base.mapFields(f, options).annotate(concurrencyUnbounded)
|
|
63
185
|
}
|
|
64
|
-
// static readonly include = include(fields)
|
|
65
|
-
static readonly pick = (...selection: any[]) => pipe(this["fields"], Struct2.pick(selection))
|
|
66
|
-
static readonly omit = (...selection: any[]) => pipe(this["fields"], Struct2.omit(selection))
|
|
67
186
|
} as any
|
|
68
187
|
}
|
|
69
188
|
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// ExtendedClass — like Class but with extra type parameter for hierarchies
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
70
193
|
export const ExtendedClass: <Self, _SelfFrom>(identifier: string) => <Fields extends S.Struct.Fields>(
|
|
71
194
|
fieldsOr: Fields | HasFields<Fields>,
|
|
72
195
|
annotations?: ClassAnnotations<Self>
|
|
@@ -76,7 +199,11 @@ export const ExtendedClass: <Self, _SelfFrom>(identifier: string) => <Fields ext
|
|
|
76
199
|
{}
|
|
77
200
|
> = Class as any
|
|
78
201
|
|
|
79
|
-
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
// ExtendedTaggedClass — like TaggedClass but with extra type parameter for hierarchies
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
export interface EnhancedTaggedClass<Self, Tag extends string, Fields extends S.Struct.Fields, SelfFrom>
|
|
80
207
|
extends
|
|
81
208
|
EnhancedClass<
|
|
82
209
|
Self,
|
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
* Recursively removes `additionalProperties: false` from JSON Schema objects.
|
|
66
|
+
* Only removes when the value is exactly `false` -- other values are left intact.
|
|
67
|
+
*/
|
|
68
|
+
export function removeAdditionalPropertiesFalse(obj: unknown): unknown {
|
|
69
|
+
if (obj === null || typeof obj !== "object") return obj
|
|
70
|
+
|
|
71
|
+
if (globalThis.Array.isArray(obj)) {
|
|
72
|
+
return obj.map(removeAdditionalPropertiesFalse)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const record = obj as Record<string, unknown>
|
|
76
|
+
const result: Record<string, unknown> = {}
|
|
77
|
+
for (const [key, value] of Object.entries(record)) {
|
|
78
|
+
if (key === "additionalProperties" && value === false) continue
|
|
79
|
+
result[key] = removeAdditionalPropertiesFalse(value)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Flattens nested `anyOf` entries: if an anyOf entry is itself just `{ anyOf: [...] }`
|
|
87
|
+
* with no other keys, its children are inlined. If only one item remains, the anyOf
|
|
88
|
+
* wrapper is removed entirely.
|
|
89
|
+
*/
|
|
90
|
+
export function flattenNestedAnyOf(obj: unknown): unknown {
|
|
91
|
+
if (obj === null || typeof obj !== "object") return obj
|
|
92
|
+
if (globalThis.Array.isArray(obj)) return obj.map(flattenNestedAnyOf)
|
|
93
|
+
|
|
94
|
+
const record = obj as Record<string, unknown>
|
|
95
|
+
const result: Record<string, unknown> = {}
|
|
96
|
+
for (const [key, value] of Object.entries(record)) {
|
|
97
|
+
result[key] = flattenNestedAnyOf(value)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (globalThis.Array.isArray(result["anyOf"])) {
|
|
101
|
+
const anyOf = result["anyOf"] as Array<unknown>
|
|
102
|
+
const flattened: Array<unknown> = []
|
|
103
|
+
for (const entry of anyOf) {
|
|
104
|
+
if (
|
|
105
|
+
typeof entry === "object"
|
|
106
|
+
&& entry !== null
|
|
107
|
+
&& !globalThis.Array.isArray(entry)
|
|
108
|
+
&& "anyOf" in entry
|
|
109
|
+
&& Object.keys(entry).length === 1
|
|
110
|
+
&& globalThis.Array.isArray((entry as Record<string, unknown>)["anyOf"])
|
|
111
|
+
) {
|
|
112
|
+
flattened.push(...(entry as Record<string, unknown>)["anyOf"] as Array<unknown>)
|
|
113
|
+
} else {
|
|
114
|
+
flattened.push(entry)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (flattened.length === 1) {
|
|
118
|
+
const { anyOf: _, ...rest } = result
|
|
119
|
+
const single = flattened[0]
|
|
120
|
+
if (typeof single === "object" && single !== null && !globalThis.Array.isArray(single)) {
|
|
121
|
+
return { ...rest, ...single }
|
|
122
|
+
}
|
|
123
|
+
return single
|
|
124
|
+
}
|
|
125
|
+
result["anyOf"] = flattened
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return result
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Applies JSON Schema post-processing: flattens simple allOf,
|
|
133
|
+
* flattens nested anyOf, then strips additionalProperties: false.
|
|
134
|
+
*/
|
|
135
|
+
export function postProcessJsonSchema(obj: JsonSchema.JsonSchema): JsonSchema.JsonSchema {
|
|
136
|
+
return removeAdditionalPropertiesFalse(flattenNestedAnyOf(flattenSimpleAllOf(obj))) as JsonSchema.JsonSchema
|
|
137
|
+
}
|