effect 4.0.0-beta.60 → 4.0.0-beta.63
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/dist/Effect.d.ts +82 -0
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Effect.js +82 -0
- package/dist/Effect.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/effect.js +17 -1
- package/dist/internal/effect.js.map +1 -1
- package/dist/unstable/cluster/K8sHttpClient.d.ts +1 -1
- package/dist/unstable/cluster/K8sHttpClient.js +1 -1
- package/dist/unstable/cluster/K8sHttpClient.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiBuilder.d.ts +1 -1
- package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
- package/dist/unstable/httpapi/HttpApiBuilder.js +27 -17
- package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiClient.d.ts.map +1 -1
- package/dist/unstable/httpapi/HttpApiClient.js +2 -1
- package/dist/unstable/httpapi/HttpApiClient.js.map +1 -1
- package/dist/unstable/httpapi/HttpApiTest.d.ts +20 -0
- package/dist/unstable/httpapi/HttpApiTest.d.ts.map +1 -0
- package/dist/unstable/httpapi/HttpApiTest.js +54 -0
- package/dist/unstable/httpapi/HttpApiTest.js.map +1 -0
- package/dist/unstable/httpapi/index.d.ts +4 -0
- package/dist/unstable/httpapi/index.d.ts.map +1 -1
- package/dist/unstable/httpapi/index.js +4 -0
- package/dist/unstable/httpapi/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Effect.ts +88 -0
- package/src/index.ts +1 -1
- package/src/internal/effect.ts +28 -1
- package/src/unstable/cluster/K8sHttpClient.ts +3 -3
- package/src/unstable/httpapi/HttpApiBuilder.ts +38 -22
- package/src/unstable/httpapi/HttpApiClient.ts +4 -3
- package/src/unstable/httpapi/HttpApiTest.ts +95 -0
- package/src/unstable/httpapi/index.ts +5 -0
package/package.json
CHANGED
package/src/Effect.ts
CHANGED
|
@@ -7762,6 +7762,53 @@ export const orElseSucceed: {
|
|
|
7762
7762
|
<A, E, R, A2>(self: Effect<A, E, R>, evaluate: LazyArg<A2>): Effect<A | A2, never, R>
|
|
7763
7763
|
} = internal.orElseSucceed
|
|
7764
7764
|
|
|
7765
|
+
/**
|
|
7766
|
+
* Runs a sequence of effects and returns the result of the first successful
|
|
7767
|
+
* one.
|
|
7768
|
+
*
|
|
7769
|
+
* **Details**
|
|
7770
|
+
*
|
|
7771
|
+
* This function executes the provided effects in sequence, stopping at the
|
|
7772
|
+
* first success. If an effect succeeds, its result is returned immediately and
|
|
7773
|
+
* no further effects in the sequence are executed.
|
|
7774
|
+
*
|
|
7775
|
+
* If all effects fail, the returned effect fails with the error from the last
|
|
7776
|
+
* effect. If the collection is empty, the returned effect defects with an
|
|
7777
|
+
* `Error` whose message is `"Received an empty collection of effects"`.
|
|
7778
|
+
*
|
|
7779
|
+
* **When to Use**
|
|
7780
|
+
*
|
|
7781
|
+
* Use `firstSuccessOf` when you have prioritized fallback strategies, such as
|
|
7782
|
+
* attempting multiple APIs, reading configuration from several sources, or
|
|
7783
|
+
* trying alternative resource locations in order.
|
|
7784
|
+
*
|
|
7785
|
+
* @example
|
|
7786
|
+
* ```ts
|
|
7787
|
+
* import { Effect } from "effect"
|
|
7788
|
+
*
|
|
7789
|
+
* const primary = Effect.fail("primary unavailable")
|
|
7790
|
+
* const secondary = Effect.succeed("secondary result")
|
|
7791
|
+
* const tertiary = Effect.sync(() => {
|
|
7792
|
+
* throw new Error("not evaluated")
|
|
7793
|
+
* })
|
|
7794
|
+
*
|
|
7795
|
+
* const program = Effect.firstSuccessOf([
|
|
7796
|
+
* primary,
|
|
7797
|
+
* secondary,
|
|
7798
|
+
* tertiary
|
|
7799
|
+
* ])
|
|
7800
|
+
*
|
|
7801
|
+
* console.log(Effect.runSync(program))
|
|
7802
|
+
* // Output: "secondary result"
|
|
7803
|
+
* ```
|
|
7804
|
+
*
|
|
7805
|
+
* @since 2.0.0
|
|
7806
|
+
* @category Fallback
|
|
7807
|
+
*/
|
|
7808
|
+
export const firstSuccessOf: <Eff extends Effect<any, any, any>>(
|
|
7809
|
+
effects: Iterable<Eff>
|
|
7810
|
+
) => Effect<Success<Eff>, Error<Eff>, Services<Eff>> = internal.firstSuccessOf
|
|
7811
|
+
|
|
7765
7812
|
// -----------------------------------------------------------------------------
|
|
7766
7813
|
// Delays & timeouts
|
|
7767
7814
|
// -----------------------------------------------------------------------------
|
|
@@ -11992,6 +12039,47 @@ export const acquireRelease: <A, E, R, R2>(
|
|
|
11992
12039
|
options?: { readonly interruptible?: boolean }
|
|
11993
12040
|
) => Effect<A, E, R | R2 | Scope> = internal.acquireRelease
|
|
11994
12041
|
|
|
12042
|
+
/**
|
|
12043
|
+
* This function constructs a scoped resource from an Effect that acquires a
|
|
12044
|
+
* disposable value.
|
|
12045
|
+
*
|
|
12046
|
+
* The resource is automatically disposed when the surrounding
|
|
12047
|
+
* {@link Scope} is closed, using {@link Symbol.dispose} for
|
|
12048
|
+
* synchronous disposables or {@link Symbol.asyncDispose} for asynchronous
|
|
12049
|
+
* disposables.
|
|
12050
|
+
*
|
|
12051
|
+
* This is similar to {@link acquireRelease}, but uses the standard
|
|
12052
|
+
* JavaScript disposal protocal instead of requiring an explicit release
|
|
12053
|
+
* function.
|
|
12054
|
+
*
|
|
12055
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using}
|
|
12056
|
+
*
|
|
12057
|
+
* @example
|
|
12058
|
+
* ```ts
|
|
12059
|
+
* import sqlite from "node:sqlite";
|
|
12060
|
+
* import { Effect } from "effect";
|
|
12061
|
+
*
|
|
12062
|
+
* const program = Effect.scoped(
|
|
12063
|
+
* Effect.gen(function* () {
|
|
12064
|
+
* // acquire database connection
|
|
12065
|
+
* // database will be closed when the scope is closed
|
|
12066
|
+
* const db = yield* Effect.acquireDisposable(
|
|
12067
|
+
* Effect.sync(() => new sqlite.DatabaseSync(":memory:"))
|
|
12068
|
+
* )
|
|
12069
|
+
*
|
|
12070
|
+
* const row = db.prepare("SELECT 1 AS value").get()
|
|
12071
|
+
* yield* Effect.log(row) // { value: 1 }
|
|
12072
|
+
* })
|
|
12073
|
+
* )
|
|
12074
|
+
* ```
|
|
12075
|
+
*
|
|
12076
|
+
* @since 4.0.0
|
|
12077
|
+
* @category Resource Management & Finalization
|
|
12078
|
+
*/
|
|
12079
|
+
export const acquireDisposable: <A extends AsyncDisposable | Disposable, E, R>(
|
|
12080
|
+
acquire: Effect<A, E, R>
|
|
12081
|
+
) => Effect<A, E, R | Scope> = internal.acquireDisposable
|
|
12082
|
+
|
|
11995
12083
|
/**
|
|
11996
12084
|
* This function is used to ensure that an `Effect` value that represents the
|
|
11997
12085
|
* acquisition of a resource (for example, opening a file, launching a thread,
|
package/src/index.ts
CHANGED
|
@@ -3796,7 +3796,7 @@ export * as SchemaRepresentation from "./SchemaRepresentation.ts"
|
|
|
3796
3796
|
* - Trim/case strings → {@link trim}, {@link toLowerCase}, {@link toUpperCase}, {@link capitalize}, {@link uncapitalize}, {@link snakeToCamel}
|
|
3797
3797
|
* - Parse key-value strings → {@link splitKeyValue}
|
|
3798
3798
|
* - Coerce string ↔ number/bigint → {@link numberFromString}, {@link bigintFromString}
|
|
3799
|
-
* - Coerce string ↔ Date → {@link dateFromString}
|
|
3799
|
+
* - Coerce string ↔ Date/Duration → {@link dateFromString}, {@link durationFromString}
|
|
3800
3800
|
* - Decode durations → {@link durationFromNanos}, {@link durationFromMillis}
|
|
3801
3801
|
* - Wrap nullable/optional as Option → {@link optionFromNullOr}, {@link optionFromOptionalKey}, {@link optionFromOptional}
|
|
3802
3802
|
* - Parse URLs → {@link urlFromString}
|
package/src/internal/effect.ts
CHANGED
|
@@ -511,6 +511,7 @@ export class FiberImpl<A = any, E = any> implements Fiber.Fiber<A, E> {
|
|
|
511
511
|
this._children = undefined
|
|
512
512
|
this._interruptedCause = undefined
|
|
513
513
|
this._yielded = undefined
|
|
514
|
+
this.runtimeMetrics?.recordFiberStart(this.context)
|
|
514
515
|
}
|
|
515
516
|
|
|
516
517
|
readonly [FiberTypeId]: Fiber.Fiber.Variance<A, E>
|
|
@@ -582,7 +583,6 @@ export class FiberImpl<A = any, E = any> implements Fiber.Fiber<A, E> {
|
|
|
582
583
|
return this._exit
|
|
583
584
|
}
|
|
584
585
|
evaluate(effect: Primitive): void {
|
|
585
|
-
this.runtimeMetrics?.recordFiberStart(this.context)
|
|
586
586
|
if (this._exit) {
|
|
587
587
|
return
|
|
588
588
|
} else if (this._yielded !== undefined) {
|
|
@@ -3183,6 +3183,24 @@ export const orElseSucceed: {
|
|
|
3183
3183
|
): Effect.Effect<A | B, never, R> => catch_(self, (_) => sync(f))
|
|
3184
3184
|
)
|
|
3185
3185
|
|
|
3186
|
+
/** @internal */
|
|
3187
|
+
export const firstSuccessOf = <Eff extends Effect.Effect<any, any, any>>(
|
|
3188
|
+
effects: Iterable<Eff>
|
|
3189
|
+
): Effect.Effect<Effect.Success<Eff>, Effect.Error<Eff>, Effect.Services<Eff>> =>
|
|
3190
|
+
suspend(() => {
|
|
3191
|
+
const iterator = effects[Symbol.iterator]()
|
|
3192
|
+
let state = iterator.next()
|
|
3193
|
+
if (state.done) {
|
|
3194
|
+
return die(new Error("Received an empty collection of effects"))
|
|
3195
|
+
}
|
|
3196
|
+
function loop(current: IteratorYieldResult<Eff>): Eff {
|
|
3197
|
+
const next = iterator.next()
|
|
3198
|
+
if (next.done) return current.value
|
|
3199
|
+
return catch_(current.value, (_) => loop(next)) as any
|
|
3200
|
+
}
|
|
3201
|
+
return loop(state)
|
|
3202
|
+
})
|
|
3203
|
+
|
|
3186
3204
|
/** @internal */
|
|
3187
3205
|
export const eventually = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, never, R> =>
|
|
3188
3206
|
catch_(self, (_) => flatMap(yieldNow, () => eventually(self)))
|
|
@@ -4035,6 +4053,15 @@ export const acquireUseRelease = <Resource, E, R, A, E2, R2, E3, R3>(
|
|
|
4035
4053
|
))
|
|
4036
4054
|
)
|
|
4037
4055
|
|
|
4056
|
+
/** @internal */
|
|
4057
|
+
export const acquireDisposable = <A extends AsyncDisposable | Disposable, E, R>(
|
|
4058
|
+
acquire: Effect.Effect<A, E, R>
|
|
4059
|
+
): Effect.Effect<A, E, R | Scope.Scope> =>
|
|
4060
|
+
acquireRelease(acquire, (resource) =>
|
|
4061
|
+
hasProperty(resource, Symbol.asyncDispose)
|
|
4062
|
+
? promise(() => resource[Symbol.asyncDispose]())
|
|
4063
|
+
: sync(() => resource[Symbol.dispose]()))
|
|
4064
|
+
|
|
4038
4065
|
// ----------------------------------------------------------------------------
|
|
4039
4066
|
// Caching
|
|
4040
4067
|
// ----------------------------------------------------------------------------
|
|
@@ -203,7 +203,7 @@ export class PodStatus extends Schema.Class<PodStatus>("@effect/cluster/K8sHttpC
|
|
|
203
203
|
conditions: Schema.Array(Schema.Struct({
|
|
204
204
|
type: Schema.String,
|
|
205
205
|
status: Schema.String,
|
|
206
|
-
lastTransitionTime: Schema.String
|
|
206
|
+
lastTransitionTime: Schema.NullOr(Schema.String)
|
|
207
207
|
})),
|
|
208
208
|
podIP: Schema.String,
|
|
209
209
|
hostIP: Schema.String
|
|
@@ -227,8 +227,8 @@ export class Pod extends Schema.Class<Pod>("@effect/cluster/K8sHttpClient/Pod")(
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
get isReadyOrInitializing(): boolean {
|
|
230
|
-
let initializedAt: string | undefined
|
|
231
|
-
let readyAt: string | undefined
|
|
230
|
+
let initializedAt: string | null | undefined
|
|
231
|
+
let readyAt: string | null | undefined
|
|
232
232
|
for (let i = 0; i < this.status.conditions.length; i++) {
|
|
233
233
|
const condition = this.status.conditions[i]
|
|
234
234
|
switch (condition.type) {
|
|
@@ -15,7 +15,7 @@ import { type Pipeable, pipeArguments } from "../../Pipeable.ts"
|
|
|
15
15
|
import * as Redacted from "../../Redacted.ts"
|
|
16
16
|
import * as Result from "../../Result.ts"
|
|
17
17
|
import * as Schema from "../../Schema.ts"
|
|
18
|
-
import
|
|
18
|
+
import * as AST from "../../SchemaAST.ts"
|
|
19
19
|
import * as Issue from "../../SchemaIssue.ts"
|
|
20
20
|
import * as Transformation from "../../SchemaTransformation.ts"
|
|
21
21
|
import * as Scope from "../../Scope.ts"
|
|
@@ -77,7 +77,7 @@ export const layer = <Id extends string, Groups extends HttpApiGroup.Any>(
|
|
|
77
77
|
key.startsWith("effect/httpapi/HttpApiGroup/")
|
|
78
78
|
)
|
|
79
79
|
for (const group of Object.values(api.groups)) {
|
|
80
|
-
const groupRoutes = services.mapUnsafe.get(group.key) as Array<HttpRouter.Route<any, any>>
|
|
80
|
+
const groupRoutes = services.mapUnsafe.get(group.key)?.routes as Array<HttpRouter.Route<any, any>>
|
|
81
81
|
if (groupRoutes === undefined) {
|
|
82
82
|
const available = availableGroups.length === 0 ? "none" : availableGroups.join(", ")
|
|
83
83
|
return yield* Effect.die(
|
|
@@ -130,10 +130,15 @@ export const group = <
|
|
|
130
130
|
? (yield* result as Effect.Effect<any, any, any>)
|
|
131
131
|
: result
|
|
132
132
|
const routes: Array<HttpRouter.Route<any, any>> = []
|
|
133
|
-
for (const item of handlers.handlers) {
|
|
133
|
+
for (const item of handlers.handlers.values()) {
|
|
134
134
|
routes.push(handlerToRoute(group as any, item, services))
|
|
135
135
|
}
|
|
136
|
-
return Context.makeUnsafe(
|
|
136
|
+
return Context.makeUnsafe(
|
|
137
|
+
new Map([[group.key, {
|
|
138
|
+
routes,
|
|
139
|
+
handlers: handlers.handlers
|
|
140
|
+
}]])
|
|
141
|
+
)
|
|
137
142
|
})) as any
|
|
138
143
|
|
|
139
144
|
/**
|
|
@@ -162,7 +167,7 @@ export interface Handlers<
|
|
|
162
167
|
_Endpoints: Covariant<Endpoints>
|
|
163
168
|
}
|
|
164
169
|
readonly group: HttpApiGroup.AnyWithProps
|
|
165
|
-
readonly handlers:
|
|
170
|
+
readonly handlers: Map<string, Handlers.Item<R>>
|
|
166
171
|
|
|
167
172
|
/**
|
|
168
173
|
* Add the implementation for an `HttpApiEndpoint` to a `Handlers` group.
|
|
@@ -449,7 +454,7 @@ const HandlersProto = {
|
|
|
449
454
|
options?: { readonly uninterruptible?: boolean | undefined } | undefined
|
|
450
455
|
) {
|
|
451
456
|
const endpoint = this.group.endpoints[name]
|
|
452
|
-
this.handlers.
|
|
457
|
+
this.handlers.set(name, {
|
|
453
458
|
endpoint,
|
|
454
459
|
handler,
|
|
455
460
|
isRaw: false,
|
|
@@ -464,7 +469,7 @@ const HandlersProto = {
|
|
|
464
469
|
options?: { readonly uninterruptible?: boolean | undefined } | undefined
|
|
465
470
|
) {
|
|
466
471
|
const endpoint = this.group.endpoints[name]
|
|
467
|
-
this.handlers.
|
|
472
|
+
this.handlers.set(name, {
|
|
468
473
|
endpoint,
|
|
469
474
|
handler,
|
|
470
475
|
isRaw: true,
|
|
@@ -479,7 +484,7 @@ const makeHandlers = <R, Endpoints extends HttpApiEndpoint.Any>(
|
|
|
479
484
|
): Handlers<R, Endpoints> => {
|
|
480
485
|
const self = Object.create(HandlersProto)
|
|
481
486
|
self.group = group
|
|
482
|
-
self.handlers = new
|
|
487
|
+
self.handlers = new Map<string, Handlers.Item<R>>()
|
|
483
488
|
return self
|
|
484
489
|
}
|
|
485
490
|
|
|
@@ -492,6 +497,7 @@ type PayloadDecoder =
|
|
|
492
497
|
}
|
|
493
498
|
| {
|
|
494
499
|
readonly _tag: "Json" | "FormUrlEncoded" | "Uint8Array" | "Text"
|
|
500
|
+
readonly nullOnEmpty: boolean
|
|
495
501
|
readonly decode: (input: unknown) => Effect.Effect<unknown, Schema.SchemaError, unknown>
|
|
496
502
|
}
|
|
497
503
|
|
|
@@ -504,7 +510,11 @@ function buildPayloadDecoders(
|
|
|
504
510
|
if (encoding._tag === "Multipart") {
|
|
505
511
|
result.set(contentType, { _tag: "Multipart", mode: encoding.mode, limits: encoding.limits, decode })
|
|
506
512
|
} else {
|
|
507
|
-
result.set(contentType, {
|
|
513
|
+
result.set(contentType, {
|
|
514
|
+
_tag: encoding._tag,
|
|
515
|
+
decode,
|
|
516
|
+
nullOnEmpty: schemas.some((s) => AST.isNull(AST.toEncoded(s.ast)))
|
|
517
|
+
})
|
|
508
518
|
}
|
|
509
519
|
})
|
|
510
520
|
return result
|
|
@@ -527,21 +537,26 @@ function decodePayload(
|
|
|
527
537
|
switch (_tag) {
|
|
528
538
|
case "Multipart": {
|
|
529
539
|
if (existing.mode === "buffered") {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
decode
|
|
536
|
-
)
|
|
540
|
+
let eff = Effect.orDie(httpRequest.multipart)
|
|
541
|
+
if (existing.limits) {
|
|
542
|
+
eff = Effect.provideContext(eff, Multipart.limitsServices(existing.limits))
|
|
543
|
+
}
|
|
544
|
+
return Effect.flatMap(eff, decode)
|
|
537
545
|
}
|
|
538
|
-
return Effect.succeed(
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
546
|
+
return Effect.succeed(
|
|
547
|
+
existing.limits
|
|
548
|
+
? Stream.provideContext(httpRequest.multipartStream, Multipart.limitsServices(existing.limits))
|
|
549
|
+
: httpRequest.multipartStream
|
|
550
|
+
)
|
|
542
551
|
}
|
|
543
552
|
case "Json":
|
|
544
|
-
|
|
553
|
+
const json = Effect.orDie(Effect.flatMap(httpRequest.text, (text) => {
|
|
554
|
+
if (text === "") {
|
|
555
|
+
return existing.nullOnEmpty ? Effect.succeed(null) : Effect.undefined
|
|
556
|
+
}
|
|
557
|
+
return Effect.succeed(JSON.parse(text))
|
|
558
|
+
}))
|
|
559
|
+
return Effect.flatMap(json, decode)
|
|
545
560
|
case "Text":
|
|
546
561
|
return Effect.flatMap(Effect.orDie(httpRequest.text), decode)
|
|
547
562
|
case "FormUrlEncoded": {
|
|
@@ -622,7 +637,8 @@ function handlerToHttpEffect(
|
|
|
622
637
|
)
|
|
623
638
|
}
|
|
624
639
|
|
|
625
|
-
|
|
640
|
+
/** @internal */
|
|
641
|
+
export function handlerToRoute(
|
|
626
642
|
group: HttpApiGroup.AnyWithProps,
|
|
627
643
|
handler: Handlers.Item<any>,
|
|
628
644
|
context: Context.Context<any>
|
|
@@ -175,7 +175,8 @@ type UrlBuilderTopLevelMethods<Groups extends HttpApiGroup.Any> = Extract<Groups
|
|
|
175
175
|
: never :
|
|
176
176
|
never
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
/** @internal */
|
|
179
|
+
export const makeClient = <ApiId extends string, Groups extends HttpApiGroup.Any, E, R>(
|
|
179
180
|
api: HttpApi.HttpApi<ApiId, Groups>,
|
|
180
181
|
options: {
|
|
181
182
|
readonly httpClient: HttpClient.HttpClient.With<E, R>
|
|
@@ -201,9 +202,9 @@ const makeClient = <ApiId extends string, Groups extends HttpApiGroup.Any, E, R>
|
|
|
201
202
|
| undefined
|
|
202
203
|
readonly baseUrl?: URL | string | undefined
|
|
203
204
|
}
|
|
204
|
-
): Effect.Effect<void
|
|
205
|
+
): Effect.Effect<void> =>
|
|
205
206
|
Effect.gen(function*() {
|
|
206
|
-
const services = yield* Effect.context
|
|
207
|
+
const services = yield* Effect.context()
|
|
207
208
|
|
|
208
209
|
const httpClient = options.httpClient.pipe(
|
|
209
210
|
options?.baseUrl === undefined
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 4.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as Context from "../../Context.ts"
|
|
5
|
+
import * as Effect from "../../Effect.ts"
|
|
6
|
+
import type { FileSystem } from "../../FileSystem.ts"
|
|
7
|
+
import * as Layer from "../../Layer.ts"
|
|
8
|
+
import type { Path } from "../../Path.ts"
|
|
9
|
+
import type { Scope } from "../../Scope.ts"
|
|
10
|
+
import type { Generator } from "../http/Etag.ts"
|
|
11
|
+
import * as HttpClient from "../http/HttpClient.ts"
|
|
12
|
+
import type { HttpPlatform } from "../http/HttpPlatform.ts"
|
|
13
|
+
import * as HttpRouter from "../http/HttpRouter.ts"
|
|
14
|
+
import * as HttpServerRequest from "../http/HttpServerRequest.ts"
|
|
15
|
+
import * as HttpServerResponse from "../http/HttpServerResponse.ts"
|
|
16
|
+
import type * as HttpApi from "./HttpApi.ts"
|
|
17
|
+
import type { Handlers } from "./HttpApiBuilder.ts"
|
|
18
|
+
import * as HttpApiBuilder from "./HttpApiBuilder.ts"
|
|
19
|
+
import * as HttpApiClient from "./HttpApiClient.ts"
|
|
20
|
+
import type * as HttpApiEndpoint from "./HttpApiEndpoint.ts"
|
|
21
|
+
import type * as HttpApiGroup from "./HttpApiGroup.ts"
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @since 4.0.0
|
|
25
|
+
* @category Testing
|
|
26
|
+
*/
|
|
27
|
+
export const groups = Effect.fnUntraced(function*<
|
|
28
|
+
ApiId extends string,
|
|
29
|
+
Groups extends HttpApiGroup.Any,
|
|
30
|
+
const Names extends ReadonlyArray<HttpApiGroup.Name<Groups>>,
|
|
31
|
+
SelectedGroups = HttpApiGroup.WithName<Groups, Names[number]>
|
|
32
|
+
>(
|
|
33
|
+
api: HttpApi.HttpApi<ApiId, Groups>,
|
|
34
|
+
groupNames: Names
|
|
35
|
+
): Effect.fn.Return<
|
|
36
|
+
HttpApiClient.Client<Groups>,
|
|
37
|
+
never,
|
|
38
|
+
| HttpApiGroup.ToService<ApiId, SelectedGroups>
|
|
39
|
+
| HttpApiGroup.MiddlewareClient<Groups>
|
|
40
|
+
| HttpApiEndpoint.Middleware<HttpApiGroup.Endpoints<Groups>>
|
|
41
|
+
| FileSystem
|
|
42
|
+
| Generator
|
|
43
|
+
| HttpPlatform
|
|
44
|
+
| Path
|
|
45
|
+
| Scope
|
|
46
|
+
> {
|
|
47
|
+
let context = yield* Effect.context<HttpApiGroup.ToService<ApiId, SelectedGroups>>()
|
|
48
|
+
|
|
49
|
+
for (const name in api.groups) {
|
|
50
|
+
const group = api.groups[name]
|
|
51
|
+
if (groupNames.includes(name as any)) {
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
const handlers = new Map<string, Handlers.Item<never>>()
|
|
55
|
+
const routes: Array<HttpRouter.Route<any, any>> = []
|
|
56
|
+
for (const endpointName in group.endpoints) {
|
|
57
|
+
const endpoint = group.endpoints[endpointName]
|
|
58
|
+
const handler: Handlers.Item<never> = {
|
|
59
|
+
endpoint: endpoint as any,
|
|
60
|
+
handler: () => Effect.die(new Error(`Unhandled endpoint: ${endpointName}`)),
|
|
61
|
+
isRaw: false,
|
|
62
|
+
uninterruptible: false
|
|
63
|
+
}
|
|
64
|
+
handlers.set(endpointName, handler)
|
|
65
|
+
routes.push(HttpApiBuilder.handlerToRoute(group as any, handler, context))
|
|
66
|
+
}
|
|
67
|
+
context = Context.add(context, group as any, { handlers, routes })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const layer: Layer.Layer<
|
|
71
|
+
never,
|
|
72
|
+
never,
|
|
73
|
+
| FileSystem
|
|
74
|
+
| Generator
|
|
75
|
+
| HttpPlatform
|
|
76
|
+
| HttpRouter.HttpRouter
|
|
77
|
+
| Path
|
|
78
|
+
> = HttpApiBuilder.layer(api).pipe(
|
|
79
|
+
Layer.provide(Layer.succeedContext(context))
|
|
80
|
+
) as any
|
|
81
|
+
const handler = yield* HttpRouter.toHttpEffect(layer)
|
|
82
|
+
const httpClient = HttpClient.make(Effect.fnUntraced(function*(request) {
|
|
83
|
+
const serverRequest = HttpServerRequest.fromClientRequest(request)
|
|
84
|
+
const response = yield* handler.pipe(
|
|
85
|
+
Effect.provideService(HttpServerRequest.HttpServerRequest, serverRequest),
|
|
86
|
+
Effect.orDie
|
|
87
|
+
)
|
|
88
|
+
return HttpServerResponse.toClientResponse(response)
|
|
89
|
+
}, Effect.scoped))
|
|
90
|
+
|
|
91
|
+
return yield* HttpApiClient.makeWith(api, {
|
|
92
|
+
httpClient,
|
|
93
|
+
baseUrl: "http://localhost:3000"
|
|
94
|
+
})
|
|
95
|
+
})
|