UNPKG

u4pm-jaydata-dynamic-metadata

Version:

OData v4 metadata to JayData context util

605 lines 28.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Metadata = void 0; const annotations_1 = require("./annotations"); const dts_1 = require("./dts"); var containsField = (obj, field, cb) => { // if (field in (obj || {})) { // cb(obj[field]) // } if (obj && field in obj && typeof obj[field] !== "undefined") { cb(obj[field]); } }; var parsebool = (b, d) => { if ("boolean" === typeof b) { return b; } switch (b) { case "true": return true; case "false": return false; default: return d; } }; var _collectionRegex = /^Collection\((.*)\)$/; const dtsTypeMapping = { 'Edm.Boolean': 'boolean', 'Edm.Binary': 'Uint8Array', 'Edm.DateTime': 'Date', 'Edm.DateTimeOffset': 'Date', 'Edm.Time': 'string', 'Edm.Duration': 'string', 'Edm.TimeOfDay': 'string', 'Edm.Date': 'string', 'Edm.Decimal': 'string', 'Edm.Single': 'number', 'Edm.Float': 'number', 'Edm.Double': 'number', 'Edm.Guid': 'string', 'Edm.Int16': 'number', 'Edm.Int32': 'number', 'Edm.Int64': 'string', 'Edm.Byte': 'number', 'Edm.SByte': 'number', 'Edm.String': 'string', 'Edm.GeographyPoint': '$data.Geography', 'Edm.GeographyLineString': '$data.GeographyLineString', 'Edm.GeographyPolygon': '$data.GeographyPolygon', 'Edm.GeographyMultiPoint': '$data.GeographyMultiPoint', 'Edm.GeographyMultiPolygon': '$data.GeographyMultiPolygon', 'Edm.GeographyMultiLineString': '$data.GeographyMultiLineString', 'Edm.GeographyCollection': '$data.GeographyCollection', 'Edm.GeometryPoint': '$data.Geometry', 'Edm.GeometryLineString': '$data.GeometryLineString', 'Edm.GeometryPolygon': '$data.GeometryPolygon', 'Edm.GeometryMultiPoint': '$data.GeometryMultiPoint', 'Edm.GeometryMultiPolygon': '$data.GeometryMultiPolygon', 'Edm.GeometryMultiLineString': '$data.GeometryMultiLineString', 'Edm.GeometryCollection': '$data.GeometryCollection' }; class Metadata { constructor($data, options, metadata) { this.$data = $data; this.options = options || {}; this.metadata = metadata; this.options.container = this.$data.Container; //this.options.container || $data.createContainer() this.options.baseType = this.options.baseType || '$data.Entity'; this.options.entitySetType = this.options.entitySetType || '$data.EntitySet'; this.options.contextType = this.options.contextType || '$data.EntityContext'; this.options.collectionBaseType = this.options.collectionBaseType || 'Array'; this.annotationHandler = new annotations_1.Annotations(); this.storedTypes = {}; } _getMaxValue(maxValue) { if ("number" === typeof maxValue) return maxValue; if ("max" === maxValue) return Number.MAX_VALUE; return parseInt(maxValue); } createTypeDefinition(propertySchema, definition) { containsField(propertySchema, "type", v => { var match = _collectionRegex.exec(v); if (match) { definition.type = this.options.collectionBaseType; definition.elementType = match[1]; } else { definition.type = v; } }); } createReturnTypeDefinition(propertySchema, definition) { containsField(propertySchema, "type", v => { var match = _collectionRegex.exec(v); if (match) { definition.returnType = '$data.Queryable'; definition.elementType = match[1]; } else { definition.returnType = v; } }); } createProperty(entityFullName, entitySchema, propertySchema) { var self = this; if (!propertySchema) { propertySchema = entitySchema; entitySchema = undefined; } var definition = {}; this.createTypeDefinition(propertySchema, definition); containsField(propertySchema, "nullable", v => { definition.nullable = parsebool(v, true), definition.required = parsebool(v, true) === false; }); containsField(propertySchema, "maxLength", v => { definition.maxLength = this._getMaxValue(v); }); containsField(entitySchema, "key", keys => { if (keys.propertyRefs.some(pr => pr.name === propertySchema.name)) { definition.key = true; } }); containsField(propertySchema, "annotations", v => { this.annotationHandler.processEntityPropertyAnnotations(entityFullName, propertySchema.name, v); }); return { name: propertySchema.name, definition }; } createNavigationProperty(entityFullName, entitySchema, propertySchema) { if (!propertySchema) { propertySchema = entitySchema; entitySchema = undefined; } var definition = {}; this.createTypeDefinition(propertySchema, definition); containsField(propertySchema, "nullable", v => { definition.nullable = parsebool(v, true), definition.required = parsebool(v, true) === false; }); containsField(propertySchema, "partner", p => { definition.inverseProperty = p; }); if (!definition.inverseProperty) { definition.inverseProperty = '$$unbound'; } containsField(propertySchema, "annotations", v => { this.annotationHandler.processEntityPropertyAnnotations(entityFullName, propertySchema.name, v); }); return { name: propertySchema.name, definition }; } createEntityDefinition(entitySchema, entityFullName) { var props = (entitySchema.properties || []).map(this.createProperty.bind(this, entityFullName, entitySchema)); var navigationProps = (entitySchema.navigationProperties || []).map(this.createNavigationProperty.bind(this, entityFullName, entitySchema)); props = props.concat(navigationProps); var result = props.reduce((p, c) => { p[c.name] = c.definition; return p; }, {}); return result; } createEntityType(entitySchema, namespace) { let baseType = (entitySchema.baseType ? entitySchema.baseType : this.options.baseType); let entityFullName = `${namespace}.${entitySchema.name}`; let definition = this.createEntityDefinition(entitySchema, entityFullName); let staticDefinition = {}; containsField(entitySchema, "openType", v => { if (parsebool(v, false)) { staticDefinition.openType = { value: true }; } }); containsField(entitySchema, "annotations", v => { this.annotationHandler.processEntityAnnotations(entityFullName, v); }); return { namespace, typeName: entityFullName, baseType, params: [entityFullName, this.options.container, definition, staticDefinition], definition, type: 'entity' }; } createEnumOption(enumFullName, entitySchema, propertySchema, i) { if (!propertySchema) { propertySchema = entitySchema; entitySchema = undefined; } var definition = { name: propertySchema.name, index: i }; containsField(propertySchema, "value", value => { var v = +value; if (!isNaN(v)) { definition.value = v; } }); containsField(propertySchema, "annotations", v => { this.annotationHandler.processEntityPropertyAnnotations(enumFullName, propertySchema.name, v, true); }); return definition; } createEnumDefinition(enumSchema, enumFullName) { var props = (enumSchema.members || []).map(this.createEnumOption.bind(this, enumFullName, enumSchema)); return props; } createEnumType(enumSchema, namespace) { var self = this; let enumFullName = `${namespace}.${enumSchema.name}`; let definition = this.createEnumDefinition(enumSchema, enumFullName); containsField(enumSchema, "annotations", v => { this.annotationHandler.processEntityAnnotations(enumFullName, v, true); }); return { namespace, typeName: enumFullName, baseType: '$data.Enum', params: [enumFullName, this.options.container, enumSchema.underlyingType, definition], definition, type: 'enum' }; } createEntitySetProperty(entitySetSchema, contextSchema) { //var c = this.options.container var t = entitySetSchema.entityType; //c.classTypes[c.classNames[entitySetSchema.entityType]] // || entitySetSchema.entityType var prop = { name: entitySetSchema.name, definition: { type: this.options.entitySetType, elementType: t } }; containsField(entitySetSchema, "annotations", v => { this.annotationHandler.processEntitySetAnnotations(t, v); }); return prop; } indexBy(fieldName, pick) { return [(p, c) => { p[c[fieldName]] = c[pick]; return p; }, {}]; } createContextDefinition(contextSchema, namespace) { var props = (contextSchema.entitySets || []).map(es => this.createEntitySetProperty(es, contextSchema)); var result = props.reduce(...this.indexBy("name", "definition")); return result; } createContextType(contextSchema, namespace) { if (Array.isArray(contextSchema)) { throw new Error("Array type is not supported here"); } var definition = this.createContextDefinition(contextSchema, namespace); var baseType = this.options.contextType; var typeName = `${namespace}.${contextSchema.name}`; var contextImportMethods = []; contextSchema.actionImports && contextImportMethods.push(...contextSchema.actionImports); contextSchema.functionImports && contextImportMethods.push(...contextSchema.functionImports); return { namespace, typeName, baseType, params: [typeName, this.options.container, definition], definition, type: 'context', contextImportMethods }; } createMethodParameter(parameter, definition) { var paramDef = { name: parameter.name }; this.createTypeDefinition(parameter, paramDef); definition.params.push(paramDef); } applyBoundMethod(actionInfo, ns, typeDefinitions, type) { let definition = { type, namespace: ns, returnType: null, params: [] }; containsField(actionInfo, "returnType", value => { this.createReturnTypeDefinition(value, definition); }); let parameters = [].concat(actionInfo.parameters); parameters.forEach((p) => this.createMethodParameter(p, definition)); if (parsebool(actionInfo.isBound, false)) { let bindingParameter = definition.params.shift(); if (bindingParameter.type === this.options.collectionBaseType) { let filteredContextDefinitions = typeDefinitions.filter((d) => d.namespace === ns && d.type === 'context'); filteredContextDefinitions.forEach(ctx => { for (var setName in ctx.definition) { let set = ctx.definition[setName]; if (set.elementType === bindingParameter.elementType) { set.actions = set.actions || {}; set.actions[actionInfo.name] = definition; } } }); } else { let filteredTypeDefinitions = typeDefinitions.filter((d) => d.typeName === bindingParameter.type && d.type === 'entity'); filteredTypeDefinitions.forEach(t => { t.definition[actionInfo.name] = definition; }); } } else { delete definition.namespace; let methodFullName = ns + '.' + actionInfo.name; let filteredContextDefinitions = typeDefinitions.filter((d) => d.type === 'context'); filteredContextDefinitions.forEach((ctx) => { ctx.contextImportMethods.forEach(methodImportInfo => { if (methodImportInfo.action === methodFullName || methodImportInfo.function === methodFullName) { ctx.definition[actionInfo.name] = definition; } }); }); } } processMetadata(createdTypes) { var types = createdTypes || []; var typeDefinitions = []; var serviceMethods = []; containsField(this.metadata, "references", references => { references.forEach(ref => { containsField(ref, "includes", includes => { includes.forEach(include => { this.annotationHandler.addInclude(include); }); }); }); }); var dtsModules = {}; types.dts = '/*//////////////////////////////////////////////////////////////////////////////////////\n' + '////// Autogenerated by JaySvcUtil http://JayData.org for more info /////////\n' + '////// OData V4 TypeScript /////////\n' + '//////////////////////////////////////////////////////////////////////////////////////*/\n\n'; if (!this.options.ignoreCoreInDts) types.dts += dts_1.JayData.src + '\n\n'; //types.dts += 'declare module Edm {\n' + Object.keys(dtsTypeMapping).map(t => ' type ' + t.split('.')[1] + ' = ' + dtsTypeMapping[t] + ';').join('\n') + '\n}\n\n'; var self = this; this.metadata.dataServices.schemas.forEach(schema => { var ns = schema.namespace; dtsModules[ns] = ['declare module ' + ns + ' {', '}']; if (schema.enumTypes) { let enumTypes = schema.enumTypes.map(ct => this.createEnumType(ct, ns)); typeDefinitions.push(...enumTypes); } if (schema.complexTypes) { let complexTypes = schema.complexTypes.map(ct => this.createEntityType(ct, ns)); typeDefinitions.push(...complexTypes); } if (schema.entityTypes) { let entityTypes = schema.entityTypes.map(et => this.createEntityType(et, ns)); typeDefinitions.push(...entityTypes); } if (schema.actions) { serviceMethods.push(...schema.actions.map(m => defs => this.applyBoundMethod(m, ns, defs, '$data.ServiceAction'))); } if (schema.functions) { serviceMethods.push(...schema.functions.map(m => defs => this.applyBoundMethod(m, ns, defs, '$data.ServiceFunction'))); } if (schema.entityContainer) { let contexts = schema.entityContainer.map(ctx => this.createContextType(ctx, self.options.namespace || ns)); typeDefinitions.push(...contexts); } //console.log('annotations', schema) containsField(schema, 'annotations', (annotations) => { annotations.forEach((annot) => { containsField(annot, "target", target => { containsField(annot, "annotations", v => { this.annotationHandler.processSchemaAnnotations(target, v, annot.qualifier); }); }); }); }); }); serviceMethods.forEach(m => m(typeDefinitions)); var contextFullName; types.src = '(function(mod) {\n' + ' if (typeof exports == "object" && typeof module == "object") return mod(exports, require("u4pm-jaydata-dist/core")); // CommonJS\n' + ' if (typeof define == "function" && define.amd) return define(["exports", "u4pm-jaydata-dist/core"], mod); // AMD\n' + ' mod($data.generatedContext || ($data.generatedContext = {}), $data); // Plain browser env\n' + '})(function(exports, $data) {\n\n' + 'exports.$data = $data;\n\n' + 'var types = {};\n\n'; typeDefinitions = this.orderTypeDefinitions(typeDefinitions); types.push(...typeDefinitions.map((d) => { this.annotationHandler.preProcessAnnotation(d); this.storeExportable(d.params[0]); var dtsm = dtsModules[d.namespace]; if (!dtsm) { dtsm = dtsModules[d.namespace] = ['declare module ' + d.namespace + ' {', '}']; } var dtsPart = []; var dtsEntitySetPart = []; var srcPart = ''; if (d.baseType == '$data.Enum') { dtsPart.push(' export enum ' + d.typeName.split('.').pop() + ' {'); if (d.params[3] && Object.keys(d.params[3]).length > 0) { Object.keys(d.params[3]).forEach(dp => dtsPart.push(' ' + d.params[3][dp].name + ',')); } srcPart += 'types["' + d.params[0] + '"] = $data.createEnum("' + d.params[0] + '", [\n' + Object.keys(d.params[3]).map(dp => ' ' + this._createPropertyDefString(d.params[3][dp])).join(',\n') + '\n]);\n\n'; } else { dtsPart.push(' export class ' + d.typeName.split('.').pop() + ' extends ' + d.baseType + ' {'); if (d.baseType == self.options.contextType) { dtsPart.push(' onReady(): Promise<' + d.typeName.split('.').pop() + '>;'); dtsPart.push(''); } else { dtsPart.push(' constructor();'); var ctr = ' constructor(initData: { '; if (d.params[2] && Object.keys(d.params[2]).length > 0) { ctr += Object.keys(d.params[2]).map(dp => dp + '?: ' + (d.params[2][dp].type == 'Array' ? d.params[2][dp].elementType + '[]' : d.params[2][dp].type)).join('; '); } ctr += ' });'; dtsPart.push(ctr); dtsPart.push(''); } var typeName = d.baseType; if (d.baseType == this.options.contextType) { srcPart += 'exports.type = '; contextFullName = d.typeName; } srcPart += 'types["' + d.params[0] + '"] = ' + (typeName == this.options.baseType || typeName == this.options.contextType ? ('$data("' + typeName + '")') : 'types["' + typeName + '"]') + '.extend("' + d.params[0] + '", '; if (d.params[2] && Object.keys(d.params[2]).length > 0) { srcPart += '{\n' + Object.keys(d.params[2]).map(dp => ' ' + dp + ': ' + this._createPropertyDefString(d.params[2][dp])).join(',\n') + '\n}'; Object.keys(d.params[2]).forEach(dp => { const definition = d.params[2][dp]; if (definition.type == this.options.entitySetType && definition.actions) { dtsPart.push(' ' + dp + ': ' + dp + ';'); dtsEntitySetPart.push(' export class ' + dp + ' extends $data.EntitySet<typeof ' + definition.elementType + ', ' + definition.elementType + '> {'); Object.keys(definition.actions).forEach(a => dtsEntitySetPart.push(' ' + a + ': ' + this._typeToTS(definition.actions[a].type, definition.actions[a].elementType, definition.actions[a]) + ';')); dtsEntitySetPart.push(' }'); dtsEntitySetPart.push(''); } else { dtsPart.push(' ' + dp + ': ' + this._typeToTS(definition.type, definition.elementType, definition) + ';'); } }); } else srcPart += 'null'; if (d.params[3] && Object.keys(d.params[3]).length > 0) { srcPart += ', {\n' + Object.keys(d.params[3]).map(dp => ' ' + dp + ': ' + this._createPropertyDefString(d.params[3][dp])).join(',\n') + '\n}'; } srcPart += ');\n\n'; } types.src += srcPart; dtsPart.push(' }'); dtsPart.push(...dtsEntitySetPart); dtsm.splice(1, 0, dtsPart.join('\n')); if (this.options.debug) console.log('Type generated:', d.params[0]); if (this.options.generateTypes !== false) { var baseType = this.options.container.resolveType(d.baseType); var type = baseType.extend.apply(baseType, d.params); this.annotationHandler.addAnnotation(type); return type; } })); this.addExportables(types); types.src += 'var ctxType = exports.type;\n' + 'exports.factory = function(config){\n' + ' if (ctxType){\n' + ' var cfg = $data.typeSystem.extend({\n' + ' name: "oData",\n' + ' oDataServiceHost: "' + (this.options.url && this.options.url.replace('/$metadata', '') || '') + '",\n' + ' withCredentials: ' + (this.options.withCredentials || false) + ',\n' + ' maxDataServiceVersion: "' + (this.options.maxDataServiceVersion || '4.0') + '"\n' + ' }, config);\n' + ' return new ctxType(cfg);\n' + ' }else{\n' + ' return null;\n' + ' }\n' + '};\n\n'; if (this.options.autoCreateContext) { var contextName = typeof this.options.autoCreateContext == 'string' ? this.options.autoCreateContext : 'context'; types.src += 'exports["' + contextName + '"] = exports.factory();\n\n'; } types.src += this.annotationHandler.annotationsText(); types.src += '});'; // declare modules types.dts += Object.keys(dtsModules) .filter(m => dtsModules[m] && dtsModules[m].length > 2) .map(m => dtsModules[m].join('\n\n')) .join('\n\n'); // export modules types.dts += Object.keys(dtsModules) .filter(m => dtsModules[m] && dtsModules[m].length > 2) .map(m => m.split(".")[0]) .filter((v, i, a) => a.indexOf(v) === i) // distinct .map(m => '\n\nexport {' + m + ' as ' + m + '}') .join(''); if (contextFullName) { var mod = ['\n\nexport var type: typeof ' + contextFullName + ';', 'export var factory: (config:any) => ' + contextFullName + ';']; if (this.options.autoCreateContext) { var contextName = typeof this.options.autoCreateContext == 'string' ? this.options.autoCreateContext : 'context'; mod.push('export var ' + contextName + ': ' + contextFullName + ';'); } types.dts += mod.join('\n'); } if (this.options.generateTypes === false) { types.length = 0; } return types; } _createPropertyDefString(definition) { if (definition.concurrencyMode) { return JSON.stringify(definition).replace('"concurrencyMode":"fixed"}', '"concurrencyMode":$data.ConcurrencyMode.Fixed}'); } else { return JSON.stringify(definition); } } _typeToTS(type, elementType, definition) { if (type == this.options.entitySetType) { return '$data.EntitySet<typeof ' + elementType + ', ' + elementType + '>'; } else if (type == '$data.Queryable') { return '$data.Queryable<' + elementType + '>'; } else if (type == this.options.collectionBaseType) { return elementType + '[]'; } else if (type == '$data.ServiceAction') { var t = this._typeToTS(definition.returnType, definition.elementType, definition); if (!t) t = 'Promise<void>'; else if (t.indexOf('$data.Queryable') < 0) t = 'Promise<' + t + '>'; return '{ (' + (definition.params.length > 0 ? definition.params.map(p => p.name + ': ' + this._typeToTS(p.type, p.elementType, p)).join(', ') : '') + '): ' + t + '; }'; } else if (type == '$data.ServiceFunction') { var t = this._typeToTS(definition.returnType, definition.elementType, definition); if (t.indexOf('$data.Queryable') < 0) t = 'Promise<' + t + '>'; return '{ (' + (definition.params.length > 0 ? definition.params.map(p => p.name + ': ' + this._typeToTS(p.type, p.elementType, p)).join(', ') : '') + '): ' + t + '; }'; } else return type; } orderTypeDefinitions(typeDefinitions) { let contextTypes = typeDefinitions.filter(t => t.type === 'context'); let ordered = []; let dependants = [].concat(typeDefinitions.filter(t => t.type !== 'context')); let addedTypes; let baseType = this.options.baseType; let dependantCount = Number.MAX_VALUE; while (dependants.length) { var dependantItems = [].concat(dependants); dependants.length = 0; dependantItems.forEach(typeDef => { if (dependantCount === dependantItems.length || typeDef.type !== "entity" || typeDef.baseType === baseType || ordered.some(t => t.typeName === typeDef.baseType)) { ordered.push(typeDef); } else { dependants.push(typeDef); } }); dependantCount = dependantItems.length; } return ordered.concat(contextTypes); } storeExportable(typesStr) { let typesArr = typesStr.split("."); let container = this.storedTypes; typesArr.forEach((current) => { if (typesArr[typesArr.length - 1] === current) { container[current] = "@@" + typesStr + "@@"; } else { if (!container[current]) { container[current] = {}; } container = container[current]; } }); } addExportables(meta) { for (let key in this.storedTypes) { let types = "exports." + key + " = " + JSON.stringify(this.storedTypes[key], null, 2) .replace(/"@@/g, "types[\"") .replace(/@@"/g, "\"]") + ";\n\n"; meta.src += types; } } } exports.Metadata = Metadata; //# sourceMappingURL=metadata.js.map