UNPKG

@resin/pinejs

Version:

Pine.js is a sophisticated rules-driven API engine that enables you to define rules in a structured subset of English. Those rules are used in order for Pine.js to generate a database schema and the associated [OData](http://www.odata.org/) API. This make

186 lines (173 loc) • 5.31 kB
import type { AbstractSqlModel, AbstractSqlTable, } from '@resin/abstract-sql-compiler'; import * as sbvrTypes from '@resin/sbvr-types'; // tslint:disable-next-line:no-var-requires const { version }: { version: string } = require('../../package.json'); const getResourceName = (resourceName: string): string => resourceName .split('-') .map((namePart) => namePart.split(' ').join('_')) .join('__'); const forEachUniqueTable = <T>( model: AbstractSqlModel['tables'], callback: (tableName: string, table: AbstractSqlTable) => T, ): T[] => { const usedTableNames: { [tableName: string]: true } = {}; const result = []; for (const key in model) { if (model.hasOwnProperty(key)) { const table = model[key]; 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: string): string => { if (sbvrTypes[fieldType] == null) { console.error('Could not resolve type', fieldType); throw new Error('Could not resolve type' + fieldType); } const { complexType } = sbvrTypes[fieldType].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 ( ` <?xml version="1.0" encoding="iso-8859-1" standalone="yes"?> <edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"> <edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="2.0"> <Schema Namespace="${vocabulary}" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://schemas.microsoft.com/ado/2008/09/edm"> ` + forEachUniqueTable( model, (_key, { idField, name: resourceName, fields }) => { resourceName = getResourceName(resourceName); return ( ` <EntityType Name="${resourceName}"> <Key> <PropertyRef Name="${idField}" /> </Key> ` + fields .filter(({ dataType }) => dataType !== 'ForeignKey') .map(({ dataType, fieldName, required }) => { dataType = resolveDataType(dataType); fieldName = getResourceName(fieldName); return `<Property Name="${fieldName}" Type="${dataType}" Nullable="${!required}" />`; }) .join('\n') + '\n' + fields .filter( ({ dataType, references }) => dataType === 'ForeignKey' && references != null, ) .map(({ fieldName, references }) => { const { resourceName: referencedResource } = references!; fieldName = getResourceName(fieldName); return `<NavigationProperty Name="${fieldName}" Relationship="${vocabulary}.${ resourceName + referencedResource }" FromRole="${resourceName}" ToRole="${referencedResource}" />`; }) .join('\n') + '\n' + ` </EntityType>` ); }, ).join('\n\n') + associations .map(({ name, ends }) => { name = getResourceName(name); return ( `<Association Name="${name}">` + '\n\t' + ends .map( ({ resourceName, cardinality }) => `<End Role="${resourceName}" Type="${vocabulary}.${resourceName}" Multiplicity="${cardinality}" />`, ) .join('\n\t') + '\n' + `</Association>` ); }) .join('\n') + ` <EntityContainer Name="${vocabulary}Service" m:IsDefaultEntityContainer="true"> ` + forEachUniqueTable(model, (_key, { name: resourceName }) => { resourceName = getResourceName(resourceName); return `<EntitySet Name="${resourceName}" EntityType="${vocabulary}.${resourceName}" />`; }).join('\n') + '\n' + associations .map(({ name, ends }) => { name = getResourceName(name); return ( `<AssociationSet Name="${name}" Association="${vocabulary}.${name}">` + '\n\t' + ends .map( ({ resourceName }) => `<End Role="${resourceName}" EntitySet="${vocabulary}.${resourceName}" />`, ) .join('\n\t') + ` </AssociationSet>` ); }) .join('\n') + ` </EntityContainer>` + Object.values(complexTypes).join('\n') + ` </Schema> </edmx:DataServices> </edmx:Edmx>` ); }; generateODataMetadata.version = version;