Feature(s) impacted
Typescript autogenerated Typings
Problem
I’m encountering several difficulties with TypeScript typing in Forest Admin. I’m using auto-generated types and trying to follow documentation best practices.
Maybe adding real examples with relationships, smart actions, etc. to the documentation could help.
The code is working without typing, the field owner is an autocomplete field and work, but not when I play with TS.
Issue
- Basic Typing Issues
- Auto-completion stops working with TypeScript while it works without
- Relations work without explicit typing but break when trying to add types
- Specific TypeScript Errors
- One-to-One Relations errors
- ActionContext vs ActionContextSingle issues
- Related Records access problems
Context
I have a classic structure with relationships:
I have “Car”, owned by “Owner”, every Owner is linked to one “Account”
Not all “Account” are “Owner”
Exemple of my collection with a relation :
const handle = (collection: CarCustomizer) => {
collection.addAction("Edit car", {
scope: 'Single',
form: [
{
label: FIELD_OWNER
type: 'String',
defaultValue: async (context: ActionContextSingle) => {
const {account_id: accountId} = await context.getRecord([
'account_id',
]);
if (!accountId) {
return '';
}
const [{name}] = await context.dataSource
.getCollection('owner')
.list(filterOneOwner{accountId}), ['name']);
return name || '';
},
isReadOnly: true,
},
Type Structure Example
Here’s a simplified version of our auto-generated schema.ts:
export interface Schema {
'owner': {
plain: {
'account_id': number | null;
'id': number;
'name': string | null;
};
nested: {
'account': Schema['account']['plain'] & Schema['account']['nested'];
};
flat: {
'account:create_time': string | null;
'account:email': string | null;
'account:id': number;
'account:name': string | null;
};
};
'account': {
plain: {
'create_time': string | null;
'email': string | null;
'id': number;
'is_enabled': boolean | null;
'name': string | null;
};
nested: {
// Additional relations if any
};
flat: {
// Flattened relations fields
};
};
'car': {
plain: {
'id': number;
'account_id': number | null;
'brand': string | null;
'year': number | null;
};
nested: {
'owner': Schema['owner']['plain'] & Schema['owner']['nested'];
};
flat: {
'owner:name': string | null;
'owner:id_number': number | null;
'owner:account:email': string | null;
'owner:account:name': string | null;
};
};
}
Issues Encountered
1. One-to-One Relations
According to the documentation, I should be able to define a one-to-one relation like this:
collection.addOneToOneRelation('account', 'account', {
originKey: 'account_id',
originKeyTarget: 'id'
});
Car :
associate(models: any) {
this.Car.hasOne(models.account, {
foreignKey: 'id',
sourceKey: 'account_id',
});
}
Owner:
this.Owner.hasOne(models.account, {
foreignKey: 'id',
sourceKey: 'account_id',
});
And Account is declared.
Issues
1. One-to-One Relations
According to the documentation, I should be able to define a one-to-one relation like this:
collection.addOneToOneRelation('account', 'account', {
originKey: 'account_id',
originKeyTarget: 'id'
});
But I get this error:
TS2322: Type "account_id" is not assignable to type "id" | "name" | "country" | "create_time" | "email" | ...."
And besides, my relationship worked just fine before without this.
2. Actions with ActionContext vs ActionContextSingle
In my actions, I have issues with context typing:
defaultValue: async (context: ActionContext<Schema, 'car'>) => {
const {account_id: accountId} = await context.getRecord(['account_id']);
// ...
}
Even if I have scope: 'Single'
, TS throw different error
Type '(context: ActionContextSingle<Schema, "car">) => Promise<string>'
is not assignable to type 'ValueOrHandler<ActionContext<Schema, "car">, File[]> | undefined'
If i don’t use (context: ActionContextSingle)
, (because ActionContext is infered and doesn’t have getRecord
) I have this error.:
TS2322: Type
(context: ActionContextSingle<Schema, "car">) => Promise<string>
is not assignable to type
ValueOrHandler<ActionContext<Schema, "car">, string> | undefined
If I don’t check if I have accountID, or result for getCollection
, TS give error too,
I tried a lot, like
const ctx = context as ActionContextSingle<Schema, 'car'>;
const {account_id: accountId} = await ctx.getRecord(['account_id']);
But even when TS compiled and I have no error, the autocomplete stop to work.
3. Auto-generated Types vs TSchema
Auto-generated types use Schema
:
export type AccountCustomizer = CollectionCustomizer<Schema, 'account'>;
But it seems Forest internally uses TSchema
. I tried using TSchema
directly but it creates incompatibilities with auto-generated types.
4. Related Record Access Issues
When trying to access related records, type inference breaks:
const [{account: {name}}] = await ctx.dataSource
.getCollection('owner')
.list(filterOneOwner({accountId}), ['account:name']);
// Error: Property 'name' does not exist on type '...'
Questions
- What’s the correct approach to handle these typing issues?
- Are there any best practices for casting between
ActionContext
andActionContextSingle
? - Do you have a complete and complex example for managing ForestAdmin with auto generated Typing and relations ?
- How should we handle the difference between
Schema
andTSchema
? - Relations were working before without
addOneToOneRelation
(thanks to Sequelize). Should we avoid explicitly adding relations? - Is there a recommended way to maintain type safety without breaking existing functionality?
- What’s the best practice for typing complex relations in Forest Admin?
Env
- Environment name: development
- Agent technology: nodejs
- Agent (forest package) name & version:
"@forestadmin/agent": "1.53.1",
"@forestadmin/datasource-sequelize": "1.10.5",
- Database type: MySQL