Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Schema definition circular references #3473

Unanswered
EmileSpecs asked this question in Q&A
Discussion options

Hi

Please see sinclairzx81/typebox#844 for reference.

The solution offered unfortunately won't work in the context of feathers since none of the other functions like the TReference type produced for schema.

For example getValidator wants schema:
schema: TObject<TProperties> | TIntersect<TObject<TProperties>[]> | TUnion<TObject<TProperties>[]>

Is there any other suggested way of defining references to other schemas that reference each other that will work?

Reference to my definitions as mentioned in the post above:

// NOTE: these schemas are defined in different files and are displayed here as is for reference, thus I'm not getting errors like:
// Block-scoped variable 'accountSchema' used before its declaration.ts
const userSchema = Type.Object(
 {
 id: Type.String({ format: 'uuid' }),
 email: Type.String({ format: 'email' }),
 account: Type.Ref(accountSchema)
 },
 { $id: 'User', additionalProperties: false }
)
const accountSchema = Type.Object(
 {
 id: Type.String({ format: 'uuid' }),
 name: Type.String(),
 users: Type.Array(Type.Ref(userSchema)),
 locations: Type.Array(Type.Ref(locationSchema))
 },
 { $id: 'Account', additionalProperties: false }
)

The provided solution that does not solve the issue for me in feathers:

const userSchemaBuilder = <T extends TSchema>(reference: T) =>
 Type.Object({
 id: Type.String({ format: "uuid" }),
 email: Type.String({ format: "email" }),
 account: Type.Optional(reference),
 });
const accountSchemaBuilder = <T extends TSchema>(reference: T) =>
 Type.Object({
 id: Type.String({ format: "uuid" }),
 name: Type.String(),
 users: Type.Optional(Type.Array(reference)),
 });
const userSchema = Type.Recursive((This) =>
 userSchemaBuilder(accountSchemaBuilder(This))
);
const accountSchema = Type.Recursive((This) =>
 accountSchemaBuilder(userSchemaBuilder(This))
);
type User = Static<typeof userSchema>;
type Account = Static<typeof accountSchema>;

Would appreciate some help on how to make this work!

You must be logged in to vote

Replies: 2 comments 11 replies

Comment options

It seems to me like the most simple solution here is just to use JSON Schema instead, as it doesn't give any errors when doing the same circular relations.

Any opinions, as I know TypeBox is the preferred option?
Doesn't TypeBox just compile to JSON Schema anyway?

You must be logged in to vote
0 replies
Comment options

You can always fall back fall back to JSON schema directly with Type.Unsafe.

const userSchema = Type.Object(
 {
 id: Type.String({ format: 'uuid' }),
 email: Type.String({ format: 'email' }),
 account: Type.Ref(accountSchema)
 },
 { $id: 'User', additionalProperties: false }
)
const accountSchema = Type.Object(
 {
 id: Type.String({ format: 'uuid' }),
 name: Type.String(),
 users: Type.Array(Type.Unsafe<User>({ $ref: 'User' })),
 locations: Type.Array(Type.Ref(locationSchema))
 },
 { $id: 'Account', additionalProperties: false }
)
You must be logged in to vote
11 replies
Comment options

I thought I documented this but couldn't find it 🤔. There should be a guard against recursive resolving when passing params.resolve along:

export const userResolver = resolve<User, HookContext<UserService>>({
 // THIS RESOLVER EXECUTES IN ENDLESS LOOP
 account: virtual(async (user, context) => {
 // Populate the account field with the actual account data
 if (!user.account_id) {
 return
 }
 const { resolve } = context.params
 return await context.app.service('accounts').get(user.account_id, { resolve })
 })
})
Comment options

Hi @daffl

Unfortunately that didn't resolve it, it still endlessly loops that property resolver.
Bug?

Comment options

I also added the resolve parameter to the account schema side:

export const accountResolver = resolve<Account, HookContext<AccountService>>({
 users: virtual(async (account, context) => {
 // Populate the users field with the actual user data
 const { resolve } = context.params
 const users = await context.app.service('users').find({ query: { account_id: account.id }, paginate: false, resolve })
 return users?.length ? users : undefined
 })
})

That made no difference either.

Comment options

daffl May 2, 2024
Maintainer

Could be a bug but it'd be easier to verify with a minimal example to reproduce.

Comment options

Here you go!

https://github.com/EmileSpecs/f5-resolve

Get it set up and then just query users or accounts and watch the console go nuts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
2 participants
Converted from issue

This discussion was converted from issue #3472 on April 23, 2024 16:59.

AltStyle によって変換されたページ (->オリジナル) /