How can I call around in my api? (solved)

If I have multiple services in my backend, how can I call one from the other?

For contrived purposes let’s say I have two things: api/src/services/oneThing & api/src/services/twoThing

can I import createTwoThing into createOneThing and call it?

import { createTwoThing } from 'src/services/twoThing/twoThing

export const createOneThing = ({ input }) => async {
   const newTwoThing = await createTwoThing(data)
   return await db.twoThing.create({
      data: {
         twoThingId: newTwoThing.id
     }
}

that is to say, how can I import createTwoThing into createOneThing and call it?

I get an error:

api | TypeError: Cannot read property 'Symbol(pino.nestedKey)' of undefined

Thanks

Al;

Hi @ajoslin103 !

I think you have more than one thing that cause the error, but let’s start by createTwoThing inside createOneThing.

You don’t need to import another service to do that, you can do it with something like this :

export const createOneThing = ({ input }) => async {
   return await db.oneThing.create({
      data: {
         twoThing: {
          create: data,
       }
     }

Finally, the error you see seem to be related with a misconfiguration of Logger who use Pino internally.

Tell me if I had understood correctly your problem :slight_smile:

1 Like

@simoncrypta Nice !! I’ll check on that this evening – but it looks Spot On ™

Thanks!!

Al;

1 Like

@ajoslin103 Can you share your db.js does it look like:

// See https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/constructor
// for options.

import { PrismaClient } from '@prisma/client'

import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger'

import { logger } from './logger'

/*
 * Instance of the Prisma Client
 */
export const db = new PrismaClient({
  log: emitLogLevels(['info', 'warn', 'error']),
})

handlePrismaLogging({
  db,
  logger,
  logLevels: ['info', 'warn', 'error'],
})

Have you tried the connectOrCreate ?

https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#connectorcreate

This will save two Prisma calls and either link or create the second item to the first.

const user = await prisma.post.create({
  data: {
    title: 'Hello World',
    author: {
      connectOrCreate: {
        where: { email: 'alice@prisma.io' },
        create: { email: 'alice@prisma.io' },
      },
    },
  },
})

So newTwoThing is your author here and post is your twoThing.

Just create twoThing and connect or create a newTwoThing.

And all in the same transaction.

2 Likes

@simoncrypta @dthyresson both such good answers - can’t mark one over the other as the one solution!

// See https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/constructor
// for options.

import { PrismaClient } from '@prisma/client'

import { emitLogLevels, handlePrismaLogging } from '@redwoodjs/api/logger'

import { logger } from './logger'

/*
 * Instance of the Prisma Client
 */
export const db = new PrismaClient({
  log: emitLogLevels(['info', 'warn', 'error']),
})

handlePrismaLogging({
  db,
  logger,
  logLevels: ['info', 'warn', 'error'],
})
import type { Prisma } from "@prisma/client";
import type { ResolverArgs, BeforeResolverSpecType } from "@redwoodjs/api";

const stringify = require('json-stringify-safe');
import { context } from '@redwoodjs/api'
import { logger } from 'src/lib/logger'
import { thenDebug } from 'src/lib/thenables'

import { db } from "src/lib/db";
import { requireAuth } from "src/lib/auth";

// Used when the environment variable REDWOOD_SECURE_SERVICES=1
export const beforeResolver = (rules: BeforeResolverSpecType) => {
  rules.add(requireAuth);
};

export const user = () => {
  if (context?.currentUser?.sub) {
    return db.user
      .findUnique({
        where: { id: context?.currentUser?.sub  }
      })
      .then(thenDebug('user'))

  } else {
    return Promise.resolve(null)
  }
};

export const User = {
  customer: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).customer(),
  exployee: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).business(),
  owner: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).owner(),
  purchaser: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).purchases(),
  attendees: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).attendees(),
  events: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).events(),
};
const stringify = require('json-stringify-safe');

import { logger } from './logger'

