Skip to content

Commit

Permalink
Add option to replace Serial with Integer
Browse files Browse the repository at this point in the history
Change-type: minor
  • Loading branch information
joshbwlng committed Dec 12, 2024
1 parent 53c3926 commit 479ac29
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 13 deletions.
41 changes: 33 additions & 8 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@ type RequiredModelSubset = Pick<
'tables' | 'relationships' | 'synonyms'
>;

export type Options = {
convertSerialToInteger?: boolean;
};

const trimNL = new TemplateTag(
replaceResultTransformer(/^[\r\n]*|[\r\n]*$/g, ''),
);

const replaceSerial = (s: string): string => s.replace(/serial$/i, 'Integer');

const modelNameToCamelCaseName = (s: string): string =>
s
.split(/[ -]/)
Expand All @@ -34,6 +40,7 @@ const sqlTypeToTypescriptType = (
m: RequiredModelSubset,
f: AbstractSqlField,
mode: Mode,
opts: Options,
): string => {
if (!['ForeignKey', 'ConceptType'].includes(f.dataType) && f.checks) {
const inChecks = f.checks.find(
Expand Down Expand Up @@ -63,7 +70,7 @@ const sqlTypeToTypescriptType = (
return `{ __id: ${referencedFieldType} } | [${referencedInterface}]${nullable}`;
}
default:
return `Types['${f.dataType}']['${mode}']`;
return `Types['${opts.convertSerialToInteger ? replaceSerial(f.dataType) : f.dataType}']['${mode}']`;
}
};

Expand All @@ -72,6 +79,7 @@ const fieldToInterfaceProps = (
m: RequiredModelSubset,
f: AbstractSqlField,
mode: Mode,
opts: Options,
): string | undefined => {
if (mode === 'Write' && f.computed != null) {
// Computed terms cannot be written to
Expand All @@ -82,16 +90,18 @@ const fieldToInterfaceProps = (
m,
f,
mode,
opts,
)}${nullable};`;
};

const fieldsToInterfaceProps = (
m: RequiredModelSubset,
fields: AbstractSqlField[],
mode: Mode,
opts: Options,
): string[] =>
fields
.map((f) => fieldToInterfaceProps(f.fieldName, m, f, mode))
.map((f) => fieldToInterfaceProps(f.fieldName, m, f, mode, opts))
.filter((f) => f != null);

const getSynonyms = (
Expand All @@ -114,6 +124,7 @@ const recurseRelationships = (
mode: Mode,
currentTable: AbstractSqlTable,
parentKey: string,
opts: Options,
): string[] =>
Object.keys(relationships).flatMap((key) => {
if (key === '$') {
Expand Down Expand Up @@ -149,7 +160,13 @@ const recurseRelationships = (
const addDefinition = (propName: string) => {
// Only add the relationship if it doesn't directly match the field name to avoid duplicates
if (f.fieldName !== propName) {
const propDefiniton = fieldToInterfaceProps(propName, m, f, mode);
const propDefiniton = fieldToInterfaceProps(
propName,
m,
f,
mode,
opts,
);
if (propDefiniton != null) {
propDefinitons.push(propDefiniton);
}
Expand All @@ -174,13 +191,15 @@ const recurseRelationships = (
mode,
currentTable,
`${parentKey}-${key}`,
opts,
);
});

const relationshipsToInterfaceProps = (
m: RequiredModelSubset,
table: AbstractSqlTable,
mode: Mode,
opts: Options,
): string[] => {
const relationships = m.relationships[table.resourceName];
if (relationships == null) {
Expand All @@ -204,15 +223,20 @@ const relationshipsToInterfaceProps = (
mode,
table,
key,
opts,
);
});
};

const tableToInterface = (m: RequiredModelSubset, table: AbstractSqlTable) => {
const tableToInterface = (
m: RequiredModelSubset,
table: AbstractSqlTable,
opts: Options,
) => {
const writableFields =
table.definition != null
? []
: fieldsToInterfaceProps(m, table.fields, 'Write');
: fieldsToInterfaceProps(m, table.fields, 'Write', opts);
const writeType =
writableFields.length === 0
? // If there's a table definition then we cannot write anything
Expand All @@ -224,8 +248,8 @@ const tableToInterface = (m: RequiredModelSubset, table: AbstractSqlTable) => {
export interface ${modelNameToCamelCaseName(table.name)} {
Read: {
${[
...fieldsToInterfaceProps(m, table.fields, 'Read'),
...relationshipsToInterfaceProps(m, table, 'Read'),
...fieldsToInterfaceProps(m, table.fields, 'Read', opts),
...relationshipsToInterfaceProps(m, table, 'Read', opts),
].join('\n\t\t')}
};
Write: ${writeType};
Expand All @@ -237,6 +261,7 @@ type Mode = 'Read' | 'Write';

export const abstractSqlToTypescriptTypes = (
m: RequiredModelSubset,
opts: Options = {},
): string => {
return trimNL`
// These types were generated by @balena/abstract-sql-to-typescript v${version}
Expand All @@ -246,7 +271,7 @@ import type { Types } from '@balena/abstract-sql-to-typescript';
${Object.keys(m.tables)
.map((tableName) => {
const t = m.tables[tableName];
return tableToInterface(m, t);
return tableToInterface(m, t, opts);
})
.join('\n\n')}
Expand Down
99 changes: 94 additions & 5 deletions test/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { AbstractSqlModel } from '@balena/abstract-sql-compiler';
import { expect } from 'chai';
import { source } from 'common-tags';
import { abstractSqlToTypescriptTypes } from '../src/generate';
import { abstractSqlToTypescriptTypes, type Options } from '../src/generate';
import { version } from '../package.json';

const test = (
msg: string,
model: Partial<AbstractSqlModel>,
expectation: string,
opts?: Options,
) => {
it(`should generate ${msg}`, () => {
// Set defaults for required props
Expand All @@ -19,7 +20,7 @@ const test = (
lfInfo: { rules: {} },
...model,
};
const result = abstractSqlToTypescriptTypes(t);
const result = abstractSqlToTypescriptTypes(t, opts);

expect(result).to.equal(source`
// These types were generated by @balena/abstract-sql-to-typescript v${version}
Expand Down Expand Up @@ -95,7 +96,7 @@ const testTable: Partial<AbstractSqlModel> = {
defaultValue: 'CURRENT_TIMESTAMP',
},
{
dataType: 'Serial',
dataType: 'Big Serial',
fieldName: 'id',
required: true,
index: 'PRIMARY KEY',
Expand Down Expand Up @@ -323,13 +324,13 @@ test(
Read: {
created_at: Types['Date Time']['Read'];
modified_at: Types['Date Time']['Read'];
id: Types['Serial']['Read'];
id: Types['Big Serial']['Read'];
is_referenced_by__test?: Array<Test['Read']>;
};
Write: {
created_at: Types['Date Time']['Write'];
modified_at: Types['Date Time']['Write'];
id: Types['Serial']['Write'];
id: Types['Big Serial']['Write'];
};
}
Expand Down Expand Up @@ -386,3 +387,91 @@ test(
}
`,
);

test(
'correct types for a test table with convertSerialToInteger=true',
testTable,
source`
export interface Parent {
Read: {
created_at: Types['Date Time']['Read'];
modified_at: Types['Date Time']['Read'];
id: Types['Integer']['Read'];
};
Write: {
created_at: Types['Date Time']['Write'];
modified_at: Types['Date Time']['Write'];
id: Types['Integer']['Write'];
};
}
export interface Other {
Read: {
created_at: Types['Date Time']['Read'];
modified_at: Types['Date Time']['Read'];
id: Types['Big Integer']['Read'];
is_referenced_by__test?: Array<Test['Read']>;
};
Write: {
created_at: Types['Date Time']['Write'];
modified_at: Types['Date Time']['Write'];
id: Types['Big Integer']['Write'];
};
}
export interface Test {
Read: {
created_at: Types['Date Time']['Read'];
modified_at: Types['Date Time']['Read'];
id: Types['Integer']['Read'];
a_date: Types['Date']['Read'];
a_file: Types['WebResource']['Read'];
parent: { __id: Parent['Read']['id'] } | [Parent['Read']];
references__other: { __id: Other['Read']['id'] } | [Other['Read']];
aliased__tag?: Array<TestTag['Read']>;
references__test__has__tag_key?: Array<TestTag['Read']>;
references__test_tag?: Array<TestTag['Read']>;
test__has__tag_key?: Array<TestTag['Read']>;
test_tag?: Array<TestTag['Read']>;
};
Write: {
created_at: Types['Date Time']['Write'];
modified_at: Types['Date Time']['Write'];
id: Types['Integer']['Write'];
a_date: Types['Date']['Write'];
a_file: Types['WebResource']['Write'];
parent: Parent['Write']['id'];
references__other: Other['Write']['id'];
};
}
export interface TestTag {
Read: {
created_at: Types['Date Time']['Read'];
modified_at: Types['Date Time']['Read'];
test: { __id: Test['Read']['id'] } | [Test['Read']];
tag_key: Types['Short Text']['Read'];
id: Types['Integer']['Read'];
};
Write: {
created_at: Types['Date Time']['Write'];
modified_at: Types['Date Time']['Write'];
test: Test['Write']['id'];
tag_key: Types['Short Text']['Write'];
id: Types['Integer']['Write'];
};
}
export default interface $Model {
parent: Parent;
other: Other;
test: Test;
test__has__tag_key: TestTag;
// Synonyms
test_tag: TestTag;
}
`,
{
convertSerialToInteger: true,
},
);

0 comments on commit 479ac29

Please sign in to comment.