Skip to content
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

Closed
MrOxMasTer opened this issue Nov 20, 2024 · 19 comments
Closed

Effect is not a serializable value #3974

MrOxMasTer opened this issue Nov 20, 2024 · 19 comments
Labels
wontfix This will not be worked on working as intended

Comments

@MrOxMasTer
Copy link

MrOxMasTer commented Nov 20, 2024

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:

  • Next.js - inability to serialize to cache and save function result ('use cache' or unstable_cache)
  • Trpc - return promise initially when creating functions and return either promise (Effect.runPromise) or Effect, but because of the impossibility of serialization, I can't use the Effect type

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:
image
image

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

@MrOxMasTer MrOxMasTer added the enhancement New feature or request label Nov 20, 2024
@IMax153
Copy link
Member

IMax153 commented Nov 21, 2024

@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 Effect does not represent a Result, but instead represents a series of instructions to execute to arrive at a result (in Effect the name we give to our result is Exit).

I would suggest looking into Schema for data serialization / deserialization.

@IMax153 IMax153 added wontfix This will not be worked on working as intended and removed enhancement New feature or request labels Nov 21, 2024
@MrOxMasTer
Copy link
Author

MrOxMasTer commented Nov 21, 2024

I would suggest looking into Schema for data serialization / deserialization.

Yes indeed, Effect is not a class, but is still a non-serializable value.
What if I have a different library for validation (like zod). Yours is not suitable for me. It has few interacting libraries with others (with orm, for example drizzle and with other additional libraries for zod). And rpc is not ready yet.
What should I do in this case? Is it really in promise to make an effect and lose all the advantages? Is it really convenient to make a new section of the library (schema, rpc, cache, etc.) for each case, instead of making Effect itself serializable?
Or create some kind of layer between promise and Effect to work with other libraries that don't involve working with Effect (just about everything like: trpc, next-safe-action, next.js). A layer that helps to make a promis from Effect, but understand what errors it returns, the result, etc

@mikearnaldi
Copy link
Member

I would suggest looking into Schema for data serialization / deserialization.

Yes indeed, Effect is not a class, but is still a non-serializable value. What if I have a different library for validation (like zod). Yours is not suitable for me. It has few interacting libraries with others (with orm, for example drizzle and with other additional libraries for zod). And rpc is not ready yet. What should I do in this case? Is it really in promise to make an effect and lose all the advantages? Is it really convenient to make a new section of the library (schema, rpc, cache, etc.) for each case, instead of making Effect itself serializable?

Effect represents a computation so it won't ever be possible to perform serialization, what you're asking is the equivalent of serializing const hello = () => console.log("AA"). What you can serialize are result types like Option / Either / Exit albeit not directly with JSON.stringify/JSON.parse. If you want plain JS objects you can return plain JS objects from your computation:

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) })
    })
  )
)

@mikearnaldi
Copy link
Member

Is it really convenient to make a new section of the library (schema, rpc, cache, etc.) for each case, instead of making Effect itself serializable?
Or create some kind of layer between promise and Effect to work with other libraries that don't involve working with Effect (just about everything like: trpc, next-safe-action, next.js)

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.

@MrOxMasTer
Copy link
Author

MrOxMasTer commented Nov 21, 2024

Effect represents a computation so it won't ever be possible to perform serialization, what you're asking is the equivalent of serializing const hello = () => console.log("AA"). What you can serialize are result types like Option / Either / Exit albeit not directly with JSON.stringify/JSON.parse. If you want plain JS objects you can return plain JS objects from your computation:

I tried using the Exit object, but it still doesn't serialize with next.js and gives an error:

Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.       
  [{}]
   ^^
    at Error (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1970:16)
    at renderModelDestructive (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1714:15)
    at request (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1481:29)
    at stringify (<anonymous>)
    at stringify (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:2613:46)
    at emitChunk (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:2643:12)
    at retryTask (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:2716:10)
    at performWork (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1428:21)        
  1968 |             null !== getPrototypeOf(elementReference))
  1969 |         )
> 1970 |           throw Error(
       |                ^
  1971 |             "Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported." +
  1972 |               describeObjectForErrorMessage(parent, parentPropertyName)
  1973 |           );
