Fix all the bugs (it wörks now)

This commit is contained in:
Daniel Kluge
2023-11-01 20:39:30 +01:00
parent 7d21df351b
commit cab50cda6a
12 changed files with 104 additions and 79 deletions
+11 -5
View File
@@ -2,7 +2,7 @@ import type { Express } from "express";
import express from "express";
import { jsonization, types } from "@aas-core-works/aas-core3.0-typescript";
import AASInterfaceServer from "../server";
import type { OnRequestCallback, Request } from "types/requests";
import type { OnRequestCallback, Request } from "../types/requests";
import Traverser from "../helper/traverser";
export type Config = {
@@ -11,8 +11,8 @@ export type Config = {
}
export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
public readonly name: string = "HTTPInterfaceServer";
public readonly supportsSubscriptions: boolean = false; // TODO, longpolling?
public static serverInterfaceName: string = "HTTPInterfaceServer";
public static supportsSubscriptions: boolean = false; // TODO, longpolling?
private app: Express = express();
private server: any = null;
@@ -62,6 +62,10 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
}
}
this.app.use("/*", (req, res, next) => {
console.log(req.originalUrl); next();
});
// /aas
this.app.get("/aas", (_, res) => res.json(jsonization.toJsonable(this.aas)).end());
this.app.put("/aas", NOT_IMPLEMENTED);
@@ -88,8 +92,10 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
this.app.put("/aas/submodels/:smId", NOT_IMPLEMENTED);
this.app.patch("/aas/submodels/:smId", NOT_IMPLEMENTED);
this.app.delete("/aas/submodels/:smId", NOT_IMPLEMENTED);
// /aas/submodels/:smId/submodel-elements
this.app.use("/aas/submodels/:smId/submodel-elements", (req, res) => {
this.app.use("/aas/submodels/:smId/submodel-elements", (req, res, next) => {
if (req.path !== "/") return next();
if (req.method !== "GET") return res.status(501).end();
const sm = getSM(req.params.smId);
@@ -101,7 +107,6 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
// Main function
this.app.use("/aas/submodels/:smId/submodel-elements/*", (req, res) => {
const idShortPathString = (req.params as any)[0];
console.log(idShortPathString);
if (idShortPathString.endsWith("/")) res.status(400).end();
const sm = getSM(req.params.smId);
@@ -126,6 +131,7 @@ export default class HTTPInterfaceServer extends AASInterfaceServer<Config> {
const idShortPath = endingMatch ? idShortPathString.replace(new RegExp(`${endingMatch}$`), "").split("/") : idShortPathString.split("/");
console.log(idShortPath, endingMatch)
switch (endingMatch) {
case undefined: {
+14 -9
View File
@@ -1,12 +1,12 @@
import * as mqtt from "mqtt";
import InterfaceConnectionObject from "interfaceConnectionObject";
import InterfaceConnectionObject from "../interfaceConnectionObject";
import type { types } from "@aas-core-works/aas-core3.0-typescript";
export default class MQTTConnector extends InterfaceConnectionObject<mqtt.IClientOptions> {
public readonly name: string = "MQTT Connector";
public readonly uriProtocol: string[] = ["mqtt", "mqtts"];
public readonly connectionType: "ON_DEMAND" | "PERMANENT" = "PERMANENT";
public readonly supportsSubscriptions: boolean = true;
public static readonly connectorName: string = "MQTT Connector";
public static readonly uriProtocol: string[] = ["mqtt", "mqtts"];
public static readonly connectionType: "ON_DEMAND" | "PERMANENT" = "PERMANENT";
public static readonly supportsSubscriptions: boolean = true;
private client: mqtt.MqttClient|null = null;
@@ -15,6 +15,7 @@ export default class MQTTConnector extends InterfaceConnectionObject<mqtt.IClien
public connect(): boolean {
if (!this.client) this.client = mqtt.connect(this.endpointMetadata.base, this.connectionParameter);
this.client.on("message", (topic, message) => {
//console.log(`${topic}: ${message.toString("utf-8")}`)
// If json is expected you could parse it here
this.messageStore[topic] = message.toString("utf-8");
// Notify observers
@@ -32,13 +33,18 @@ export default class MQTTConnector extends InterfaceConnectionObject<mqtt.IClien
}
public readProperty(prop: types.Property): any {
console.log(prop);
const cc = this.mapper.get(prop);
console.log(cc);
if (cc === undefined) return null;
const errorReturn = cc.default ?? null;
if (!this.client || !this.client.connected) return errorReturn;
const value = cc.forms.map(f => this.messageStore[f.href.substring(1)]).filter(v => v !== undefined)[0];
console.log(this.messageStore)
const value = this.messageStore[cc.forms.href.substring(1)];
console.log(value)
if (value === undefined) return errorReturn;
switch (cc.type) {
case "integer":
@@ -58,7 +64,7 @@ export default class MQTTConnector extends InterfaceConnectionObject<mqtt.IClien
const cc = this.mapper.get(prop);
if (cc === undefined || !this.client || !this.client.connected) return false;
const topic = cc.forms.map(f => f.href.substring(1))[0];
const topic = cc.forms.href.substring(1);
if (topic === undefined) return false;
this.client.publish(topic, value.toString());
@@ -70,10 +76,9 @@ export default class MQTTConnector extends InterfaceConnectionObject<mqtt.IClien
const cc = this.mapper.get(prop);
if (cc === undefined || !cc.observable || !this.client || !this.client.connected) return false;
cc.forms.map(f => f.href.substring(1)).forEach(topic => {
const topic = cc.forms.href.substring(1);
if (this.observerStore[topic] === undefined) this.observerStore[topic] = [callback];
else this.observerStore[topic].push(callback);
});
return true;
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { types } from "@aas-core-works/aas-core3.0-typescript";
import { RelationshipElement } from "@aas-core-works/aas-core3.0-typescript/dist/types/types";
import { ResolvedRelationshipElement } from "types/common";
import { ResolvedRelationshipElement } from "../types/common";
const GLOBALLY_IDENTIFIABLES = [types.KeyTypes.GlobalReference, types.KeyTypes.AssetAdministrationShell, types.KeyTypes.ConceptDescription, types.KeyTypes.Identifiable, types.KeyTypes.Submodel]
+3 -1
View File
@@ -1,10 +1,12 @@
import MultiMessageBroker from "./multimessageBroker";
import HTTPInterfaceServer from "./example_modules/httpInterfaceServer";
import FileImporter from "./helper/fileImporter";
import MQTTConnector from "./example_modules/mqttConnector";
const aas = FileImporter.readAASByPath("../AID-AIMC-Full-Example-20230911T1608.json");
const aas = FileImporter.readAASByPath("../owntest.json");
const broker = new MultiMessageBroker();
broker.registerInterfaceConnection({ interfaceConnection: MQTTConnector, config: { reconnectPeriod: 1000 }})
broker.registerAAS({ aas, serverInterfaces: { serverInterface: HTTPInterfaceServer, config: { bindPort: 3000, bindAddress: "0.0.0.0" } } });
broker.prepare();
+5 -5
View File
@@ -1,17 +1,17 @@
import type { types } from "@aas-core-works/aas-core3.0-typescript";
import AIMCMapper from "./parser/AIMCMapper";
import { v4 } from "uuid";
import type { EndpointMetadata } from "types/aidConf";
import type { EndpointMetadata } from "./types/aidConf";
type ConnectionType = "ON_DEMAND" | "PERMANENT";
export type OnEventCallback = (response: any) => void
export default abstract class InterfaceConnectionObject<ConfigInterface> {
public abstract readonly name: string;
public abstract readonly uriProtocol: string[]|string;
public abstract readonly connectionType: ConnectionType;
public abstract readonly supportsSubscriptions: boolean;
public static readonly connectorName: string;
public static readonly uriProtocol: string[]|string;
public static readonly connectionType: ConnectionType;
public static readonly supportsSubscriptions: boolean;
protected readonly observerStore: Record<any, ((value: any) => void)[]> = {};
protected readonly eventSubStore: Record<any, ((value: any) => void)[]> = {};
+18 -6
View File
@@ -1,9 +1,10 @@
import { types } from "@aas-core-works/aas-core3.0-typescript";
import type AASInterfaceServer from "./server";
import type { Request } from "types/requests";
import type { Request } from "./types/requests";
import type InterfaceConnectionObject from "interfaceConnectionObject";
import AIMCMapper from "./parser/AIMCMapper";
import AIDParser from "./parser/AIDParser";
import { EndpointMetadata } from "types/aidConf";
type AASRegistration = {
aas: types.Environment,
@@ -55,7 +56,7 @@ export default class MultiMessageBroker {
registration.serverInstances = registration.serverInterfaces.map(serverInterface => {
try {
// @ts-ignore
const server = new serverInterface.serverInterface<typeof serverInterface.config>(serverInterface.config, registration.aas, registration.mappingConfiguration, (req: Request) => this.onInterfaceRequest(req, registration));
const server = new serverInterface.serverInterface<typeof serverInterface.config>(serverInterface.config, registration.aas, (req: Request) => this.onInterfaceRequest(req, registration));
server.prepare();
return server;
} catch (e) {
@@ -68,13 +69,23 @@ export default class MultiMessageBroker {
if (!interfaceDescription) continue;
const endpoints = Object.values(interfaceDescription).flatMap(idEntries => idEntries.map(entry => entry.EndPointMetadata));
const uniqueProtocols = [...new Set(endpoints.map(endpoint => new URL(endpoint.base).protocol))];
const uniqueProtocols: Record<string, EndpointMetadata[]> = {};
for (const protocol of uniqueProtocols) {
const connectorProto = this.interfaceConnections.find(connection => connection.interfaceConnection.prototype.uriProtocol.includes(protocol))
endpoints.forEach(endpoint => {
const pUrl = new URL(endpoint.base);
if (!uniqueProtocols[pUrl.protocol]) uniqueProtocols[pUrl.protocol] = [endpoint];
else uniqueProtocols[pUrl.protocol].push(endpoint);
});
for (const [protocol, endpoints] of Object.entries(uniqueProtocols)) {
const connectorProto = this.interfaceConnections.find(connection => {
const protos = typeof connection.interfaceConnection.uriProtocol === "string" ? [connection.interfaceConnection.uriProtocol] : connection.interfaceConnection.uriProtocol;
return protos.map(proto => proto.endsWith(":") ? proto : proto + ":").includes(protocol.endsWith(":") ? protocol : protocol + ":");
});
if (connectorProto === undefined) continue;
// @ts-ignore
registration.connectorInterfaces.push(new connectorProto.interfaceConnection(connectorProto.config, registration.mappingConfiguration, (response: any) => this.onConnectorEvent(response)));
for (const ep of endpoints) registration.connectorInterfaces.push(new connectorProto.interfaceConnection(connectorProto.config, ep, registration.mappingConfiguration, (response: any) => this.onConnectorEvent(response)));
}
}
@@ -85,6 +96,7 @@ export default class MultiMessageBroker {
if (!this.prepared) this.prepare();
for (const registration of this.aasRegistrations) {
registration.connectorInterfaces.forEach(connector => connector.connect());
registration.serverInstances?.forEach(server => server?.run());
}
}
+17 -25
View File
@@ -1,12 +1,12 @@
import * as aasCore from "@aas-core-works/aas-core3.0-typescript";
import type { AssetInterfacesDescription, EndpointMetadata, InterfaceDescription, InterfaceMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty, InterfacePropertyForm } from "types/aidConf";
import type { AssetInterfacesDescription, EndpointMetadata, InterfaceDescription, InterfaceMetadata, InterfaceAction, InterfaceEvent, InterfaceProperty, InterfacePropertyForm } from "../types/aidConf";
import { AvailableEndpoint, endpointAvailable } from "../types/common";
import Traverser from "../helper/traverser";
export default class AIDParser {
public static parse(env: aasCore.types.Environment): AssetInterfacesDescription | null {
const sm = Traverser.findSMByIdShort(env, "AssetInterfaceDescription");
const sm = Traverser.findSMByIdShort(env, "AssetInterfacesDescription");
if (sm === null || sm.submodelElements === null) return null;
const parsed = {} as AssetInterfacesDescription;
@@ -28,11 +28,11 @@ export default class AIDParser {
}
private static parseAIDEntry(entry: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceDescription | null {
const title = Traverser.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "title");
const title = Traverser.findElement(entry, element => (element as any).idShort?.toLocaleLowerCase() === "title");
const titleString = title !== null && aasCore.types.isProperty(title) && title.value ? title.value : "";
const ep = Traverser.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "endpointmetadata");
const im = Traverser.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "interfacemetadata");
const ep = Traverser.findElement(entry, element => (element as any).idShort?.toLocaleLowerCase() === "endpointmetadata");
const im = Traverser.findElement(entry, element => (element as any).idShort?.toLocaleLowerCase() === "interfacemetadata");
if (ep === null || !aasCore.types.isSubmodelElementCollection(ep) || !ep.value || im === null || !aasCore.types.isSubmodelElementCollection(im) || !im.value) return null;
const parsedEp = AIDParser.parseEndpointMetaData(ep, endpointProtocol);
@@ -53,7 +53,7 @@ export default class AIDParser {
const parsed = {} as EndpointMetadata;
for (const key of ["base", "contentType"]) {
const prop = Traverser.findElement(endpoint, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase());
const prop = Traverser.findElement(endpoint, element => (element as any).idShort?.toLocaleLowerCase() === key.toLocaleLowerCase());
if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) return null; // Mandatory
else parsed[key] = prop.value;
}
@@ -73,7 +73,7 @@ export default class AIDParser {
events: []
} as InterfaceMetadata;
const prop = Traverser.findElement(interfaceMeta, element => (element as any).idShort.toLocaleLowerCase() === "properties");
const prop = Traverser.findElement(interfaceMeta, element => (element as any).idShort?.toLocaleLowerCase() === "properties");
if (prop === null || !aasCore.types.isSubmodelElementList(prop) || !prop.value) return null; // Mandatory
for (const element of prop.overValueOrEmpty()) {
if (!aasCore.types.isSubmodelElementCollection(element)) continue;
@@ -82,7 +82,7 @@ export default class AIDParser {
parsed.properties.push(parsedProp);
}
const actions = Traverser.findElement(interfaceMeta, element => (element as any).idShort.toLocaleLowerCase() === "actions");
const actions = Traverser.findElement(interfaceMeta, element => (element as any).idShort?.toLocaleLowerCase() === "actions");
if (actions === null || !aasCore.types.isSubmodelElementList(actions) || !actions.value) return null; // Mandatory
for (const element of actions.overValueOrEmpty()) {
if (!aasCore.types.isSubmodelElementCollection(element)) continue;
@@ -95,32 +95,24 @@ export default class AIDParser {
}
public static parseInterfaceMetadataProperty(prop: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfaceProperty | null {
if (!aasCore.types.isSubmodelElementCollection(prop) || !prop.value || prop.idShort?.toLocaleLowerCase() !== "properties") return null;
if (!aasCore.types.isSubmodelElementCollection(prop) || !prop.value) return null;
const parsed = {} as InterfaceProperty;
// This does not matter in any way for the interface
/* for (const key of ["title", "observeable", "type"]) {
const prop = AASStore.findElement(prop, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase());
const prop = AASStore.findElement(prop, element => (element as any).idShort?.toLocaleLowerCase() === key.toLocaleLowerCase());
if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) continue; // Not mandatory
else parsed[key] = prop.value;
} */
const parsedForms = [] as InterfacePropertyForm[];
const forms = Traverser.findElement(prop, element => (element as any).idShort.toLocaleLowerCase() === "forms");
if (forms === null || !aasCore.types.isSubmodelElementList(forms) || !forms.value) return null; // Mandatory
for (const element of forms.overValueOrEmpty()) {
if (!aasCore.types.isSubmodelElementCollection(element)) continue;
const parsedForm = AIDParser.parseInterfaceMetadataPropertyForm(element, endpointProtocol);
if (parsedForm === null) continue;
parsedForms.push(parsedForm);
}
} */;
const forms = Traverser.findElement(prop, element => (element as any).idShort?.toLocaleLowerCase() === "forms");
if (forms === null || !aasCore.types.isSubmodelElementCollection(forms) || !forms.value) return null; // Mandatory
const parsedForm = AIDParser.parseInterfaceMetadataPropertyForm(forms, endpointProtocol);
if (parsedForm === null) return null;
// TODO
// Other values based on endpoint protocol
return { ...parsed, forms: parsedForms } as InterfaceProperty;
return { ...parsed, forms: parsedForm } as InterfaceProperty;
}
private static parseInterfaceMetadataPropertyForm(form: aasCore.types.SubmodelElementCollection, endpointProtocol?: AvailableEndpoint): InterfacePropertyForm | null {
@@ -129,7 +121,7 @@ export default class AIDParser {
const parsed = {} as any;
for (const key of ["href", "contentType"]) {
const prop = Traverser.findElement(form, element => (element as any).idShort.toLocaleLowerCase() === key.toLocaleLowerCase());
const prop = Traverser.findElement(form, element => (element as any).idShort?.toLocaleLowerCase() === key.toLocaleLowerCase());
if (prop === null || !aasCore.types.isProperty(prop) || !prop.value) return null; // Mandatory
else parsed[key] = prop.value;
}
+14 -6
View File
@@ -1,7 +1,7 @@
import { types } from "@aas-core-works/aas-core3.0-typescript";
import { ConnectionConfiguration, AIMCMap } from "types/aimcConf";
import { ConnectionConfiguration, AIMCMap } from "../types/aimcConf";
import AIMCParser from "./AIMCParser";
import Traverser from "helper/traverser";
import Traverser from "../helper/traverser";
import AIDParser from "./AIDParser";
import { SubmodelElementCollection } from "@aas-core-works/aas-core3.0-typescript/dist/types/types";
@@ -31,14 +31,22 @@ export default class AIMCMapper {
const resolved = Traverser.resolveRelationship(this.env, ssm);
if (resolved === null) continue;
const parsedPropConf = AIDParser.parseInterfaceMetadataProperty(resolved.second as SubmodelElementCollection);
let {first, second} = resolved;
let parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as SubmodelElementCollection);
if (parsedPropConf === null) {
// maybe the property is in the first element
[first, second] = [second, first];
parsedPropConf = AIDParser.parseInterfaceMetadataProperty(first as SubmodelElementCollection);
}
if (parsedPropConf === null) continue;
this.map.set(resolved.first, {
this.map.set(second, {
...ep,
...parsedPropConf
});
}
console.log(this.map)
}
}
@@ -65,8 +73,8 @@ export default class AIMCMapper {
const result = [];
for (const [e, cc] of this.map.entries()) {
const paths = cc.forms.map(f => cc.base + f.href);
if (paths.includes(path)) result.push(e);
const formsPath = cc.base + cc.forms.href;
if (formsPath === path) result.push(e);
}
return result;
+13 -13
View File
@@ -1,12 +1,12 @@
import * as aasCore from "@aas-core-works/aas-core3.0-typescript";
import Traverser from "helper/traverser";
import { AssetInterfacesMappingConfiguration, MappingConfEntry, MappingConfiguration } from "types/aimcConf";
import { endpointAvailable, ResolvedRelationshipElement } from "types/common";
import Traverser from "../helper/traverser";
import { AssetInterfacesMappingConfiguration, MappingConfEntry, MappingConfiguration } from "../types/aimcConf";
import { endpointAvailable, ResolvedRelationshipElement } from "../types/common";
export default class AIMCParser {
public static parse(env: aasCore.types.Environment): AssetInterfacesMappingConfiguration | null {
const sm = Traverser.findSMByIdShort(env, "AssetInterfaceMappingConfiguration");
const sm = Traverser.findSMByIdShort(env, "AssetInterfacesMappingConfiguration");
if (sm === null || sm.submodelElements === null) return null;
const parsed = {} as AssetInterfacesMappingConfiguration;
@@ -28,12 +28,12 @@ export default class AIMCParser {
}
public static parseAIMCEntry(entry: aasCore.types.SubmodelElementCollection, environment: aasCore.types.Environment): MappingConfEntry | null {
const ep = Traverser.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "endpointmetadatareference");
const mc = Traverser.findElement(entry, element => (element as any).idShort.toLocaleLowerCase() === "mappingconfiguration");
if (ep === null || !aasCore.types.isReference(ep) || !ep.keys || !ep.keys[0] || !mc || !aasCore.types.isSubmodelElementList(mc) || !mc.value) return null;
const ep = Traverser.findElement(entry, element => (element as any).idShort?.toLocaleLowerCase() === "endpointmetadatareference");
const mc = Traverser.findElement(entry, element => (element as any).idShort?.toLocaleLowerCase() === "mappingconfiguration");
if (ep === null || !aasCore.types.isReferenceElement(ep) || !ep.value || !ep.value.keys || !ep.value.keys[0] || !mc || !aasCore.types.isSubmodelElementList(mc) || !mc.value) return null;
const resolvedEp = Traverser.resolveReference(environment, ep);
if (resolvedEp === null || !aasCore.types.isSubmodelElementCollection(resolvedEp) || resolvedEp.idShort?.toLocaleLowerCase() !== "endpointmetadatareference") return null;
const resolvedEp = Traverser.resolveReference(environment, ep.value);
if (resolvedEp === null || !aasCore.types.isSubmodelElementCollection(resolvedEp) || resolvedEp.idShort?.toLocaleLowerCase() !== "endpointmetadata") return null;
const parsedMc: (MappingConfiguration|null)[] = []
for (const conf of mc.overValueOrEmpty()) {
@@ -57,13 +57,13 @@ export default class AIMCParser {
SourceSinkMappings: []
} as any;
const ifMetaRef = Traverser.findElement(conf, element => (element as any).idShort.toLocaleLowerCase() === "interfacemetadatareference");
if (!ifMetaRef || !aasCore.types.isReference(ifMetaRef) || !ifMetaRef.keys) return null;
const ifMeta = Traverser.resolveReference(env, ifMetaRef);
const ifMetaRef = Traverser.findElement(conf, element => (element as any).idShort?.toLocaleLowerCase() === "interfacemetadatareference");
if (!ifMetaRef || !aasCore.types.isReferenceElement(ifMetaRef) || !ifMetaRef.value || !ifMetaRef.value.keys || !ifMetaRef.value.keys[0]) return null;
const ifMeta = Traverser.resolveReference(env, ifMetaRef.value);
if (!ifMeta || !aasCore.types.isSubmodelElementCollection(ifMeta) || ifMeta.idShort?.toLocaleLowerCase() !== "interfacemetadata") return null;
parsed.InterfaceMetaDataReference = ifMeta;
const sourceSinkMappings = Traverser.findElement(conf, element => (element as any).idShort.toLocaleLowerCase() === "sourcesinkmappings");
const sourceSinkMappings = Traverser.findElement(conf, element => (element as any).idShort?.toLocaleLowerCase() === "sourcesinkmappings");
if (!sourceSinkMappings || !aasCore.types.isSubmodelElementList(sourceSinkMappings) || !sourceSinkMappings.value) return null;
for (const ssm of sourceSinkMappings.overValueOrEmpty()) {
+3 -3
View File
@@ -1,9 +1,9 @@
import { types } from "@aas-core-works/aas-core3.0-typescript";
import type { Request, OnRequestCallback } from "types/requests";
import type { Request, OnRequestCallback } from "./types/requests";
export default abstract class AASInterfaceServer<ConfigInterface> {
public abstract readonly name: string;
public abstract readonly supportsSubscriptions: boolean;
public static readonly serverInterfaceName: string;
public static readonly supportsSubscriptions: boolean;
constructor(
protected readonly config: ConfigInterface,
+1 -1
View File
@@ -30,7 +30,7 @@ export type InterfaceMetadata = {
export type InterfaceProperty = {
title?: string;
observable?: boolean;
forms: InterfacePropertyForm[];
forms: InterfacePropertyForm;
type?: string;
enum?: string[];
const?: any;
+1 -1
View File
@@ -30,7 +30,7 @@
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "src", /* Specify the base directory to resolve non-relative module names. */
"paths": {
"types/*": ["types/*"]
"types/*": ["types/*"],
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */