Giter VIP home page Giter VIP logo

Comments (7)

sinclairzx81 avatar sinclairzx81 commented on July 20, 2024 1

@sdc395 Hi,

Would support for definitions solve this? Is there a workaround that would allow me to export a single schema containing references to recursive types from a package?

Not necessarily, but let me explain.

Many versions ago, TypeBox included a Type.Namespace type that produced a $defs schematic. This type was removed however as the construction of $defs varied widely among json schema users, and anything TypeBox introduced was too narrow to meet ALL end user requirements. Many users were experiencing issues trying to port $defs schematics to Type.Namespace and finding out the referencing paths, and structures did not fit the type.

As such, a call was made to remove Type.Namespace in the short term which forced users to build out their own $defs and wrestle with Type.Ref, String Ref, and out of order definitions (things that TypeBox would have otherwise had to wrestle with internally). This decision was mostly prompted due to the uncertainty on how to appropriately handle $defs (with many of your questions here and recently noted on #882 being pressing questions internal to the library also)

All this said, I am planning to reintroduce Type.Namespace. As before, this Type would generate $defs schematics, however the type would be documented as being "extremely opinionated" and "specific to TypeBox". This following is the high level idea.

// Model generates referential $id based on property name
const Model = Type.Namespace({ 
  Name: Type.String(),
  Person: Type.Object({
    name: Type.Ref('Name') // reference the Name type
  })
})

// Infer This Way
type Model = Static<typeof Model>
type Name = Model['Name']
type Person = Model['Person']

// Get Schematics This Way
const Name = Type.Index(Model, 'Name')
const Person = Type.Index(Model, 'Person')

// Or this way
const Name = Model.$defs.Name
const Person = Model.$defs.Person 

Keep in mind, the above probably wouldn't solve the json-schema-to-typescript issue noted (as for the above to work, TypeBox is heavily reliant on internal tracked identifiers, and linear / flat structure of the $defs, and cannot predict in advance what exterior tools will generate). But there will be something to map into if you're prepared to follow TB conventions.

Hope this brings some insight
Cheers
S

from typebox.

sdc395 avatar sdc395 commented on July 20, 2024

I've just realised that the involvement of a recursive type was an assumption of mine. The following causes the same issue...

import { Type } from '@sinclair/typebox';
import { TypeCompiler } from '@sinclair/typebox/compiler';

const Vector = Type.Object({
    x: Type.Number(),
    y: Type.Number(),
}, { $id: 'Vector' });

const VectorRef = Type.Ref(Vector);

TypeCompiler.Compile(VectorRef);

Does TypeCompiler not support references?

from typebox.

sdc395 avatar sdc395 commented on July 20, 2024

OK, so I had assumed that TypeCompiler had magical access to the referenced type. I'd somehow failed to notice the references optional argument to TypeCompiler.compile. Providing the Vector schema to TypeCompiler.Compile(VectorRef) fixes my error.

That said, there still seems to be an issue with recursive types.

import { Type } from '@sinclair/typebox';
import { TypeCompiler } from '@sinclair/typebox/compiler';

const Vector = Type.Object({
    x: Type.Number(),
    y: Type.Number(),
}, { $id: 'Vector' })

const VectorRef = Type.Ref(Vector)
const VectorDeref = Type.Deref(VectorRef, [Vector]);

// OK
TypeCompiler.Compile(VectorDeref);

const Node = Type.Recursive(Node => Type.Object({
    id: Type.String(),
    nodes: Type.Array(Node),
}, { $id: 'Node' }));

const NodeRef = Type.Ref(Node);
const NodeDeref = Type.Deref(NodeRef, [Node]);

// TypeDereferenceError: Unable to dereference schema with $id 'undefined'
TypeCompiler.Compile(NodeDeref);

from typebox.

sinclairzx81 avatar sinclairzx81 commented on July 20, 2024

@sdc395 Hi,

Please ignore this non-issue. I had assumed that TypeCompiler had magical access to the referenced type. My bad.

That's right, Ref types are string referenced aliases to some target type. To validate a type containing a reference type, you will need to inform the compiler about the target type so it can be dereferenced. There's a couple of way to achieve this. Consider for the provided Vector type.

const Vector = Type.Object({
    x: Type.Number(),
    y: Type.Number(),
}, { $id: 'Vector' })

const VectorRef = Type.Ref(Vector)

Option A: Pass references array to Compile

Most TypeBox functions have an overloaded function to pass a references array. The types passed on the array should all have $id identifiers such that the Ref can target it.

TypeCompiler.Compile(VectorRef, [Vector])

// similarly you can pass references to Value.Check

Value.Check(VectorRef, [Vector], { x: 1, y: 2 })

Option B: Defer target type

In 0.32.0, TypeBox added the Deref type which will return a de-normalized type. The following uses Deref to deference the target type meaning you don't need to pass the references array on Compile.

TypeCompiler.Compile(Type.Deref(VectorRef, [Vector]))

Auto Tracking References

In both cases above, you will need to carry around the reference + target types, however there are abstractions you can write over the top of Compile to auto track $id / $ref. The following is a quick implementation of this.

import { Type, TSchema } from '@sinclair/typebox'
import { TypeCompiler, TypeCheck } from '@sinclair/typebox/compiler'

// ------------------------------------------------------------------
// Auto Tracking References
// ------------------------------------------------------------------

const references: TSchema[] = []
let ordinal = 0

// wraps a type and ensures it has an $id. The type is added to the references array.
function Target<T extends TSchema>(schema: T): T {
  if('$id' in schema) {
    references.push(schema)
    return schema
  } else {
    const remapped = { $id: `type-${ordinal++}`, ...schema }
    references.push(remapped)
    return remapped
  }
}
// custom compile method
function Compile<T extends TSchema>(schema: T): TypeCheck<T> {
  return TypeCompiler.Compile(schema, references)
}

// ------------------------------------------------------------------
// Example
// ------------------------------------------------------------------

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

const VectorRef = Type.Ref(Vector)

Compile(VectorRef)

Hope this helps
S

from typebox.

sdc395 avatar sdc395 commented on July 20, 2024

Hi @sinclairzx81

Thank you for your response to my confusion. I'll read through it and try to get a grip. :-)

from typebox.

sinclairzx81 avatar sinclairzx81 commented on July 20, 2024

@sdc395 Hey

Will close off this issue as you will need to pass references arrays on various functions if using Ref. TypeBox will deference types if you supply the references array on various functions, however tracking references is something that must be handled by the implementer (as there's a few ways to approach it). You can use the "Auto Tracking References" above as a starter.

Cheers!
S

from typebox.

sdc395 avatar sdc395 commented on July 20, 2024

With regard to my "Vector versus Node" code above, I'm guessing the problem comes down to the way references are unpacked into duplicates of the referenced schema (an assumption based on the documentation of Type.Deref).

Would support for definitions solve this? Is there a workaround that would allow me to export a single schema containing references to recursive types from a package?

from typebox.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.