@balena/abstract-sql-to-typescript
Advanced tools
Comparing version 2.3.0-build-foreign-key-ref-interface-7fa840da8577407e4b90e2c0b06df917ba6f83ec-1 to 3.0.0-build-3-x-cdcb52735998e2ca1b783fcb8d10c8e474c0b72f-1
@@ -7,4 +7,6 @@ # Change Log | ||
## 2.3.0 - 2024-04-17 | ||
## 3.0.0 - 2024-04-17 | ||
* Use types directly from sbvr-types [Pagan Gazzard] | ||
* Expose read vs write selection in generated types [Pagan Gazzard] | ||
* Improve foreign key typings by referencing the appropriate interface [Pagan Gazzard] | ||
@@ -11,0 +13,0 @@ |
@@ -0,7 +1,12 @@ | ||
export type { Types } from '@balena/sbvr-types'; | ||
export type Expanded<T> = Extract<T, any[]>; | ||
export type PickExpanded<T, K extends keyof T> = { | ||
[P in K]-?: Expanded<T[P]>; | ||
}; | ||
export type Deferred<T> = Exclude<T, any[]>; | ||
export type PickDeferred<T, K extends keyof T> = { | ||
[P in K]: Deferred<T[P]>; | ||
}; | ||
import type { AbstractSqlModel } from '@balena/abstract-sql-compiler'; | ||
type RequiredModelSubset = Pick<AbstractSqlModel, 'tables' | 'relationships' | 'synonyms'>; | ||
export interface Options { | ||
mode?: 'read' | 'write'; | ||
} | ||
export declare const abstractSqlToTypescriptTypes: (m: RequiredModelSubset, opts?: Options) => string; | ||
export {}; | ||
export declare const abstractSqlToTypescriptTypes: (m: RequiredModelSubset) => string; |
@@ -6,23 +6,3 @@ "use strict"; | ||
const common_tags_1 = require("common-tags"); | ||
const typeHelpers = { | ||
read: ` | ||
export type DateString = string; | ||
export type Expanded<T> = Extract<T, any[]>; | ||
export type PickExpanded<T, K extends keyof T> = { | ||
[P in K]-?: Expanded<T[P]>; | ||
}; | ||
export type Deferred<T> = Exclude<T, any[]>; | ||
export type PickDeferred<T, K extends keyof T> = { | ||
[P in K]: Deferred<T[P]>; | ||
}; | ||
export interface WebResource { | ||
filename: string; | ||
href: string; | ||
content_type?: string; | ||
content_disposition?: string; | ||
size?: number; | ||
}; | ||
`, | ||
write: '', | ||
}; | ||
const typeHelpers = `import type { Types } from '@balena/abstract-sql-to-typescript';\n`; | ||
const trimNL = new common_tags_1.TemplateTag((0, common_tags_1.replaceResultTransformer)(/^[\r\n]*|[\r\n]*$/g, '')); | ||
@@ -33,3 +13,4 @@ const modelNameToCamelCaseName = (s) => s | ||
.join(''); | ||
const sqlTypeToTypescriptType = (m, f, opts) => { | ||
const getReferencedInterface = (modelName, mode) => `${modelNameToCamelCaseName(modelName)}['${mode}']`; | ||
const sqlTypeToTypescriptType = (m, f, mode) => { | ||
if (!['ForeignKey', 'ConceptType'].includes(f.dataType) && f.checks) { | ||
@@ -45,22 +26,7 @@ const inChecks = f.checks.find((checkTuple) => checkTuple[0] === 'In'); | ||
switch (f.dataType) { | ||
case 'Boolean': | ||
return 'boolean'; | ||
case 'Short Text': | ||
case 'Text': | ||
case 'Hashed': | ||
return 'string'; | ||
case 'Date': | ||
case 'Date Time': | ||
return opts.mode === 'read' ? 'DateString' : 'Date'; | ||
case 'Serial': | ||
case 'Big Serial': | ||
case 'Integer': | ||
case 'Big Integer': | ||
case 'Real': | ||
return 'number'; | ||
case 'ConceptType': | ||
case 'ForeignKey': { | ||
const referencedInterface = modelNameToCamelCaseName(m.tables[f.references.resourceName].name); | ||
const referencedInterface = getReferencedInterface(m.tables[f.references.resourceName].name, mode); | ||
const referencedFieldType = `${referencedInterface}['${f.references.fieldName}']`; | ||
if (opts.mode === 'write') { | ||
if (mode === 'Write') { | ||
return referencedFieldType; | ||
@@ -71,17 +37,11 @@ } | ||
} | ||
case 'File': | ||
return 'Buffer'; | ||
case 'JSON': | ||
return 'object'; | ||
case 'WebResource': | ||
return 'WebResource'; | ||
default: | ||
throw new Error(`Unknown data type: '${f.dataType}'`); | ||
return `Types['${f.dataType}']['${mode}']`; | ||
} | ||
}; | ||
const fieldsToInterfaceProps = (m, fields, opts) => fields.map((f) => { | ||
const fieldsToInterfaceProps = (m, fields, mode) => fields.map((f) => { | ||
const nullable = f.required ? '' : ' | null'; | ||
return `${(0, odata_to_abstract_sql_1.sqlNameToODataName)(f.fieldName)}: ${sqlTypeToTypescriptType(m, f, opts)}${nullable};`; | ||
return `${(0, odata_to_abstract_sql_1.sqlNameToODataName)(f.fieldName)}: ${sqlTypeToTypescriptType(m, f, mode)}${nullable};`; | ||
}); | ||
const recurseRelationships = (m, relationships, inverseSynonyms, opts, currentTable, parentKey) => Object.keys(relationships).flatMap((key) => { | ||
const recurseRelationships = (m, relationships, inverseSynonyms, mode, currentTable, parentKey) => Object.keys(relationships).flatMap((key) => { | ||
if (key === '$') { | ||
@@ -92,3 +52,3 @@ const [localField, referencedField] = relationships.$; | ||
if (referencedTable != null) { | ||
const referencedInterface = modelNameToCamelCaseName(referencedTable.name); | ||
const referencedInterface = getReferencedInterface(referencedTable.name, mode); | ||
const propDefinitons = [`${parentKey}?: ${referencedInterface}[];`]; | ||
@@ -104,5 +64,5 @@ const synonym = inverseSynonyms[(0, odata_to_abstract_sql_1.odataNameToSqlName)(parentKey)]; | ||
} | ||
return recurseRelationships(m, relationships[key], inverseSynonyms, opts, currentTable, `${parentKey}__${key.replace(/ /g, '_')}`); | ||
return recurseRelationships(m, relationships[key], inverseSynonyms, mode, currentTable, `${parentKey}__${key.replace(/ /g, '_')}`); | ||
}); | ||
const relationshipsToInterfaceProps = (m, table, opts) => { | ||
const relationshipsToInterfaceProps = (m, table, mode) => { | ||
const relationships = m.relationships[table.resourceName]; | ||
@@ -120,25 +80,27 @@ if (relationships == null) { | ||
])); | ||
return recurseRelationships(m, relationships[key], inverseSynonyms, opts, table, key.replace(/ /g, '_')); | ||
return recurseRelationships(m, relationships[key], inverseSynonyms, mode, table, key.replace(/ /g, '_')); | ||
}); | ||
}; | ||
const tableToInterface = (m, table, opts) => { | ||
const relationshipProps = opts.mode === 'read' ? relationshipsToInterfaceProps(m, table, opts) : []; | ||
const tableToInterface = (m, table) => { | ||
return trimNL ` | ||
export interface ${modelNameToCamelCaseName(table.name)} { | ||
${[...fieldsToInterfaceProps(m, table.fields, opts), ...relationshipProps].join('\n\t')} | ||
Read: { | ||
${[ | ||
...fieldsToInterfaceProps(m, table.fields, 'Read'), | ||
...relationshipsToInterfaceProps(m, table, 'Read'), | ||
].join('\n\t\t')} | ||
} | ||
Write: { | ||
${[...fieldsToInterfaceProps(m, table.fields, 'Write')].join('\n\t\t')} | ||
} | ||
} | ||
`; | ||
}; | ||
const abstractSqlToTypescriptTypes = (m, opts = {}) => { | ||
const mode = opts.mode ?? 'read'; | ||
const requiredOptions = { | ||
...opts, | ||
mode, | ||
}; | ||
const abstractSqlToTypescriptTypes = (m) => { | ||
return trimNL ` | ||
${typeHelpers[mode]} | ||
${typeHelpers} | ||
${Object.keys(m.tables) | ||
.map((tableName) => { | ||
const t = m.tables[tableName]; | ||
return tableToInterface(m, t, requiredOptions); | ||
return tableToInterface(m, t); | ||
}) | ||
@@ -145,0 +107,0 @@ .join('\n\n')} |
{ | ||
"name": "@balena/abstract-sql-to-typescript", | ||
"version": "2.3.0-build-foreign-key-ref-interface-7fa840da8577407e4b90e2c0b06df917ba6f83ec-1", | ||
"version": "3.0.0-build-3-x-cdcb52735998e2ca1b783fcb8d10c8e474c0b72f-1", | ||
"description": "A translator for abstract sql into typescript types.", | ||
@@ -19,2 +19,3 @@ "main": "out/index.js", | ||
"@balena/odata-to-abstract-sql": "^6.2.3", | ||
"@balena/sbvr-types": "^7.1.0-build-read-write-types-66b9a012e242533372ce34a73e31f6e3aac93d91-1", | ||
"@types/node": "^20.11.24", | ||
@@ -49,4 +50,4 @@ "common-tags": "^1.8.2" | ||
"versionist": { | ||
"publishedAt": "2024-04-17T16:43:14.068Z" | ||
"publishedAt": "2024-04-17T17:23:09.089Z" | ||
} | ||
} |
117
src/index.ts
@@ -0,1 +1,12 @@ | ||
export type { Types } from '@balena/sbvr-types'; | ||
export type Expanded<T> = Extract<T, any[]>; | ||
export type PickExpanded<T, K extends keyof T> = { | ||
[P in K]-?: Expanded<T[P]>; | ||
}; | ||
export type Deferred<T> = Exclude<T, any[]>; | ||
export type PickDeferred<T, K extends keyof T> = { | ||
[P in K]: Deferred<T[P]>; | ||
}; | ||
import type { | ||
@@ -21,23 +32,3 @@ AbstractSqlField, | ||
const typeHelpers = { | ||
read: ` | ||
export type DateString = string; | ||
export type Expanded<T> = Extract<T, any[]>; | ||
export type PickExpanded<T, K extends keyof T> = { | ||
[P in K]-?: Expanded<T[P]>; | ||
}; | ||
export type Deferred<T> = Exclude<T, any[]>; | ||
export type PickDeferred<T, K extends keyof T> = { | ||
[P in K]: Deferred<T[P]>; | ||
}; | ||
export interface WebResource { | ||
filename: string; | ||
href: string; | ||
content_type?: string; | ||
content_disposition?: string; | ||
size?: number; | ||
}; | ||
`, | ||
write: '', | ||
}; | ||
const typeHelpers = `import type { Types } from '@balena/abstract-sql-to-typescript';\n`; | ||
@@ -54,6 +45,9 @@ const trimNL = new TemplateTag( | ||
const getReferencedInterface = (modelName: string, mode: Mode) => | ||
`${modelNameToCamelCaseName(modelName)}['${mode}']`; | ||
const sqlTypeToTypescriptType = ( | ||
m: RequiredModelSubset, | ||
f: AbstractSqlField, | ||
opts: RequiredOptions, | ||
mode: Mode, | ||
): string => { | ||
@@ -73,24 +67,10 @@ if (!['ForeignKey', 'ConceptType'].includes(f.dataType) && f.checks) { | ||
switch (f.dataType) { | ||
case 'Boolean': | ||
return 'boolean'; | ||
case 'Short Text': | ||
case 'Text': | ||
case 'Hashed': | ||
return 'string'; | ||
case 'Date': | ||
case 'Date Time': | ||
return opts.mode === 'read' ? 'DateString' : 'Date'; | ||
case 'Serial': | ||
case 'Big Serial': | ||
case 'Integer': | ||
case 'Big Integer': | ||
case 'Real': | ||
return 'number'; | ||
case 'ConceptType': | ||
case 'ForeignKey': { | ||
const referencedInterface = modelNameToCamelCaseName( | ||
const referencedInterface = getReferencedInterface( | ||
m.tables[f.references!.resourceName].name, | ||
mode, | ||
); | ||
const referencedFieldType = `${referencedInterface}['${f.references!.fieldName}']`; | ||
if (opts.mode === 'write') { | ||
if (mode === 'Write') { | ||
return referencedFieldType; | ||
@@ -102,10 +82,4 @@ } | ||
} | ||
case 'File': | ||
return 'Buffer'; | ||
case 'JSON': | ||
return 'object'; | ||
case 'WebResource': | ||
return 'WebResource'; | ||
default: | ||
throw new Error(`Unknown data type: '${f.dataType}'`); | ||
return `Types['${f.dataType}']['${mode}']`; | ||
} | ||
@@ -117,3 +91,3 @@ }; | ||
fields: AbstractSqlField[], | ||
opts: RequiredOptions, | ||
mode: Mode, | ||
): string[] => | ||
@@ -125,3 +99,3 @@ fields.map((f) => { | ||
f, | ||
opts, | ||
mode, | ||
)}${nullable};`; | ||
@@ -134,3 +108,3 @@ }); | ||
inverseSynonyms: Record<string, string>, | ||
opts: RequiredOptions, | ||
mode: Mode, | ||
currentTable: AbstractSqlTable, | ||
@@ -147,4 +121,5 @@ parentKey: string, | ||
if (referencedTable != null) { | ||
const referencedInterface = modelNameToCamelCaseName( | ||
const referencedInterface = getReferencedInterface( | ||
referencedTable.name, | ||
mode, | ||
); | ||
@@ -167,3 +142,3 @@ const propDefinitons = [`${parentKey}?: ${referencedInterface}[];`]; | ||
inverseSynonyms, | ||
opts, | ||
mode, | ||
currentTable, | ||
@@ -177,3 +152,3 @@ `${parentKey}__${key.replace(/ /g, '_')}`, | ||
table: AbstractSqlTable, | ||
opts: RequiredOptions, | ||
mode: Mode, | ||
): string[] => { | ||
@@ -199,3 +174,3 @@ const relationships = m.relationships[table.resourceName]; | ||
inverseSynonyms, | ||
opts, | ||
mode, | ||
table, | ||
@@ -207,15 +182,14 @@ key.replace(/ /g, '_'), | ||
const tableToInterface = ( | ||
m: RequiredModelSubset, | ||
table: AbstractSqlTable, | ||
opts: RequiredOptions, | ||
) => { | ||
const relationshipProps = | ||
opts.mode === 'read' ? relationshipsToInterfaceProps(m, table, opts) : []; | ||
const tableToInterface = (m: RequiredModelSubset, table: AbstractSqlTable) => { | ||
return trimNL` | ||
export interface ${modelNameToCamelCaseName(table.name)} { | ||
${[...fieldsToInterfaceProps(m, table.fields, opts), ...relationshipProps].join( | ||
'\n\t', | ||
)} | ||
Read: { | ||
${[ | ||
...fieldsToInterfaceProps(m, table.fields, 'Read'), | ||
...relationshipsToInterfaceProps(m, table, 'Read'), | ||
].join('\n\t\t')} | ||
} | ||
Write: { | ||
${[...fieldsToInterfaceProps(m, table.fields, 'Write')].join('\n\t\t')} | ||
} | ||
} | ||
@@ -225,22 +199,13 @@ `; | ||
export interface Options { | ||
mode?: 'read' | 'write'; | ||
} | ||
type RequiredOptions = Required<Options>; | ||
type Mode = 'Read' | 'Write'; | ||
export const abstractSqlToTypescriptTypes = ( | ||
m: RequiredModelSubset, | ||
opts: Options = {}, | ||
): string => { | ||
const mode = opts.mode ?? 'read'; | ||
const requiredOptions: RequiredOptions = { | ||
...opts, | ||
mode, | ||
}; | ||
return trimNL` | ||
${typeHelpers[mode]} | ||
${typeHelpers} | ||
${Object.keys(m.tables) | ||
.map((tableName) => { | ||
const t = m.tables[tableName]; | ||
return tableToInterface(m, t, requiredOptions); | ||
return tableToInterface(m, t); | ||
}) | ||
@@ -247,0 +212,0 @@ .join('\n\n')} |
import type { AbstractSqlModel } from '@balena/abstract-sql-compiler'; | ||
import { expect } from 'chai'; | ||
import { source } from 'common-tags'; | ||
import type { Options } from '../src'; | ||
import { abstractSqlToTypescriptTypes } from '../src'; | ||
@@ -11,3 +10,2 @@ | ||
expectation: string, | ||
mode?: Options['mode'], | ||
) => { | ||
@@ -24,28 +22,9 @@ it(`should generate ${msg}`, () => { | ||
}; | ||
const result = abstractSqlToTypescriptTypes(t, { mode }); | ||
const result = abstractSqlToTypescriptTypes(t); | ||
if (mode == null || mode === 'read') { | ||
expect(result).to.equal(source` | ||
export type DateString = string; | ||
export type Expanded<T> = Extract<T, any[]>; | ||
export type PickExpanded<T, K extends keyof T> = { | ||
[P in K]-?: Expanded<T[P]>; | ||
}; | ||
export type Deferred<T> = Exclude<T, any[]>; | ||
export type PickDeferred<T, K extends keyof T> = { | ||
[P in K]: Deferred<T[P]>; | ||
}; | ||
export interface WebResource { | ||
filename: string; | ||
href: string; | ||
content_type?: string; | ||
content_disposition?: string; | ||
size?: number; | ||
}; | ||
expect(result).to.equal(source` | ||
import type { Types } from '@balena/abstract-sql-to-typescript'; | ||
${expectation} | ||
`); | ||
} else { | ||
expect(result).to.equal(expectation); | ||
} | ||
}); | ||
@@ -304,75 +283,72 @@ }; | ||
test( | ||
'correct read types for a test table', | ||
'correct types for a test table', | ||
testTable, | ||
source` | ||
export interface Parent { | ||
created_at: DateString; | ||
modified_at: DateString; | ||
id: number; | ||
Read: { | ||
created_at: Types['Date Time']['Read']; | ||
modified_at: Types['Date Time']['Read']; | ||
id: Types['Serial']['Read']; | ||
} | ||
Write: { | ||
created_at: Types['Date Time']['Write']; | ||
modified_at: Types['Date Time']['Write']; | ||
id: Types['Serial']['Write']; | ||
} | ||
} | ||
export interface Other { | ||
created_at: DateString; | ||
modified_at: DateString; | ||
id: number; | ||
is_referenced_by__test?: Test[]; | ||
Read: { | ||
created_at: Types['Date Time']['Read']; | ||
modified_at: Types['Date Time']['Read']; | ||
id: Types['Serial']['Read']; | ||
is_referenced_by__test?: Test['Read'][]; | ||
} | ||
Write: { | ||
created_at: Types['Date Time']['Write']; | ||
modified_at: Types['Date Time']['Write']; | ||
id: Types['Serial']['Write']; | ||
} | ||
} | ||
export interface Test { | ||
created_at: DateString; | ||
modified_at: DateString; | ||
id: number; | ||
a_date: DateString; | ||
a_file: WebResource; | ||
parent: { __id: Parent['id'] } | [Parent]; | ||
references__other: { __id: Other['id'] } | [Other]; | ||
test__has__tag_key?: TestTag[]; | ||
test_tag?: TestTag[]; | ||
Read: { | ||
created_at: Types['Date Time']['Read']; | ||
modified_at: Types['Date Time']['Read']; | ||
id: Types['Serial']['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']]; | ||
test__has__tag_key?: TestTag['Read'][]; | ||
test_tag?: TestTag['Read'][]; | ||
} | ||
Write: { | ||
created_at: Types['Date Time']['Write']; | ||
modified_at: Types['Date Time']['Write']; | ||
id: Types['Serial']['Write']; | ||
a_date: Types['Date']['Write']; | ||
a_file: Types['WebResource']['Write']; | ||
parent: Parent['Write']['id']; | ||
references__other: Other['Write']['id']; | ||
} | ||
} | ||
export interface TestTag { | ||
created_at: DateString; | ||
modified_at: DateString; | ||
test: { __id: Test['id'] } | [Test]; | ||
tag_key: string; | ||
id: number; | ||
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['Serial']['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['Serial']['Write']; | ||
} | ||
} | ||
`, | ||
); | ||
test( | ||
'correct write types for a test table', | ||
testTable, | ||
source` | ||
export interface Parent { | ||
created_at: Date; | ||
modified_at: Date; | ||
id: number; | ||
} | ||
export interface Other { | ||
created_at: Date; | ||
modified_at: Date; | ||
id: number; | ||
} | ||
export interface Test { | ||
created_at: Date; | ||
modified_at: Date; | ||
id: number; | ||
a_date: Date; | ||
a_file: WebResource; | ||
parent: Parent['id']; | ||
references__other: Other['id']; | ||
} | ||
export interface TestTag { | ||
created_at: Date; | ||
modified_at: Date; | ||
test: Test['id']; | ||
tag_key: string; | ||
id: number; | ||
} | ||
`, | ||
'write', | ||
); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6
197112
5
669