sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.65k stars 150 forks source link

Clean call on extended prisma result object does not work #842

Closed m1212e closed 3 months ago

m1212e commented 3 months ago

Hi,

I use a prisma extension to augment the returned objects with the model name:

const brandExtension = Prisma.defineExtension((client) => {
  type ModelKey = Exclude<keyof typeof client, `$${string}` | symbol>;
  type Result = {
    [K in ModelKey]: {
      $kind: {
        needs: Record<string, never>;
        compute: () => Capitalize<K>;
      };
    };
  };

  const result = {} as Result;
  const modelKeys = Object.keys(client).filter(
    (key) => !key.startsWith("$"),
  ) as ModelKey[];

  for (const k of modelKeys) {
    const capK = k.charAt(0).toUpperCase() + k.slice(1);
    result[k] = {
      // biome-ignore lint/suspicious/noExplicitAny:
      $kind: { needs: {}, compute: () => capK as any },
    };
  }

  return client.$extends({ result });
});

export const db = new PrismaClient({
  datasourceUrl: appConfiguration.db.postgresUrl,
})
.$extends(brandExtension);

This results in fields on the returned objects:

const r = await db.conference.findMany();
r[0].$kind; // "Conference"

Now I have a schema:

const ConferencePlain = t.Object(
  {
    id: t.String(),
    name: t.String(),
  },
  { additionalProperties: false },
);

And run a Clean on the db return value:

      console.log(r);
      Value.Clean(t.Array(ConferencePlain), r);
      console.log(r);

For some reason the $kind field is not cleaned away. even though the schema does not allow it. Do you have any idea why this happens? Maybe this is related to the object being a class instance (console.log(r[0]!.constructor); // [class Object])? Thanks!

sinclairzx81 commented 3 months ago

@m1212e Hi,

TypeBox doesn't necessarily mind a class instance is passed to Clean, but it's generally better to pass plain object types. Also, it's important to keep in mind that the Clean function will mutate the value you pass it (so if you're passing prisma class instances, Clean may break that instance potentially making it unusable afterwards). To prevent mutation of the instance, you should Clone the prisma value first (prisma instances may support a .clone() function)

So, quick test code below shows the behavior when passing class instances. Things should be working as expected here.

import { Type, } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'

const T = Type.Object({
  x: Type.Number(),
  y: Type.Number()
})

class Foo {
  x: number = 1
  y: number = 2
  z: number = 3
  $kind: number = 4
}

// instances
const A = new Foo()
const B = Value.Clean(T, new Foo())
const C = Value.Clean(Type.Array(T), [new Foo()])

// results
console.log(A)       // Foo { x: 1, y: 2, z: 3, '$kind': 4 }
console.log(B)       // Foo { x: 1, y: 2 }
console.log(C)       // [ Foo { x: 1, y: 2 } ]

I don't see an immediate issue in TypeBox here, but perhaps the above repro might lend some insights. Let me know if this helps Cheers S

m1212e commented 3 months ago

I see the problem now. Thanks for the input, I'll try to work around that :D