Error while saving cache key: ["development","c0752fcdba28f2f16c6634f54e2135fc07d719aff3",[{"path":"user.getByEmail2","getRawInput":"$T","ctx":{},"type":"query","signal":"$undefined","input":"oxmaster@gmail.com","meta":"$undefined","next":"$T"}]] Error: Only plain 
objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
  [{}]
   ^^
    at Error (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1970:16)
    at renderModelDestructive (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1714:15)
    at request (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1481:29)
    at stringify (<anonymous>)
    at stringify (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:2613:46)
    at emitChunk (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:2643:12)
    at retryTask (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:2716:10)
    at performWork (webpack://next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.development.js:1428:21)        
  1968 |             null !== getPrototypeOf(elementReference))
  1969 |         )
> 1970 |           throw Error(
       |                ^
  1971 |             "Only plain objects, and a few built-ins, can be 
passed to Client Components from Server Components. Classes or null prototypes are not supported." +
  1972 |               describeObjectForErrorMessage(parent, parentPropertyName)
  1973 |           );

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);
    }),

image

@MrOxMasTer
Copy link
Author

although with JSON.stringify this is serialized:
image

@fubhy
Copy link
Member

fubhy commented Nov 21, 2024

You can serialize Exit values with Effect Schema: https://effect.website/docs/schema/effect-data-types/#exit-1

@MrOxMasTer
Copy link
Author

You can serialize Exit values with Effect Schema: https://effect.website/docs/schema/effect-data-types/#exit-1

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

@fubhy
Copy link
Member

fubhy commented Nov 22, 2024

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.

@MrOxMasTer
Copy link
Author

MrOxMasTer commented Nov 23, 2024

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: useHookForm, drizzle-orm, next-safe-action, zod-form-data, trpc. So as far as I know useHookForm supports effect. Drizzle - I saw that the package is already being prepared, but I don't know about next-safe-action and zod-form-data. Maybe effect has its own functions to handle form-data? And I will talk to the next-safe-action developer. trpc as far as I have seen can be used with Effect schemes

drizzle-team/drizzle-orm#2665

@MrOxMasTer
Copy link
Author

You can serialize Exit values with Effect Schema: https://effect.website/docs/schema/effect-data-types/#exit-1

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:

Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
  [{}]
   ^^
    at stringify (<anonymous>)
Error while saving cache key: ["development","c01d88351cfccc89cd44791184b2e36426bd6f7147",[{"path":"user.getByEmail","getRawInput":"$T","ctx":{},"type":"query","signal":"$undefined","input":"oxmaster911@gmail.com","meta":"$undefined","next":"$T"}]] Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
  [{}]
   ^^
    at stringify (<anonymous>)
ААААА:  {"_id":"FiberFailure","cause":{"_id":"Cause","_tag":"Die","defect":{"cause":{"environmentName":"Cache","digest":""},"code":"INTERNAL_SERVER_ERROR","name":"TRPCError"}}}
 POST /auth 200 in 2200ms

@datner
Copy link
Member

datner commented Nov 25, 2024

Note that many structures in effect do implement .toJSON(). This is not serialization, it's mere representation, but that is what you're seeing and why JSON.stringify works. NextJS is complaining because they deliberately do not support magic serialization (which is a good thing). The issue at hand is not about supporting these libs, we are not against supporting them, nor do we raise our noses at their implementation when we suggest effect schema over and over.
The reason bi-directionality is relevant, critical even, is because serializing is just the first half of the job. Once the payload reaches its destination, you need to deserialize it back to something usable. Example:

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 🎉
Open then page and bam, can't invoke pipe because it's undefined.
Typescript won't know, and neither will you, what the actual object holds. But with bidirectional schemas you can encode and then decode. Meaning you turn that Exit into some arbitrary json-representable structure and on the other side you have something that can turn json of a very specific pattern into the real deal again.

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

I hope it's clear now why Exit is "serializable" but also why it is not serializable in a more relevant way. Encode/Decode are very much not a type transformations. You can see it in your IDE as well, just do go-to-definition

