Skip to content

Commit

Permalink
feat(datasource-customizer): sort enum values in typings file (#892)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiniria authored Nov 30, 2023
1 parent cfad47b commit 564974d
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 13 deletions.
41 changes: 28 additions & 13 deletions packages/datasource-customizer/src/typing-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ export default class TypingGenerator {
this.options.maxFieldsCount = options.maxFieldsCount ?? this.options.maxFieldsCount;
}

private static sortedEntries<T>(
...args: Parameters<typeof Object.entries<T>>
): ReturnType<typeof Object.entries<T>> {
return Object.entries(...args).sort(([name1], [name2]) => name1.localeCompare(name2));
}

/**
* Write types to disk at a given path.
* This method read the file which is already there before overwriting so that customers
Expand Down Expand Up @@ -98,21 +104,21 @@ export default class TypingGenerator {
}

private getRow(collection: Collection): string {
const content = Object.entries(collection.schema.fields)
.sort(([nameA], [nameB]) => nameA.localeCompare(nameB))
.reduce((memo, [name, field]) => {
const content = TypingGenerator.sortedEntries(collection.schema.fields).reduce(
(memo, [name, field]) => {
return field.type === 'Column'
? [...memo, ` '${name}': ${this.getType(field)};`]
: memo;
}, []);
},
[],
);

return ` plain: {\n${content.join('\n')}\n };`;
}

private getRelations(collection: Collection): string {
const content = Object.entries(collection.schema.fields)
.sort(([nameA], [nameB]) => nameA.localeCompare(nameB))
.reduce((memo, [name, field]) => {
const content = TypingGenerator.sortedEntries(collection.schema.fields).reduce(
(memo, [name, field]) => {
if (field.type === 'ManyToOne' || field.type === 'OneToOne') {
const relation = field.foreignCollection;

Expand All @@ -123,7 +129,9 @@ export default class TypingGenerator {
}

return memo;
}, []);
},
[],
);

return content.length ? ` nested: {\n${content.join('\n')}\n };` : ` nested: {};`;
}
Expand All @@ -143,11 +151,11 @@ export default class TypingGenerator {

while (queue.length > 0 && result.length < this.options.maxFieldsCount) {
const { collection, depth, prefix, traversed } = queue.shift();
const sortedFields = TypingGenerator.sortedEntries(collection.schema.fields);

if (prefix) {
result.push(
...Object.entries(collection.schema.fields)
.sort(([nameA], [nameB]) => nameA.localeCompare(nameB))
...sortedFields
.filter(([, schema]) => schema.type === 'Column')
.map(
([name, schema]) => `'${prefix}:${name}': ${this.getType(schema as ColumnSchema)};`,
Expand All @@ -157,7 +165,7 @@ export default class TypingGenerator {

if (depth < maxDepth) {
queue.push(
...Object.entries(collection.schema.fields)
...sortedFields
.filter(([, schema]) => schema.type === 'ManyToOne' || schema.type === 'OneToOne')
.map(([name, schema]: [name: string, schema: OneToOneSchema | ManyToOneSchema]) => {
return {
Expand Down Expand Up @@ -203,7 +211,14 @@ export default class TypingGenerator {
}

if (field.columnType === 'Enum') {
return field.enumValues?.map(v => `'${v.replace(/'/g, "\\'")}'`).join(' | ') ?? 'string';
if (field.enumValues === undefined) return 'string';

return (
[...field.enumValues]
.sort((v1, v2) => v1.localeCompare(v2))
.map(v => `'${v.replace(/'/g, "\\'")}'`)
.join(' | ') ?? 'string'
);
}

if (typeof field.columnType === 'string') {
Expand All @@ -221,7 +236,7 @@ export default class TypingGenerator {
}[field.columnType];
}

return `{${Object.entries(field.columnType)
return `{${TypingGenerator.sortedEntries(field.columnType)
.map(([key, subType]) => `${key}: ${this.getType({ columnType: subType })}`)
.join('; ')}}`;
}
Expand Down
130 changes: 130 additions & 0 deletions packages/datasource-customizer/test/typing-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,136 @@ describe('TypingGenerator', () => {
expectEqual(generated, expected);
});

test('should sort field names', () => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({
name: 'aCollectionName',
schema: {
fields: {
b: factories.columnSchema.build({ columnType: 'String' }),
A: factories.columnSchema.build({ columnType: 'String' }),
z: factories.columnSchema.build({ columnType: 'String' }),
a: factories.columnSchema.build({ columnType: 'String' }),
0: factories.columnSchema.build({ columnType: 'String' }),
},
},
}),
]);

const generated = new TypingGenerator(jest.fn()).generateTypes(datasource, 5);
const expected = `
export type Schema = {
'aCollectionName': {
plain: {
'0': string;
'a': string;
'A': string;
'b': string;
'z': string;
};
nested: {};
flat: {};
};
};`;

expectContains(generated, expected);
});

test('should sort nested field names', () => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({
name: 'z',
schema: {
fields: {
id: factories.columnSchema.build({ columnType: 'Number' }),
b: factories.manyToOneSchema.build({ foreignCollection: 'b', foreignKey: 'id' }),
a: factories.manyToOneSchema.build({ foreignCollection: 'a', foreignKey: 'id' }),
},
},
}),
factories.collection.build({
name: 'b',
schema: {
fields: {
id: factories.columnSchema.build({ columnType: 'Number' }),
},
},
}),
factories.collection.build({
name: 'a',
schema: {
fields: {
id: factories.columnSchema.build({ columnType: 'Number' }),
},
},
}),
]);

const generated = new TypingGenerator(jest.fn()).generateTypes(datasource, 5);
const expected = `
export type Schema = {
'a': {
plain: {
'id': number;
};
nested: {};
flat: {};
};
'b': {
plain: {
'id': number;
};
nested: {};
flat: {};
};
'z': {
plain: {
'id': number;
};
nested: {
'a': Schema['a']['plain'] & Schema['a']['nested'];
'b': Schema['b']['plain'] & Schema['b']['nested'];
};
flat: {
'a:id': number;
'b:id': number;
};
};
};`;

expectContains(generated, expected);
});

test('should sort enum values', () => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({
name: 'aCollectionName',
schema: {
fields: {
enum: factories.columnSchema.build({
columnType: 'Enum',
enumValues: ['b', '0', 'z', 'a', 'A'],
}),
},
},
}),
]);

const generated = new TypingGenerator(jest.fn()).generateTypes(datasource, 5);
const expected = `
export type Schema = {
'aCollectionName': {
plain: {
'enum': '0' | 'a' | 'A' | 'b' | 'z';
};
nested: {};
flat: {};
};
};`;

expectContains(generated, expected);
});

it.each(['_underscores', '-dashes'])('aliases should work with a collection with %s', char => {
const datasource = factories.dataSource.buildWithCollections([
factories.collection.build({ name: `aCollectionNameWith${char}` }),
Expand Down

0 comments on commit 564974d

Please sign in to comment.