-
-
Notifications
You must be signed in to change notification settings - Fork 791
Schema definition circular references #3473
-
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!
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 2 comments 11 replies
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
-
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 } )
Beta Was this translation helpful? Give feedback.
All reactions
-
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 }) }) })
Beta Was this translation helpful? Give feedback.
All reactions
-
Hi @daffl
Unfortunately that didn't resolve it, it still endlessly loops that property resolver.
Bug?
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
Could be a bug but it'd be easier to verify with a minimal example to reproduce.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.