// 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.
We have tried to push a bi-directional standard as part of StandardSchema so we could integrate better with tools in the wild, but it encountered pushbacks. We could, and you could, define these data structures in other validation libs, but theres not much value in that.. I suggest you either return encoded data and decode it back on the FE, or use primitive values instead...

Maybe effect has its own functions to handle form-data?

It does, but as part of @effect/platform. That said, zod-form-data is not doing anything especially hard, its like one step above doing schema.parse(Object.fromEntries([...formData.entries()])), it's not difficult to implement its parsing api. The form api is supposedly more interesting but you're using RHF for that anyways

@MrOxMasTer
Copy link
Author

This is true (though usually not to this extent) with other data structures in effect. We have tried to push a bi-directional standard as part of StandardSchema so we could integrate better with tools in the wild, but it encountered pushbacks. We could, and you could, define these data structures in other validation libs, but theres not much value in that.. I suggest you either return encoded data and decode it back on the FE, or use primitive values instead...

Wow. All I wanted was to return an object - which I can cache (apparently only possibly serialized) and return it to another Effect, so that I don't lose the benefit of the effect in error handling, while preserving the types of errors returned, but I found out that it turns out there is a serialization that is not a full-fledged serialization. Terrible, but thanks for explaining it. It became a little clearer, but my head is exploding from the flow of information.

Primitive doesn't suit me as a regular object, as I would like to keep the advantage of Effect typing in error detection (because that's the main literal reason why I started using Effect)
And as I understand it - in any other case than Exit and decode/encode nothing else suits me as an object from the ones offered in Effect

The problem is that again my orm doesn't support your schemas yet. With form-Data something else can be solved, but manually recreate all types - it's masochism. And yet - I created at least for this root a user type and a schema for Exit - but I still got an error, what I encoded - what I decoded - it didn't help me. I may not have figured out how to fully work with this encoding/decoding, but I'll try again.

If you can, please give me an example of how it should look like for me to return the encoded or decoded value, because I'm confused on this point. In which case it decodes to Exit when it encodes to Exit:
I understand I have it returning to Exit, so I need to use the schema S.ExitFormSelf, but the question is what do I do with the errors that are returned from Effect to Exit. How do I specify them in the schema if it complains to me about type mismatch:

const UserSchema = S.Struct({
  id: S.String,
  image: S.String.pipe(S.NullOr),
  name: S.String.pipe(S.NullOr),
  email: S.String,
  emailVerified: S.Date.pipe(S.NullOr),
  password: S.String,
  createAt: S.Date.pipe(S.NullOr),
  updateAt: S.Date.pipe(S.NullOr),
});

const UserExit = S.ExitFromSelf({
  failure: S.String,
  success: UserSchema,
  defect: S.String,
});

type Encoded = typeof UserExit.Encoded;

const decode = S.decodeUnknownPromise(UserExit);
const encode = S.encodePromise(UserExit);
...
getByEmail: publicProcedure
    .input(emailSchema)
    .query(async ({ input: email }) => {
      const res = await 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);

      const encoded = await encode(res); // ERROR TYPE

      return res;
    }),

image

And can you please give me an example, how without user schema I can save the return type of the user, but at the same time decode/encode the value, so that it would be serialized/cached, while the drizzle-effect library is under development (As far as I know, drizzle is waiting for an answer from Effect.). The library is really complicated in many aspects, that's why I'm asking for examples, because this thing is not clear:
image

How, when writing like this, who goes where in its place (decoded value or encoded value):

const UserSchema = S.Struct({
  id: S.String,
  image: S.String.pipe(S.NullOr),
  name: S.String.pipe(S.NullOr),
  email: S.String,
  emailVerified: S.Date.pipe(S.NullOr),
  password: S.String,
  createAt: S.Date.pipe(S.NullOr),
  updateAt: S.Date.pipe(S.NullOr),
});

const UserExit = S.ExitFromSelf({
  failure: S.String,
  success: UserSchema,
  defect: S.String,
});

type Encoded = typeof UserExit.Encoded;

const decode = S.decodeUnknownPromise(UserExit);
const encode = S.encodePromise(UserExit);

@mikearnaldi
Copy link
Member

This is true (though usually not to this extent) with other data structures in effect. We have tried to push a bi-directional standard as part of StandardSchema so we could integrate better with tools in the wild, but it encountered pushbacks. We could, and you could, define these data structures in other validation libs, but theres not much value in that.. I suggest you either return encoded data and decode it back on the FE, or use primitive values instead...

Wow. All I wanted was to return an object - which I can cache (apparently only possibly serialized) and return it to another Effect, so that I don't lose the benefit of the effect in error handling, while preserving the types of errors returned, but I found out that it turns out there is a serialization that is not a full-fledged serialization. Terrible, but thanks for explaining it. It became a little clearer, but my head is exploding from the flow of information.

Primitive doesn't suit me as a regular object, as I would like to keep the advantage of Effect typing in error detection (because that's the main literal reason why I started using Effect) And as I understand it - in any other case than Exit and decode/encode nothing else suits me as an object from the ones offered in Effect

The problem is that again my orm doesn't support your schemas yet. With form-Data something else can be solved, but manually recreate all types - it's masochism. And yet - I created at least for this root a user type and a schema for Exit - but I still got an error, what I encoded - what I decoded - it didn't help me. I may not have figured out how to fully work with this encoding/decoding, but I'll try again.

If you can, please give me an example of how it should look like for me to return the encoded or decoded value, because I'm confused on this point. In which case it decodes to Exit when it encodes to Exit: I understand I have it returning to Exit, so I need to use the schema S.ExitFormSelf, but the question is what do I do with the errors that are returned from Effect to Exit. How do I specify them in the schema if it complains to me about type mismatch:

const UserSchema = S.Struct({
  id: S.String,
  image: S.String.pipe(S.NullOr),
  name: S.String.pipe(S.NullOr),
  email: S.String,
  emailVerified: S.Date.pipe(S.NullOr),
  password: S.String,
  createAt: S.Date.pipe(S.NullOr),
  updateAt: S.Date.pipe(S.NullOr),
});

const UserExit = S.ExitFromSelf({
  failure: S.String,
  success: UserSchema,
  defect: S.String,
});

type Encoded = typeof UserExit.Encoded;

const decode = S.decodeUnknownPromise(UserExit);
const encode = S.encodePromise(UserExit);
...
getByEmail: publicProcedure
    .input(emailSchema)
    .query(async ({ input: email }) => {
      const res = await 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);

      const encoded = await encode(res); // ERROR TYPE

      return res;
    }),

image

And can you please give me an example, how without user schema I can save the return type of the user, but at the same time decode/encode the value, so that it would be serialized/cached, while the drizzle-effect library is under development (As far as I know, drizzle is waiting for an answer from Effect.). The library is really complicated in many aspects, that's why I'm asking for examples, because this thing is not clear: image

How, when writing like this, who goes where in its place (decoded value or encoded value):

const UserSchema = S.Struct({
  id: S.String,
  image: S.String.pipe(S.NullOr),
  name: S.String.pipe(S.NullOr),
  email: S.String,
  emailVerified: S.Date.pipe(S.NullOr),
  password: S.String,
  createAt: S.Date.pipe(S.NullOr),
  updateAt: S.Date.pipe(S.NullOr),
});

const UserExit = S.ExitFromSelf({
  failure: S.String,
  success: UserSchema,
  defect: S.String,
});

type Encoded = typeof UserExit.Encoded;

const decode = S.decodeUnknownPromise(UserExit);
const encode = S.encodePromise(UserExit);

not aware of any pending question on our side for drizzle, pls link the issue if you see one

@MrOxMasTer
Copy link
Author

not aware of any pending question on our side for drizzle, pls link the issue if you see one

Maybe it's just me, but it's kind of like this:
drizzle-team/drizzle-orm#2665 (comment)

@MrOxMasTer
Copy link
Author

MrOxMasTer commented Nov 27, 2024

This is true (though usually not to this extent) with other data structures in effect. We have tried to push a bi-directional standard as part of StandardSchema so we could integrate better with tools in the wild, but it encountered pushbacks. We could, and you could, define these data structures in other validation libs, but theres not much value in that.. I suggest you either return encoded data and decode it back on the FE, or use primitive values instead...

So I made this thing:

const UserSchema = S.Struct({
  id: S.String,
  image: S.String.pipe(S.NullOr),
  name: S.String.pipe(S.NullOr),
  email: S.String,
  emailVerified: S.Date.pipe(S.NullOr),
  password: S.String,
  createAt: S.Date.pipe(S.NullOr),
  updateAt: S.Date.pipe(S.NullOr),
});

export const UserExit = S.Exit({
  failure: S.Union(
    S.instanceOf(UnknownDatabaseError),
    S.instanceOf(UserNotFoundError),
  ),
  success: UserSchema,
  defect: S.String,
});

const encode = S.encodePromise(UserExit);

and when encoding:

const encoded = await encode(res);

Appeared serializable pretty much and even removed the _tag: Exit that was stressing me out:
image

The only thing that is not very convenient is not understanding - what you have is Encoded value and what is Decoded, when writing like this:

S.Exit(S.Strust({}))

And then I had to pick up what I needed to decode/encode and .Exit/.ExitFromSelf but I found this combination and I know that it is possible to output the Encoded type and view it, but this does not give me an understanding of what exactly I needed to use anyway:
image

And for some reason, the encoded value does not have value, although in practice it does:
image

@MrOxMasTer
Copy link
Author

It does, but as part of @effect/platform. That said, zod-form-data is not doing anything especially hard, its like one step above doing schema.parse(Object.fromEntries([...formData.entries()])), it's not difficult to implement its parsing api. The form api is supposedly more interesting but you're using RHF for that anyways

Also. Yes, I don't dispute that you can get fields from formData in this way, but the problem is that there will probably be types that don't exist in Effect schemes yet, for example file types. Yes - it can be done with S.instanceOf, but I think it's better to build it into the library, it won't be superfluous. Here is a possible list of fields that are not in the effect scheme:
image

I've already made a `issue' about it: #3986

@datner
Copy link
Member

datner commented Dec 1, 2024

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:

            ┌─Your─App───┐            
            │            │            
            │            │            
--Encoded-->│--Decoded-->│--Encoded-->
            │            │            
            │            │            
            └────────────┘            

so a message arrives, it is serialized-- it is encoded into some json-compatible shape. { "_tag": "Success", "value": ... }
But you need an Exit<Entity, Error>, not some random json. Luckily, you have the decoder that can take that shape and parse it all the way from a random unknown shape into a bonafide Exit<Entity, Error>. You do what work is required on that datum, and now it's time to send the message forward. But there's an issue, the object you have is impossible to serialize, it has functions and instances and all sorts of not-json stuff. You could serialize it magically like with superjson but you want your messages to be as compact as possible and not any more compact. These magic serializers must send a bloated body rife with metadata and raw code to rehydrate your structure since they can't make any assumptions. Thankfully, a schema describes the relationships bi-directionally. The random json you got was not really all that random, it was the result of encoding a real Exit<Entity, Error> into a form that the same schema can 'rehydrate' or enrich or what-have-you back into the original shape. Not only that, because you define the schema explicitly you can add or omit as much information you want, leaving you with only what is required to retain enough information to rehydrate from.
So, to recap. You get encoded data, decode it, do whatever you want to it, encode it and send it out. You can cache the encoded version and just decode it when you fetch it from the cache.

types that don't exist in Effect schemes yet, for example file types.

But they do. Theres a whole module for working with multipart forms and file payloads, but as I said, they are part of @effect/platform. As the name suggests, that package is for platform-related stuff as opposed to general programming like effect. So that would include working with filesystems, HTTP server, HTTP client, file uploads/downloads, command execution, cookies, etags, headers, body parsng, routing templating etc etc

@MrOxMasTer
Copy link
Author

But they do. Theres a whole module for working with multipart forms and file payloads, but as I said, they are part of @effect/platform. As the name suggests, that package is for platform-related stuff as opposed to general programming like effect. So that would include working with filesystems, HTTP server, HTTP client, file uploads/downloads, command execution, cookies, etags, headers, body parsng, routing templating etc etc

You have really good answers - for that I want to say thank you. I would be glad if you could answer my last 2 issue that I have left

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on working as intended
Projects
None yet
Development

No branches or pull requests

5 participants