prisma / extension-read-replicas

Prisma Client Extension to add read replica support to your Prisma Client
Apache License 2.0
96 stars 6 forks source link

Fluent API returns incorrect object structure with read replica #12

Open snake575 opened 9 months ago

snake575 commented 9 months ago

When using Prisma's Fluent API with a read replica, the returned object does not adhere to the expected structure.

Repro: read-replicas-demo

Current Behavior:

Using the primary database, the following code:

await prisma.$primary().post.findFirst().author()

returns:

{ id: 1, email: 'my@email.com', name: null }

However, when using a read replica, this code:

await prisma.post.findFirst().author() 

returns a nested object:

{ author: { id: 1, email: 'my@email.com', name: null } }

Expected Behavior

Using a read replica should also return:

{ id: 1, email: 'my@email.com', name: null }
casey-chow commented 9 months ago

This seems to be a deeper problem in client extensions, since I've encountered this when running my own client extensions.

zacharyliu commented 9 months ago

This issue seems to be specific to this extension needing to reconstruct a method call to the replica:

https://github.com/prisma/extension-read-replicas/blob/3c1a7127b4c80a3352893d8bea71c73898d8d391/src/extension.ts#L72-L76

The fluent API gets translated into operation: 'findFirst', args: { select: { author: true } }, so the reconstructed call has the result incorrectly nested. Normally, the query function seems to handle the work of lifting the nested data.

Within __internalParams, there appears to be a __internalParams.dataPath array which points to the nested path to extract, which maybe could be the solution but is a private API.

tbell511 commented 8 months ago

I am having the same exact issue.

This extension cannot be used on projects using the fluent API.

tbell511 commented 8 months ago

@SevInf do you know when this might get fixed? 🙂

SevInf commented 8 months ago

Sorry, can not provide ETA at the moment. Query extensions seem to have a problem with Fluent API in general and we probably should fix it in Prisma Client rather than work around in extension.

janpio commented 8 months ago

Do we have a higher level issue for this @SevInf that we can link to?

daniel-nagy commented 5 months ago

Tried adding a replica and ran into this same issue.

tbell511 commented 4 months ago

Are there any updates to this? This extension is still completely useable for applications that use the fluent API.

pedrovanzella commented 2 months ago

I'd love to see this fixed too. This is such a great extension, but we can't use it until it supports the fluent API.

jadenlemmon commented 2 months ago

We are using the fluent API and really want to see this land as well.

FWIW we slightly modified the $allOperations function similar to what @zacharyliu mentioned above that seems to work for our use case. Obviously not ideal.

async $allOperations({
  // @ts-expect-error __internalParams does not appear on the type as it's an internal property
  __internalParams: { dataPath, transaction },
  args,
  model,
  operation,
  query,
}) {
  if (transaction) return query(args);

  if (readOperations.includes(operation)) {
    const replica = replicaManager.pickReplica() as unknown as {
      [key: string]: {
        [key: string]: (args: unknown) => Promise<unknown>;
      };
    };

    if (
      typeof replica === "object" &&
      replica !== null &&
      model &&
      model in replica
    ) {
      const modelMethod = replica[model];

      if (modelMethod && operation in modelMethod) {
        const readMethod = modelMethod[operation];

        if (readMethod) {
          const res = (await readMethod(args)) as {
            [key: string]: unknown;
          };

          // This is a workaround for the Fluent API.
          // This references the internal dataPath to access the correct
          // nested property of the result.
          const path = dataPath as string[];

          if (path && path.length > 1) {
            const p = path[1];
            if (p && p in res) return res[p];
          }

          return res;
        }
      }
    }
  }

  return query(args);
}
casey-chow commented 1 week ago

I put up a PR trying to productionize @jadenlemmon, though of course I'm not able to eliminate the fundamental jankiness of using (more) internal APIs: https://github.com/prisma/extension-read-replicas/pull/36