Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@balena/abstract-sql-to-typescript

Package Overview
Dependencies
Maintainers
3
Versions
240
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@balena/abstract-sql-to-typescript - npm Package Compare versions

Comparing version 2.3.0-build-foreign-key-ref-interface-7fa840da8577407e4b90e2c0b06df917ba6f83ec-1 to 3.0.0-build-3-x-cdcb52735998e2ca1b783fcb8d10c8e474c0b72f-1

4

CHANGELOG.md

@@ -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"
}
}

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc