sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
5.08k stars 161 forks source link

Support clone for custom unsafe types #1040

Closed rishavjain closed 3 weeks ago

rishavjain commented 1 month ago

Hi,

I have created a Transform type for Mongo ObjectId with the base type as Unsafe as follows -

Type.Transform(
  Type.Unsafe<ObjectId>({[Kind]: 'CustomObjectId'})
)
  .Decode(value => value.toString())
  .Encode(value => new ObjectId(value))

Also, registered 'CustomObjectId' in TypeRegistry.

This setup has worked fine for most of the cases, however, I have encountered an issue - When using Value.Clean on Intersect schema which consists of CustomObjectId fields, it gives the following error - ValueClone: Unable to clone value

Upon looking into this problem, I discovered that the implementation of Clean internally uses Clone for Intersection type, and Clone does not support custom unsafe type.

sinclairzx81 commented 3 weeks ago

@rishavjain Hi, sorry for the delay in reply.

So, this issue is mostly to do with supporting clone for additional data structures (in this case ObjectId). Unfortunately this isn't possible as some JS data structures can't be cloned safely. An example of this would be class instances with private variables that are inaccessible (which may or may not be the case for ObjectId)

TypeBox currently supports a few clone operations that work above and beyond JavaScripts structuredClone() (Date and Uint8Array specifically) but this is only possible because both these types are standard in JS environments and both have known interfaces that are conducive to cloning. This isn't the case for unknown types (like Mongo ObjectId) because the interface is unknown to TypeBox and there is no assurances performing a naive clone would yield a complete object (in the case of private variables). The ValueClone: Unable to clone value error is mostly an indication that the value cannot be cloned safely rather than an omission of functionality.

A possible solution to this might be for JS to standardize a way that a class instance / value can be safely cloned. Something like the following.

class Foo {
  [Symbol.clone]: () {
    return new Foo()
  }
}

const foo = Foo()

const clone1 = foo[Symbol.clone]()

const clone2 = structuredClone(foo) // calls [Symbol.clone] if exists

Unfortunately nothing like this exists today (afaik), but this would largely solve the issue (where TypeBox could just trust the type knows how to clone itself should the symbol exist). Maybe one day.


Will close up this issue for now as this issue is non-actionable, but if you have any follow up questions, feel free to ping on this thread. Again, sorry for the delay.

Cheers! S