Merge commit '36c66dd290d3ce6eb1ccd310d0c658d4a32bb8eb' as 'repos/effect'
This commit is contained in:
141
repos/effect/packages/platform-browser/test/fixtures/rpc-e2e.ts
vendored
Normal file
141
repos/effect/packages/platform-browser/test/fixtures/rpc-e2e.ts
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
import { assert, describe, it } from "@effect/vitest"
|
||||
import { Cause, Context, Effect, Fiber, Option, Stream } from "effect"
|
||||
// @ts-ignore
|
||||
// oxlint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { NodeInspectSymbol } from "effect/Inspectable"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as RpcClient from "effect/unstable/rpc/RpcClient"
|
||||
import type { RpcClientError } from "effect/unstable/rpc/RpcClientError"
|
||||
import type * as RpcGroup from "effect/unstable/rpc/RpcGroup"
|
||||
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
||||
import * as RpcTest from "effect/unstable/rpc/RpcTest"
|
||||
import { AuthClient, AuthLive, TimingLive, User, UserRpcs, UsersLive } from "./rpc-schemas.ts"
|
||||
|
||||
export class UsersClient extends Context.Service<
|
||||
UsersClient,
|
||||
RpcClient.RpcClient<RpcGroup.Rpcs<typeof UserRpcs>, RpcClientError>
|
||||
>()("UsersClient") {
|
||||
static readonly layer = Layer.effect(UsersClient)(RpcClient.make(UserRpcs)).pipe(
|
||||
Layer.provide(AuthClient)
|
||||
)
|
||||
static layerTest = Layer.effect(UsersClient)(RpcTest.makeClient(UserRpcs)).pipe(
|
||||
Layer.provide([UsersLive, AuthLive, TimingLive, AuthClient])
|
||||
)
|
||||
}
|
||||
|
||||
export const e2eSuite = <E>(
|
||||
name: string,
|
||||
layer: Layer.Layer<UsersClient | RpcServer.Protocol, E>,
|
||||
concurrent = true
|
||||
) => {
|
||||
describe(name, { concurrent, timeout: 30_000 }, () => {
|
||||
it.effect("should get user", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const user = yield* client.GetUser({ id: "1" })
|
||||
assert.instanceOf(user, User)
|
||||
assert.deepStrictEqual(user, new User({ id: "1", name: "Logged in user" }))
|
||||
}).pipe(Effect.provide(layer)))
|
||||
|
||||
it.effect("nested method", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
yield* client["nested.test"]()
|
||||
}).pipe(Effect.provide(layer)))
|
||||
|
||||
it.effect("should not flatten Option", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const user = yield* client.GetUserOption({ id: "1" })
|
||||
assert.deepStrictEqual(user, Option.some(new User({ id: "1", name: "John" })))
|
||||
}).pipe(Effect.provide(layer)))
|
||||
|
||||
it.effect("headers", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const user = yield* client.GetUser({ id: "1" })
|
||||
assert.instanceOf(user, User)
|
||||
assert.deepStrictEqual(user, new User({ id: "123", name: "Logged in user" }))
|
||||
}).pipe(
|
||||
RpcClient.withHeaders({ userId: "123" }),
|
||||
Effect.provide(layer)
|
||||
))
|
||||
|
||||
it.live("Stream", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const users: Array<User> = []
|
||||
yield* client.StreamUsers({ id: "1" }).pipe(
|
||||
Stream.take(5),
|
||||
Stream.runForEach((user) =>
|
||||
Effect.sync(() => {
|
||||
users.push(user)
|
||||
})
|
||||
),
|
||||
Effect.forkChild
|
||||
)
|
||||
|
||||
yield* Effect.sleep(2000)
|
||||
assert.lengthOf(users, 5)
|
||||
|
||||
// test interrupts
|
||||
const interrupts = yield* client.GetInterrupts()
|
||||
assert.equal(interrupts, 1)
|
||||
|
||||
const { supportsAck } = yield* RpcServer.Protocol
|
||||
|
||||
// test backpressure
|
||||
if (supportsAck) {
|
||||
const emits = yield* client.GetEmits()
|
||||
assert.equal(emits, 5)
|
||||
}
|
||||
}).pipe(Effect.provide(layer)), { timeout: 20000 })
|
||||
|
||||
it.effect("defect", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const cause = yield* client.ProduceDefect().pipe(
|
||||
Effect.sandbox,
|
||||
Effect.flip
|
||||
)
|
||||
assert.deepStrictEqual(cause, Cause.die("boom"))
|
||||
}).pipe(
|
||||
RpcClient.withHeaders({ userId: "123" }),
|
||||
Effect.provide(layer)
|
||||
))
|
||||
|
||||
it.live("never", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const fiber = yield* client.Never().pipe(
|
||||
Effect.forkChild
|
||||
)
|
||||
yield* Effect.sleep(500)
|
||||
assert.isUndefined(fiber.pollUnsafe())
|
||||
|
||||
yield* Fiber.interrupt(fiber)
|
||||
yield* Effect.sleep(100)
|
||||
|
||||
const { supportsAck } = yield* RpcServer.Protocol
|
||||
if (supportsAck) {
|
||||
const interrupts = yield* client.GetInterrupts()
|
||||
assert.equal(interrupts, 1)
|
||||
}
|
||||
}).pipe(
|
||||
RpcClient.withHeaders({ userId: "123" }),
|
||||
Effect.provide(layer)
|
||||
))
|
||||
|
||||
it.effect("timing middleware", () =>
|
||||
Effect.gen(function*() {
|
||||
const client = yield* UsersClient
|
||||
const result = yield* client.TimedMethod({ shouldFail: false })
|
||||
assert.equal(result, 1)
|
||||
yield* client.TimedMethod({ shouldFail: true }).pipe(Effect.exit)
|
||||
const { count, defect, success } = yield* client.GetTimingMiddlewareMetrics()
|
||||
assert.notEqual(count, 0)
|
||||
assert.notEqual(defect, 0)
|
||||
assert.notEqual(success, 0)
|
||||
}).pipe(Effect.provide(layer)))
|
||||
})
|
||||
}
|
||||
157
repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts
vendored
Normal file
157
repos/effect/packages/platform-browser/test/fixtures/rpc-schemas.ts
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Context, Effect, Layer, Metric, Option, Queue, Schema } from "effect"
|
||||
import { Headers } from "effect/unstable/http"
|
||||
import * as Rpc from "effect/unstable/rpc/Rpc"
|
||||
import * as RpcGroup from "effect/unstable/rpc/RpcGroup"
|
||||
import * as RpcMiddleware from "effect/unstable/rpc/RpcMiddleware"
|
||||
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
||||
|
||||
export class User extends Schema.Class<User>("User")({
|
||||
id: Schema.String,
|
||||
name: Schema.String
|
||||
}) {}
|
||||
|
||||
class StreamUsers extends Rpc.make("StreamUsers", {
|
||||
success: User,
|
||||
payload: {
|
||||
id: Schema.String
|
||||
},
|
||||
stream: true
|
||||
}) {}
|
||||
|
||||
class CurrentUser extends Context.Service<CurrentUser, User>()("CurrentUser") {}
|
||||
|
||||
class Unauthorized extends Schema.ErrorClass<Unauthorized>("Unauthorized")({
|
||||
_tag: Schema.tag("Unauthorized")
|
||||
}) {}
|
||||
|
||||
class AuthMiddleware extends RpcMiddleware.Service<AuthMiddleware, {
|
||||
provides: CurrentUser
|
||||
}>()("AuthMiddleware", {
|
||||
error: Unauthorized,
|
||||
requiredForClient: true
|
||||
}) {}
|
||||
|
||||
class TimingMiddleware extends RpcMiddleware.Service<TimingMiddleware>()("TimingMiddleware") {}
|
||||
|
||||
class GetUser extends Rpc.make("GetUser", {
|
||||
success: User,
|
||||
payload: { id: Schema.String }
|
||||
}) {}
|
||||
|
||||
export const UserRpcs = RpcGroup.make(
|
||||
GetUser,
|
||||
Rpc.make("GetUserOption", {
|
||||
success: Schema.Option(User),
|
||||
payload: { id: Schema.String }
|
||||
}),
|
||||
StreamUsers,
|
||||
Rpc.make("GetInterrupts", {
|
||||
success: Schema.Number
|
||||
}),
|
||||
Rpc.make("GetEmits", {
|
||||
success: Schema.Number
|
||||
}),
|
||||
Rpc.make("ProduceDefect"),
|
||||
Rpc.make("Never"),
|
||||
Rpc.make("nested.test"),
|
||||
Rpc.make("TimedMethod", {
|
||||
payload: {
|
||||
shouldFail: Schema.Boolean
|
||||
},
|
||||
success: Schema.Number
|
||||
}).middleware(TimingMiddleware),
|
||||
Rpc.make("GetTimingMiddlewareMetrics", {
|
||||
success: Schema.Struct({
|
||||
success: Schema.Number,
|
||||
defect: Schema.Number,
|
||||
count: Schema.Number
|
||||
})
|
||||
})
|
||||
).middleware(AuthMiddleware)
|
||||
|
||||
export const AuthLive = Layer.succeed(AuthMiddleware)(
|
||||
AuthMiddleware.of((effect, options) =>
|
||||
Effect.provideService(
|
||||
effect,
|
||||
CurrentUser,
|
||||
new User({ id: options.headers.userid ?? "1", name: options.headers.name ?? "Fallback name" })
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const rpcSuccesses = Metric.counter("rpc_middleware_success")
|
||||
const rpcDefects = Metric.counter("rpc_middleware_defects")
|
||||
const rpcCount = Metric.counter("rpc_middleware_count")
|
||||
export const TimingLive = Layer.succeed(TimingMiddleware)(
|
||||
TimingMiddleware.of((effect) =>
|
||||
effect.pipe(
|
||||
Effect.tap(Metric.update(rpcSuccesses, 1)),
|
||||
Effect.tapDefect(() => Metric.update(rpcDefects, 1)),
|
||||
Effect.ensuring(Metric.update(rpcCount, 1))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
export const UsersLive = UserRpcs.toLayer(Effect.gen(function*() {
|
||||
let interrupts = 0
|
||||
let emits = 0
|
||||
return UserRpcs.of({
|
||||
GetUser: (_) =>
|
||||
CurrentUser.pipe(
|
||||
Rpc.fork
|
||||
),
|
||||
GetUserOption: Effect.fnUntraced(function*(req) {
|
||||
return Option.some(new User({ id: req.id, name: "John" }))
|
||||
}),
|
||||
StreamUsers: Effect.fnUntraced(function*(req, _) {
|
||||
const mailbox = yield* Queue.bounded<User>(0)
|
||||
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.sync(() => {
|
||||
interrupts++
|
||||
})
|
||||
)
|
||||
|
||||
yield* Queue.offer(mailbox, new User({ id: req.id, name: "John" })).pipe(
|
||||
Effect.tap(() =>
|
||||
Effect.sync(() => {
|
||||
emits++
|
||||
})
|
||||
),
|
||||
Effect.delay(100),
|
||||
Effect.forever,
|
||||
Effect.forkScoped
|
||||
)
|
||||
|
||||
return mailbox
|
||||
}),
|
||||
GetInterrupts: () => Effect.sync(() => interrupts),
|
||||
GetEmits: () => Effect.sync(() => emits),
|
||||
ProduceDefect: () => Effect.die("boom"),
|
||||
Never: () => Effect.never.pipe(Effect.onInterrupt(() => Effect.sync(() => interrupts++))),
|
||||
"nested.test": () => Effect.void,
|
||||
TimedMethod: (_) => _.shouldFail ? Effect.die("boom") : Effect.succeed(1),
|
||||
GetTimingMiddlewareMetrics: () =>
|
||||
Effect.all({
|
||||
defect: Metric.value(rpcDefects).pipe(Effect.map((_) => _.count)),
|
||||
success: Metric.value(rpcSuccesses).pipe(Effect.map((_) => _.count)),
|
||||
count: Metric.value(rpcCount).pipe(Effect.map((_) => _.count))
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
||||
export const RpcLive = RpcServer.layer(UserRpcs, {
|
||||
disableFatalDefects: true
|
||||
}).pipe(
|
||||
Layer.provide([
|
||||
UsersLive,
|
||||
AuthLive,
|
||||
TimingLive
|
||||
])
|
||||
)
|
||||
|
||||
export const AuthClient = RpcMiddleware.layerClient(AuthMiddleware, ({ next, request }) =>
|
||||
next({
|
||||
...request,
|
||||
headers: Headers.set(request.headers, "name", "Logged in user")
|
||||
}))
|
||||
12
repos/effect/packages/platform-browser/test/fixtures/rpc-worker.ts
vendored
Normal file
12
repos/effect/packages/platform-browser/test/fixtures/rpc-worker.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as BrowserWorkerRunner from "@effect/platform-browser/BrowserWorkerRunner"
|
||||
import * as Effect from "effect/Effect"
|
||||
import * as Layer from "effect/Layer"
|
||||
import * as RpcServer from "effect/unstable/rpc/RpcServer"
|
||||
import { RpcLive } from "./rpc-schemas.ts"
|
||||
|
||||
const MainLive = RpcLive.pipe(
|
||||
Layer.provide(RpcServer.layerProtocolWorkerRunner),
|
||||
Layer.provide(BrowserWorkerRunner.layer)
|
||||
)
|
||||
|
||||
Effect.runFork(Layer.launch(MainLive))
|
||||
Reference in New Issue
Block a user