-
-
Notifications
You must be signed in to change notification settings - Fork 257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Effect is not a serializable value #3974
Comments
@MrOxMasTer - it is not possible to serialize an Effect because the AST of instructions described by an Effect may include arbitrary user data which may or may not be serializable, and also includes continuations (i.e. arbitrary functions) which are impossible to serialize. The use of classes to represent individual Effect instructions is irrelevant in this case. Also, an I would suggest looking into |
Yes indeed, Effect is not a class, but is still a non-serializable value. |
Effect represents a computation so it won't ever be possible to perform serialization, what you're asking is the equivalent of serializing import { Cause, Effect } from "effect"
const program = ...
const result = Effect.runPromise(
program.pipe(
Effect.matchCauseEffect({
onSuccess: (x) => Effect.succeed({ _tag: "Success" as const, value: x }),
onFailure: (c) => Effect.succeed({ _tag: "Failure" as const, value: Cause.pretty(c) })
})
)
) |
The whole point of effect is to make things safe and production ready, using libraries like Zod and tRPC that don't allow for bidirectional encoding / decoding is unsafe and out of scope for effect. |
I tried using the Exit object, but it still doesn't serialize with next.js and gives an error:
Code: getByEmail2: publicProcedure
.input(emailSchema)
.query(async ({ input: email }) => {
'use cache';
console.log('GET_BY_EMAIL_2');
return Effect.gen(function* (_) {
const db = yield* DBClient;
const user = yield* _(
Effect.tryPromise({
try: () =>
db.query.users.findFirst({
where: (users, { eq }) => eq(users.email, email),
}),
catch: (error) =>
new UnknownDatabaseError({
message: `${error}`,
}),
}),
Effect.filterOrElse(
(user) => !!user,
() => new UserNotFoundError(),
),
);
return yield* Effect.succeed(user);
}).pipe(Effect.provideService(DBClient, db), Effect.runPromiseExit);
}), |
You can serialize |
Thanks for the offer. I wrote above that I don't use effect schemas because I use a different library for validation, because it combines with other libraries and I like its syntax better |
Effect Schema is the only validation library to my knowledge that supports bi-directional decoding & encoding. If you are using something else like Zod, you'll have to manually serialize/encode the values. |
I can't help that I have 5 libs that depend on zod: |
This helped solve the problem. Exit was already a serializable object, and decode/encode was just a type transformation. I tried to cache both decoded and encoded value, but it still causes an error:
|
Note that many structures in effect do implement getByEmail2: publicProcedure
.input(emailSchema)
.query(async ({ input: email }) => {
return Exit.succeed(1) // Exit.Exit<number>
}) We magically serialized it, now lets consume it (just imagine we skip the loading) const { data: exit } = trpc.getByEmail.useQuery(email);
// ^? Exit.Exit<number>
const value = exit.pipe(
Exit.map(...),
... // etc
) Everything looks fine 🎉
I hope it's clear now why // Encoded Exit
type ExitSuccess<A> = { _tag: "Success", _id: "Exit", value: A }
// Decoded (aka 'real') Exit
class EffectPrimitiveSuccess {
public effect_instruction_i0 = undefined
public effect_instruction_i1 = undefined
public effect_instruction_i2 = undefined
public trace = undefined;
[EffectTypeId] = effectVariance
constructor(readonly _op: Primitive["_op"]) {
// @ts-expect-error
this._tag = _op
}
[Equal.symbol](this: {}, that: unknown) {
return exitIsExit(that) && that._op === "Success" &&
// @ts-expect-error
Equal.equals(this.effect_instruction_i0, that.effect_instruction_i0)
}
[Hash.symbol](this: {}) {
return pipe(
// @ts-expect-error
Hash.string(this._tag),
// @ts-expect-error
Hash.combine(Hash.hash(this.effect_instruction_i0)),
Hash.cached(this)
)
}
get value() {
return this.effect_instruction_i0
}
pipe() {
return pipeArguments(this, arguments)
}
toJSON() { // <-- You're seeing the result of this
return {
_id: "Exit",
_tag: this._op,
value: toJSON(this.value)
}
}
toString() { // <-- and this
return format(this.toJSON())
}
[NodeInspectSymbol]() { // <-- and this
return this.toJSON()
}
[Symbol.iterator]() {
return new SingleShotGen(new YieldWrap(this))
}
} This is true (though usually not to this extent) with other data structures in effect.
It does, but as part of |
Maybe it's just me, but it's kind of like this: |
Also. Yes, I don't dispute that you can get fields from I've already made a `issue' about it: #3986 |
It is not as complicated as it might seem right now, maybe the terminology is a bit new. In your usecase, encoded/decoded can be mentally mapped out to from/to outside, to visualize:
so a message arrives, it is serialized-- it is encoded into some json-compatible shape.
But they do. Theres a whole module for working with multipart forms and file payloads, but as I said, they are part of |
You have really good answers - for that I want to say thank you. I would be glad if you could answer my last 2 |
What is the problem this feature would solve?
More convenient work with other libraries.
The problem is that it is inconvenient to use Effect with other libraries, such as a number of these:
Yes, you can make a promise, but then the Effect type itself is lost and it is not so convenient to control the function in other effects that use this function (promise). You have to work with it as a promis, which is not very convenient, and also removes all the advantages of using the effect:
Here is an example. The person talks about the impossibility of serialization and explains it in a bit more detail. I noticed it in other points, but the gist is the same: https://youtu.be/HInf8wvUovk?t=237
Here's an example of what happens because Effect is not a serializable value:
What is the feature you are proposing to solve the problem?
I don't know yet. Instead of a class, use an object
What alternatives have you considered?
An approximate solution to the problem (Result<T, E>):
https://youtu.be/HInf8wvUovk?t=578
The text was updated successfully, but these errors were encountered: