nobrainr / morphism

⚡ Type-safe data transformer for JavaScript, TypeScript & Node.js.
https://morphism-playground.now.sh/
MIT License
486 stars 23 forks source link

Async morphism #106

Open SamuelColacchia opened 5 years ago

SamuelColacchia commented 5 years ago

Is your feature request related to a problem? Please describe. Morphism is unable to resolve promises in ActionFunction and ActionSelector and Type Promise is not allowed.

Describe the solution you'd like Allow ActionFunction and ActionSelector to return a Promise that would later be resolvable by say a morphismasync method.

Example Schema

// Schema type removed to allow for group to be of type promise
const customerSchema = {
  firstname: "firstname",
  lastname: "lastname",
  email: "email",
  group: {
    path: "group",
    fn: async value => {
      const res = await callSomeService(value);
      return res.data;
    }
  }
};

Describe alternatives you've considered Alternative solutions would be to map the data available with one morphism call then make any async requests desired then remap the data returned from those async requests.

Another solution would be to add a helper function like so.

async function resolvePromises(objectFromMorph) {
  for (const key of Object.keys(objectFromMorph)) {
    const value = objectFromMorph[key];
    if (Promise.resolve(value) == value) {
      console.log("Found Promise", value);
      objectFromMorph[key] = await value;
    }
  }
  return objectFromMorph;
}
const morphed = morphism(customerSchema, customer.data);
const customer = resolvePromises(morphed);
{
  "sourceCustomerId": 39392,
  "firstname": "somefirstname",
  "lastname": "somelastname",
  "email": "mail@mail.com",
  "group": "Promise { <pending> }"
}
// After resolvePromises
{
  "sourceCustomerId": 39392,
  "firstname": "somefirstname",
  "lastname": "somelastname",
  "email": "mail@mail.com",
  "group": "somegroup"
}

Additional context Ideal implementation

const morphed = morphism(customerSchema, customer.data)

Current result, with unresolved promise

{
"firstname": "somefirstname",
"lastname": "somelastname",
"email": "mail@mail.com",
"group": "Promise { <pending> }"
}

Ideal result, with resolved promise

{
"firstname": "somefirstname",
"lastname": "somelastname",
"email": "mail@mail.com",
"group": "somegroup"
}
emyann commented 5 years ago

@SamuelColacchia Thank you for using Morphism and bringing this feature on the table. I was thinking about having an async interface on Morphism for several purposes (parallelization, async transformations...). It might be a good start.

My first thought was to have an .async property available on morphism like:

const customerSchema = {
  firstname: "firstname",
  lastname: "lastname",
  email: "email",
  group: {
    path: "group",
    fn: async value => {
      const res = await callSomeService(value);
      return res.data;
    }
  }
};

const result = await morphism.async(customerSchema, input)
// ==>
// {
//   "firstname": "somefirstname",
//   "lastname": "somelastname",
//   "email": "mail@mail.com",
//   "group": "somegroup"
// }

Mostly to keep the backward compatibility on the synchronous interface and provide the appropriate typing with TypeScript.

What do you think about this implementation ?

SamuelColacchia commented 5 years ago

@emyann That seems like it would be a good solution to the problem. Thinking it over in my head it would require the caller of morphism to explicitly want to wait for a async request, which I think is a good approach.

emyann commented 5 years ago

@SamuelColacchia Awesome! Thank you for your feedback, I'm going to schedule this feature on the next branch as a beta feature. I'll get back to you for testing purposes if you're ok with it :)

SamuelColacchia commented 5 years ago

@emyann Sounds good to me.

kirsar commented 4 years ago

@emyann Hi, thanks for the library, it's nice and compact! How does the feature live? :)

emyann commented 4 years ago

@kirsar Thank you for the feedback! I'm actively working on a big chunk of the library which is Data Validation that should land soon on the beta branch (https://github.com/nobrainr/morphism/releases)

Working on that make me realize that I needed to support async validations also, so I'm ideating this async part of morphism but I do want to have a clear separation between side effects and the actions of transforming the data itself, that in my opinion should stay as pure as possible.

I'll likely come up by the end of this month with a design on how I envision this in Morphism, and in the meantime I would love to hear about your use-case with that feature if you're interested 🙂

kirsar commented 4 years ago

Thanks for reply. My case is very simple: I need to talk with 3rd party via some amqp and use morphism to map 'my' entities to 'their' commands / events. And I want to separate mapping config (schema) away from other complexity, but sometimes I need to make async calls to db to reconstruct entity from event (3rd party is not under my control), like:

{
    id: 'id',
    stateId: event => (await stateRepo.getByCode(event.state)).id,
}