export const thenDebug = (label) => (data) => {
  logger.debug(`${label}: ${stringify(data, null, 2)}`)
  return data
}

so this worked 99%

    const me = await db.user.findUnique({
      where: { id: context?.currentUser?.sub }
    })

    return await db.event.create({
      data: {
        id: newEventId,
        plannerId: me.id,
        businessId: me.businessId,
        imageStore: {
          create: {
            id: nanoid(),
            s3Bucket: newS3Bucket
          }
        }
      }
    })
    .then(thenDebug(`db.event.create`))
    .catch(logger.error)

The event was created, properly linked to me & vice-versa. The imageStore was created, including it’s linkage back to the event.

The linkage to the event to the imageStore was not added, not sure why – prisma is happy w/the declarations…

And when I try:

    return await db.event.create({
      data: {
        id: newEventId,
        plannerId: me.id,
        businessId: me.businessId,
        imageStore: {
          connectOrCreate: {
            id: newImageStoreId,
            s3Bucket: newS3Bucket
          }
        }
      },
    })

I get TS warning :

Type '{ id: string; s3Bucket: string; }' is not assignable to type 'ImageStoreCreateOrConnectWithoutEventInput'. Object literal may only specify known properties, and 'id' does not exist in type 'ImageStoreCreateOrConnectWithoutEventInput'.

drilling into it takes me to index.d.ts which I don’t expect to have to modify…

I had added id to the CreateImageStoreInput in imageStore.ts but that wasn’t enough…

@ajoslin103 I have seen you use the above code pattern often and I am curious about what you’d like logged here.

I ask because am adding better logging support at the GraphQL execution level to do debug/info logs when an operation (query/mutation) occurs and also any errors raised.

I’m wondering with these additions if that pattern would be needed – or if it will suffice to log that info. Of course, this would not be logged if you used the service from a serverless function ie - not via GraphQL.

Do you debug log the entire object here? then(thenDebug('user')) ?

Also, is there a reason you do:

return Promise.resolve(null)

and not make user() async and then use await on return db.user()? Or is that done explicitly so you can wrap that then debug around (and if so maybe GraphQL logging can clean that up).

Hope to have that in for the next release and can revisit.

1 Like

@dthyresson thank you for inquiring. I use thenDebug to log the entire object at any point in any given .then chain. I like it because I can move it up and down the chain as needed.

It has a few shortcomings which could likely be solved if it were written into a logger.

  1. it has no awareness of logLevel - I have to use thenInfo & thenWarn to use different levels
  2. It has no redaction capabilities - something which I like about your logger
  3. It fails when I try to log circular objects, unless I import safe-stringify code
export const thenDebug = (label) => (data) => {
  logger.debug(`${label}: ${stringify(data, null, 2)}`)
  return data
}

While I truly enjoy both async/await and promises I can’t always figure out how to translate between the two. If I really understand all the things a given bit of code is trying to do I may convert a Promise-based function to an async function if it’s going to make it easier to reason about, but I usually don’t do that where it’s going to create a question for me later. If I enter the room and all the functions are using Promises I am unlikely to switch one signature to async/await unless I’m feeling confident I could change them all.

In this case I return Promise.resolve(null) because the routine is already returning Promises. Most times I am trying to make sense of a suite of functions, and making minimal changes to existing code helps more than rewriting it and [probably] introducing more uncertainty into a situation I don’t fully understand to begin with.

I wish I was a better at reading and understanding code, and that I had better habits of isolating and understanding code before I used it [usually in haste.]

Thanks much for that info @ajoslin103 – I think we can make this a bit easier, cleaner in some updating GraphQL updates. Stay tuned!

In short - there could be a few options to configure logging in GraphQL - to include operationName, requestId, query, trace timings, and even some output that could replace what you are doing in services.

1 Like

You are welcome, I’m sure it will be neat and cool.

I’m still looking into a Mutating Cell – but it’s taking a backseat to getting my latest efforts online