@balena/abstract-sql-to-typescript
Advanced tools
Comparing version 1.0.0-generate-interface-2bd172edacc5749b3a732db716f5674c4f16dba2 to 1.0.0-generate-interface-7a3c3a35b0f9dc1f0b5c77d2004685c276b89cd3
@@ -7,4 +7,4 @@ # Change Log | ||
## 1.0.0 - 2020-10-05 | ||
## 1.0.0 - 2020-10-08 | ||
* Support generating typescript interfaces from an abstract sql model [Pagan Gazzard] |
import { AbstractSqlModel } from '@balena/abstract-sql-compiler'; | ||
export declare const abstractSqlToTypescriptTypes: (m: AbstractSqlModel) => string; | ||
export interface Options { | ||
mode?: 'read' | 'write'; | ||
} | ||
export declare const abstractSqlToTypescriptTypes: (m: AbstractSqlModel, opts?: Options) => string; |
@@ -7,7 +7,16 @@ "use strict"; | ||
const trimNL = new common_tags_1.TemplateTag(common_tags_1.replaceResultTransformer(/^[\r\n]*|[\r\n]*$/g, '')); | ||
const modelNameToTypescriptName = (s) => s | ||
const modelNameToCamelCaseName = (s) => s | ||
.split(/[ -]/) | ||
.map((p) => p[0].toLocaleUpperCase() + p.slice(1)) | ||
.join(''); | ||
const sqlTypeToTypescriptType = (m, f) => { | ||
const sqlTypeToTypescriptType = (m, f, opts) => { | ||
if (!['ForeignKey', 'ConceptType'].includes(f.dataType) && f.checks) { | ||
const inChecks = f.checks.find((checkTuple) => checkTuple[0] === 'In'); | ||
if (inChecks) { | ||
const [, , ...allowedValues] = inChecks; | ||
return allowedValues | ||
.map(([type, value]) => (type === 'Text' ? `'${value}'` : value)) | ||
.join(' | '); | ||
} | ||
} | ||
switch (f.dataType) { | ||
@@ -21,3 +30,3 @@ case 'Boolean': | ||
case 'Date Time': | ||
return 'Date'; | ||
return opts.mode === 'read' ? 'DateString' : 'Date'; | ||
case 'Serial': | ||
@@ -30,3 +39,6 @@ case 'Integer': | ||
case 'ConceptType': | ||
const referencedInterface = modelNameToTypescriptName(m.tables[f.references.resourceName].name); | ||
if (opts.mode === 'write') { | ||
return 'number'; | ||
} | ||
const referencedInterface = modelNameToCamelCaseName(m.tables[f.references.resourceName].name); | ||
const nullable = f.required ? '' : '?'; | ||
@@ -42,21 +54,30 @@ return `{ __id: number } | [${referencedInterface}${nullable}]`; | ||
}; | ||
const fieldsToInterfaceProps = (m, fields) => fields | ||
const fieldsToInterfaceProps = (m, fields, opts) => fields | ||
.map((f) => { | ||
const nullable = f.required ? '' : ' | null'; | ||
return trimNL ` | ||
${odata_to_abstract_sql_1.sqlNameToODataName(f.fieldName)}?: ${sqlTypeToTypescriptType(m, f)}${nullable}; | ||
${odata_to_abstract_sql_1.sqlNameToODataName(f.fieldName)}: ${sqlTypeToTypescriptType(m, f, opts)}${nullable}; | ||
`; | ||
}) | ||
.join('\n'); | ||
const tableToInterface = (m, table) => trimNL ` | ||
interface ${modelNameToTypescriptName(table.name)} { | ||
${fieldsToInterfaceProps(m, table.fields)} | ||
const tableToInterface = (m, table, opts) => trimNL ` | ||
export interface ${modelNameToCamelCaseName(table.name)} { | ||
${fieldsToInterfaceProps(m, table.fields, opts)} | ||
} | ||
`; | ||
exports.abstractSqlToTypescriptTypes = (m) => Object.keys(m.tables) | ||
.map((tableName) => { | ||
const t = m.tables[tableName]; | ||
return tableToInterface(m, t); | ||
}) | ||
.join('\n'); | ||
exports.abstractSqlToTypescriptTypes = (m, opts = {}) => { | ||
const requiredOptions = { | ||
mode: 'read', | ||
...opts, | ||
}; | ||
return trimNL ` | ||
export type DateString = string; | ||
${Object.keys(m.tables) | ||
.map((tableName) => { | ||
const t = m.tables[tableName]; | ||
return tableToInterface(m, t, requiredOptions); | ||
}) | ||
.join('\n\n')} | ||
`; | ||
}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@balena/abstract-sql-to-typescript", | ||
"version": "1.0.0-generate-interface-2bd172edacc5749b3a732db716f5674c4f16dba2", | ||
"version": "1.0.0-generate-interface-7a3c3a35b0f9dc1f0b5c77d2004685c276b89cd3", | ||
"description": "A translator for abstract sql into typescript types.", | ||
@@ -5,0 +5,0 @@ "main": "out/index.js", |
@@ -5,2 +5,3 @@ import { | ||
AbstractSqlTable, | ||
InNode, | ||
} from '@balena/abstract-sql-compiler'; | ||
@@ -14,3 +15,3 @@ import { sqlNameToODataName } from '@balena/odata-to-abstract-sql'; | ||
const modelNameToTypescriptName = (s: string) => | ||
const modelNameToCamelCaseName = (s: string) => | ||
s | ||
@@ -21,3 +22,19 @@ .split(/[ -]/) | ||
const sqlTypeToTypescriptType = (m: AbstractSqlModel, f: AbstractSqlField) => { | ||
const sqlTypeToTypescriptType = ( | ||
m: AbstractSqlModel, | ||
f: AbstractSqlField, | ||
opts: RequiredOptions, | ||
) => { | ||
if (!['ForeignKey', 'ConceptType'].includes(f.dataType) && f.checks) { | ||
const inChecks = f.checks.find( | ||
(checkTuple): checkTuple is InNode => checkTuple[0] === 'In', | ||
); | ||
if (inChecks) { | ||
const [, , ...allowedValues] = inChecks; | ||
return allowedValues | ||
.map(([type, value]) => (type === 'Text' ? `'${value}'` : value)) | ||
.join(' | '); | ||
} | ||
} | ||
switch (f.dataType) { | ||
@@ -31,3 +48,3 @@ case 'Boolean': | ||
case 'Date Time': | ||
return 'Date'; | ||
return opts.mode === 'read' ? 'DateString' : 'Date'; | ||
case 'Serial': | ||
@@ -41,3 +58,7 @@ case 'Integer': | ||
case 'ConceptType': | ||
const referencedInterface = modelNameToTypescriptName( | ||
if (opts.mode === 'write') { | ||
return 'number'; | ||
} | ||
const referencedInterface = modelNameToCamelCaseName( | ||
m.tables[f.references!.resourceName].name, | ||
@@ -59,2 +80,3 @@ ); | ||
fields: AbstractSqlField[], | ||
opts: RequiredOptions, | ||
) => | ||
@@ -65,5 +87,6 @@ fields | ||
return trimNL` | ||
${sqlNameToODataName(f.fieldName)}?: ${sqlTypeToTypescriptType( | ||
${sqlNameToODataName(f.fieldName)}: ${sqlTypeToTypescriptType( | ||
m, | ||
f, | ||
opts, | ||
)}${nullable}; | ||
@@ -77,14 +100,31 @@ `; | ||
table: AbstractSqlTable, | ||
opts: RequiredOptions, | ||
) => trimNL` | ||
interface ${modelNameToTypescriptName(table.name)} { | ||
${fieldsToInterfaceProps(m, table.fields)} | ||
export interface ${modelNameToCamelCaseName(table.name)} { | ||
${fieldsToInterfaceProps(m, table.fields, opts)} | ||
} | ||
`; | ||
export const abstractSqlToTypescriptTypes = (m: AbstractSqlModel) => | ||
Object.keys(m.tables) | ||
.map((tableName) => { | ||
const t = m.tables[tableName]; | ||
return tableToInterface(m, t); | ||
}) | ||
.join('\n'); | ||
export interface Options { | ||
mode?: 'read' | 'write'; | ||
} | ||
type RequiredOptions = Required<Options>; | ||
export const abstractSqlToTypescriptTypes = ( | ||
m: AbstractSqlModel, | ||
opts: Options = {}, | ||
) => { | ||
const requiredOptions: RequiredOptions = { | ||
mode: 'read', | ||
...opts, | ||
}; | ||
return trimNL` | ||
export type DateString = string; | ||
${Object.keys(m.tables) | ||
.map((tableName) => { | ||
const t = m.tables[tableName]; | ||
return tableToInterface(m, t, requiredOptions); | ||
}) | ||
.join('\n\n')} | ||
`; | ||
}; |
import { AbstractSqlModel } from '@balena/abstract-sql-compiler'; | ||
import { expect } from 'chai'; | ||
import { stripIndent } from 'common-tags'; | ||
import { abstractSqlToTypescriptTypes } from '../src'; | ||
import { source } from 'common-tags'; | ||
import { abstractSqlToTypescriptTypes, Options } from '../src'; | ||
const test = (s: string, m: Partial<AbstractSqlModel>, e: string) => { | ||
it(`should generate ${s}`, () => { | ||
const test = ( | ||
msg: string, | ||
model: Partial<AbstractSqlModel>, | ||
expectation: string, | ||
mode?: Options['mode'], | ||
) => { | ||
it(`should generate ${msg}`, () => { | ||
// Set defaults for required props | ||
@@ -14,6 +19,10 @@ const t: AbstractSqlModel = { | ||
rules: [], | ||
...m, | ||
...model, | ||
}; | ||
expect(abstractSqlToTypescriptTypes(t)).to.equal(e); | ||
// expect(abstractSqlToTypescriptTypes(t, {mode})).to.equal(expectation) | ||
expect(abstractSqlToTypescriptTypes(t, { mode })).to.equal(source` | ||
export type DateString = string; | ||
${expectation} | ||
`); | ||
}); | ||
@@ -23,49 +32,64 @@ }; | ||
test('no types for an empty model', {}, ''); | ||
test('correct types for an actor table', | ||
{ | ||
tables: { | ||
actor: { | ||
fields: [ | ||
{ | ||
dataType: 'Date Time', | ||
fieldName: 'created at', | ||
required: true, | ||
defaultValue: 'CURRENT_TIMESTAMP', | ||
}, | ||
{ | ||
dataType: 'Date Time', | ||
fieldName: 'modified at', | ||
required: true, | ||
defaultValue: 'CURRENT_TIMESTAMP', | ||
}, | ||
{ | ||
dataType: 'Serial', | ||
fieldName: 'id', | ||
required: true, | ||
index: 'PRIMARY KEY', | ||
}, | ||
], | ||
primitive: false, | ||
name: 'actor', | ||
indexes: [], | ||
idField: 'id', | ||
resourceName: 'actor', | ||
triggers: [ | ||
{ | ||
when: 'BEFORE', | ||
operation: 'UPDATE', | ||
level: 'ROW', | ||
fnName: 'trigger_update_modified_at', | ||
}, | ||
], | ||
}, | ||
const actorTable: Partial<AbstractSqlModel> = { | ||
tables: { | ||
actor: { | ||
fields: [ | ||
{ | ||
dataType: 'Date Time', | ||
fieldName: 'created at', | ||
required: true, | ||
defaultValue: 'CURRENT_TIMESTAMP', | ||
}, | ||
{ | ||
dataType: 'Date Time', | ||
fieldName: 'modified at', | ||
required: true, | ||
defaultValue: 'CURRENT_TIMESTAMP', | ||
}, | ||
{ | ||
dataType: 'Serial', | ||
fieldName: 'id', | ||
required: true, | ||
index: 'PRIMARY KEY', | ||
}, | ||
], | ||
primitive: false, | ||
name: 'actor', | ||
indexes: [], | ||
idField: 'id', | ||
resourceName: 'actor', | ||
triggers: [ | ||
{ | ||
when: 'BEFORE', | ||
operation: 'UPDATE', | ||
level: 'ROW', | ||
fnName: 'trigger_update_modified_at', | ||
}, | ||
], | ||
}, | ||
}, | ||
stripIndent` | ||
interface Actor { | ||
created_at?: Date; | ||
modified_at?: Date; | ||
id?: number; | ||
}; | ||
test( | ||
'correct read types for an actor table', | ||
actorTable, | ||
source` | ||
export interface Actor { | ||
created_at: Date; | ||
modified_at: Date; | ||
id: number; | ||
} | ||
`, | ||
); | ||
test( | ||
'correct write types for an actor table', | ||
actorTable, | ||
source` | ||
export interface Actor { | ||
created_at: Date; | ||
modified_at: Date; | ||
id: number; | ||
} | ||
`, | ||
'write', | ||
); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
13147
312