import type { AbstractSqlModel, AbstractSqlTable, } from '@balena/abstract-sql-compiler'; import type { SbvrType } from '@balena/sbvr-types'; import { sbvrTypes } from '../sbvr-api/sbvr-utils.js'; import { version } from '../config-loader/env.js'; const getResourceName = (resourceName: string): string => resourceName .split('-') .map((namePart) => namePart.split(' ').join('_')) .join('__'); const forEachUniqueTable = ( model: AbstractSqlModel['tables'], callback: (tableName: string, table: AbstractSqlTable) => T, ): T[] => { const usedTableNames: { [tableName: string]: true } = {}; const result = []; for (const [key, table] of Object.entries(model)) { if ( typeof table !== 'string' && !table.primitive && !usedTableNames[table.name] ) { usedTableNames[table.name] = true; result.push(callback(key, table)); } } return result; }; export const generateODataMetadata = ( vocabulary: string, abstractSqlModel: AbstractSqlModel, ) => { const complexTypes: { [fieldType: string]: string } = {}; const resolveDataType = (fieldType: keyof typeof sbvrTypes): string => { if (sbvrTypes[fieldType] == null) { console.error('Could not resolve type', fieldType); throw new Error('Could not resolve type' + fieldType); } const { complexType } = (sbvrTypes[fieldType] as SbvrType).types.odata; if (complexType != null) { complexTypes[fieldType] = complexType; } return sbvrTypes[fieldType].types.odata.name; }; const model = abstractSqlModel.tables; const associations: Array<{ name: string; ends: Array<{ resourceName: string; cardinality: '1' | '0..1' | '*'; }>; }> = []; forEachUniqueTable(model, (_key, { name: resourceName, fields }) => { resourceName = getResourceName(resourceName); for (const { dataType, required, references } of fields) { if (dataType === 'ForeignKey' && references != null) { const { resourceName: referencedResource } = references; associations.push({ name: resourceName + referencedResource, ends: [ { resourceName, cardinality: required ? '1' : '0..1' }, { resourceName: referencedResource, cardinality: '*' }, ], }); } } }); return ( ` ` + forEachUniqueTable( model, (_key, { idField, name: resourceName, fields }) => { resourceName = getResourceName(resourceName); return ( ` ` + fields .filter(({ dataType }) => dataType !== 'ForeignKey') .map(({ dataType, fieldName, required }) => { dataType = resolveDataType(dataType as keyof typeof sbvrTypes); fieldName = getResourceName(fieldName); return ``; }) .join('\n') + '\n' + fields .filter( ({ dataType, references }) => dataType === 'ForeignKey' && references != null, ) .map(({ fieldName, references }) => { const { resourceName: referencedResource } = references!; fieldName = getResourceName(fieldName); return ``; }) .join('\n') + '\n' + ` ` ); }, ).join('\n\n') + associations .map(({ name, ends }) => { name = getResourceName(name); return ( `` + '\n\t' + ends .map( ({ resourceName, cardinality }) => ``, ) .join('\n\t') + '\n' + `` ); }) .join('\n') + ` ` + forEachUniqueTable(model, (_key, { name: resourceName }) => { resourceName = getResourceName(resourceName); return ``; }).join('\n') + '\n' + associations .map(({ name, ends }) => { name = getResourceName(name); return ( `` + '\n\t' + ends .map( ({ resourceName }) => ``, ) .join('\n\t') + ` ` ); }) .join('\n') + ` ` + Object.values(complexTypes).join('\n') + ` ` ); }; generateODataMetadata.version = version;