morphism icon indicating copy to clipboard operation
morphism copied to clipboard

Async morphism

Open SamuelColacchia opened this issue 5 years ago • 7 comments

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": "[email protected]",
  "group": "Promise { <pending> }"
}
// After resolvePromises
{
  "sourceCustomerId": 39392,
  "firstname": "somefirstname",
  "lastname": "somelastname",
  "email": "[email protected]",
  "group": "somegroup"
}

Additional context Ideal implementation

const morphed = morphism(customerSchema, customer.data)

Current result, with unresolved promise

{
"firstname": "somefirstname",
"lastname": "somelastname",
"email": "[email protected]",
"group": "Promise { <pending> }"
}

Ideal result, with resolved promise

{
"firstname": "somefirstname",
"lastname": "somelastname",
"email": "[email protected]",
"group": "somegroup"
}

SamuelColacchia avatar Jul 25 '19 19:07 SamuelColacchia

@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": "[email protected]",
//   "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 ?

emyann avatar Jul 26 '19 23:07 emyann

@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.

SamuelColacchia avatar Jul 27 '19 11:07 SamuelColacchia

@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 :)

emyann avatar Jul 30 '19 17:07 emyann

@emyann Sounds good to me.

SamuelColacchia avatar Jul 30 '19 22:07 SamuelColacchia

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

kirsar avatar May 07 '20 17:05 kirsar

@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 🙂

emyann avatar May 07 '20 18:05 emyann

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

kirsar avatar May 07 '20 20:05 kirsar