diff --git a/dist/connector.d.ts b/dist/connector.d.ts index e94e3af..537f136 100644 --- a/dist/connector.d.ts +++ b/dist/connector.d.ts @@ -48,6 +48,8 @@ declare class Connector extends HKEntity { public roles: { [x: string]: string; }; + properties: any; + metaProperties: any; /** * Adds a new role to this connector. * @@ -96,6 +98,6 @@ declare class Connector extends HKEntity { }; } declare namespace Connector { - const type: string; + const type: "connector"; } import HKEntity = require("./hkentity"); diff --git a/dist/connectorclass.d.ts b/dist/connectorclass.d.ts index e271c8f..14286dd 100644 --- a/dist/connectorclass.d.ts +++ b/dist/connectorclass.d.ts @@ -2,10 +2,10 @@ * Copyright (c) 2016-present, IBM Research * Licensed under The MIT License [see LICENSE for details] */ -export var HIERARCHY: string; -export var FACTS: string; -export var REASONING: string; -export var CONSTRAINT: string; -export var CAUSAL: string; -export var POSSIBILITY: string; -export var POSSIBILITYRESOLVER: string; +export const HIERARCHY: "h"; +export const FACTS: "f"; +export const REASONING: "r"; +export const CONSTRAINT: "c"; +export const CAUSAL: "C"; +export const POSSIBILITY: "p"; +export const POSSIBILITYRESOLVER: "P"; diff --git a/dist/constants.d.ts b/dist/constants.d.ts index 1b39eba..3e75fe1 100644 --- a/dist/constants.d.ts +++ b/dist/constants.d.ts @@ -2,5 +2,5 @@ * Copyright (c) 2016-present, IBM Research * Licensed under The MIT License [see LICENSE for details] */ -export var LAMBDA: string; -export var MIMETYPE: string; +export const LAMBDA: "λ"; +export const MIMETYPE: "hk:mimeType"; diff --git a/dist/context.d.ts b/dist/context.d.ts index a66a098..9530077 100644 --- a/dist/context.d.ts +++ b/dist/context.d.ts @@ -10,8 +10,9 @@ declare class Context extends Node { * @param {string | null} [parent] Parent id. */ constructor(id?: string | null | undefined, parent?: string | null | undefined); + type: "context"; } declare namespace Context { - const type: string; + const type: "context"; } import Node = require("./node"); diff --git a/dist/datasource/hkdatasource.d.ts b/dist/datasource/hkdatasource.d.ts index 569a981..6068588 100644 --- a/dist/datasource/hkdatasource.d.ts +++ b/dist/datasource/hkdatasource.d.ts @@ -147,6 +147,18 @@ declare class HKDatasource { }, payload: object, callback: (err: string, entities: { [x: string]: HKEntity; }) => any): void; + /** + * @callback GetTrailCallback + * @param {string} err An error object that indicate if the operation was succesful or not + * @param {object} trail + */ + /** + * Fetch trail + * + * @param {string} trailId The trail id to retrieve their nested entities. + * @param {GetTrailCallback} callback Callback with the entities + */ + fetchTrail(trailId: string, callback?: (err: string, trail: object) => any): void; /** * Filter entities using CSS pattern `(TODO: document it better)` * diff --git a/dist/datasource/hkdatasource.js b/dist/datasource/hkdatasource.js index cf3e83c..28ed760 100644 --- a/dist/datasource/hkdatasource.js +++ b/dist/datasource/hkdatasource.js @@ -417,6 +417,40 @@ class HKDatasource { } }); } + /** + * @callback GetTrailCallback + * @param {string} err An error object that indicate if the operation was succesful or not + * @param {object} trail + */ + /** + * Fetch trail + * + * @param {string} trailId The trail id to retrieve their nested entities. + * @param {GetTrailCallback} callback Callback with the entities + */ + fetchTrail(trailId, callback = () => { }) { + let url = this.url + "repository/" + this.graphName + "/trail/" + encodeURIComponent(trailId); + request.get(url, this.options, (err, res) => { + // console.log(res.body); + if (!err) { + if (requestCompletedWithSuccess(res.statusCode)) { + try { + let entities = convertEntities(res.body); + callback(null, entities[trailId]); + } + catch (exp) { + callback(exp); + } + } + else { + callback(`Server responded with ${res.statusCode}. ${res.body}`); + } + } + else { + callback(err); + } + }); + } /** * Filter entities using CSS pattern `(TODO: document it better)` * diff --git a/dist/datasource/observer/clients/index.d.ts b/dist/datasource/observer/clients/index.d.ts index a830568..c47b151 100644 --- a/dist/datasource/observer/clients/index.d.ts +++ b/dist/datasource/observer/clients/index.d.ts @@ -2,7 +2,7 @@ * Copyright (c) 2016-present, IBM Research * Licensed under The MIT License [see LICENSE for details] */ -export var DefaultObserverClient: typeof import("./observerclient"); -export var ConfigurableObserverClient: typeof import("./configurableobserverclient"); -export var RestObserverClient: typeof import("./restobserverclient"); -export var RabbitMQObserverClient: typeof import("./rabbitmqobserverclient"); +export const DefaultObserverClient: typeof import("./observerclient"); +export const ConfigurableObserverClient: typeof import("./configurableobserverclient"); +export const RestObserverClient: typeof import("./restobserverclient"); +export const RabbitMQObserverClient: typeof import("./rabbitmqobserverclient"); diff --git a/dist/datasource/observer/clients/rabbitmqobserverclient.d.ts b/dist/datasource/observer/clients/rabbitmqobserverclient.d.ts index 4967183..cabd9a1 100644 --- a/dist/datasource/observer/clients/rabbitmqobserverclient.d.ts +++ b/dist/datasource/observer/clients/rabbitmqobserverclient.d.ts @@ -35,7 +35,9 @@ declare class RabbitMQObserverClient extends ConfigurableObserverClient { _channelWrapper: any; _setupFunction: ((channel: any) => Promise) | null; _queueName: any; + init(): Promise; _init(exchangeName: any): Promise; _isInitialized: boolean | undefined; + deinit(): Promise; } import ConfigurableObserverClient = require("./configurableobserverclient"); diff --git a/dist/datasource/observer/clients/restobserverclient.d.ts b/dist/datasource/observer/clients/restobserverclient.d.ts index 320c80d..5f9e354 100644 --- a/dist/datasource/observer/clients/restobserverclient.d.ts +++ b/dist/datasource/observer/clients/restobserverclient.d.ts @@ -25,5 +25,6 @@ declare class RestObserverClient extends ConfigurableObserverClient { _address: string; _listeningPath: string | null; _server: any; + deinit(): Promise; } import ConfigurableObserverClient = require("./configurableobserverclient"); diff --git a/dist/fi/fioperator.d.ts b/dist/fi/fioperator.d.ts index 6b04ff8..bb996e0 100644 --- a/dist/fi/fioperator.d.ts +++ b/dist/fi/fioperator.d.ts @@ -4,6 +4,6 @@ */ export = FIOperator; declare const FIOperator: Readonly<{ - NONE: string; - DESCRIPTION: string; + NONE: ""; + DESCRIPTION: "*"; }>; diff --git a/dist/hkgraph.d.ts b/dist/hkgraph.d.ts index d9de5ab..e6ebf8c 100644 --- a/dist/hkgraph.d.ts +++ b/dist/hkgraph.d.ts @@ -13,6 +13,7 @@ declare class HKGraph { connectors: {}; refs: {}; trails: {}; + actions: {}; bindsMap: {}; linkMap: {}; virtualLinkMap: {}; @@ -75,14 +76,14 @@ declare class HKGraph { deserialize(str: any): HKGraph; } declare namespace HKGraph { - const NODE_TYPE: string; - const VIRTUAL_NODE_TYPE: string; - const CONTEXT_TYPE: string; - const VIRTUAL_CONTEXT_TYPE: string; - const LINK_TYPE: string; - const VIRTUAL_LINK_TYPE: string; - const CONNECTOR_TYPE: string; - const INTERFACE: string; + const NODE_TYPE: "node"; + const VIRTUAL_NODE_TYPE: "virtualnode"; + const CONTEXT_TYPE: "context"; + const VIRTUAL_CONTEXT_TYPE: "virtualcontext"; + const LINK_TYPE: "link"; + const VIRTUAL_LINK_TYPE: "virtuallink"; + const CONNECTOR_TYPE: "connector"; + const INTERFACE: "interface"; } declare function generateId(model: any, length: any): any; import HKEntity = require("./hkentity"); diff --git a/dist/hkgraph.js b/dist/hkgraph.js index 52c1772..82f5a9a 100644 --- a/dist/hkgraph.js +++ b/dist/hkgraph.js @@ -11,6 +11,7 @@ const Reference = require("./reference"); const Trail = require("./trail"); const HKTypes = require("./types"); const shortId = require('shortid'); +const { Action } = require("./trail"); const VirtualContext = require("./virtualcontext"); const HKEntity = require("./hkentity"); const VirtualNode = require("./virtualnode"); @@ -26,6 +27,7 @@ class HKGraph { this.connectors = {}; this.refs = {}; this.trails = {}; + this.actions = {}; // Auxiliar maps this.bindsMap = {}; this.linkMap = {}; @@ -47,7 +49,8 @@ class HKGraph { this.links.hasOwnProperty(id) || this.connectors.hasOwnProperty(id) || this.refs.hasOwnProperty(id) || - this.trails.hasOwnProperty(id); + this.trails.hasOwnProperty(id) || + this.actions.hasOwnProperty(id); } /** * Update an entity @@ -66,7 +69,7 @@ class HKGraph { oldEntity.roles = entity.roles; oldEntity.className = entity.className; } - if (entity.type === HKTypes.NODE || entity.type === HKTypes.REFERENCE || entity.type === HKTypes.CONTEXT || entity.type === HKTypes.VIRTUAL_NODE || entity.type === HKTypes.VIRTUAL_CONTEXT) { + if (entity.type === HKTypes.NODE || entity.type === HKTypes.TRAIL || entity.type === HKTypes.REFERENCE || entity.type === HKTypes.CONTEXT || entity.type === HKTypes.VIRTUAL_NODE || entity.type === HKTypes.VIRTUAL_CONTEXT || entity.type === HKTypes.ACTION) { oldEntity.interfaces = entity.interfaces; } // Update parent @@ -173,6 +176,19 @@ class HKGraph { if (Trail.isValid(entity)) { newEntity = new Trail(entity); this.trails[entity.id] = newEntity; + this.contextMap[entity.id] = {}; + if (this.orphans.hasOwnProperty(entity.id)) { + this.contextMap[entity.id] = this.orphans[entity.id]; + delete this.orphans[entity.id]; + } + } + break; + } + case HKTypes.ACTION: + { + if (entity instanceof Trail.Action) { + newEntity = entity; + this.actions[entity.id] = newEntity; } break; } @@ -356,8 +372,13 @@ class HKGraph { { /* delete children trails? */ delete this.trails[id]; + delete this.contextMap[entity.id]; break; } + case Action.type: + { + delete this.actions[id]; + } } if (this.orphans.hasOwnProperty(entity.parent)) { delete this.orphans[entity.parent][id]; @@ -480,7 +501,7 @@ class HKGraph { c.id = null; return c; } - return this.nodes[id] || this.virtualNodes[id] || this.contexts[id] || this.virtualContexts[id] || this.virtualLinks[id] || this.links[id] || this.connectors[id] || this.refs[id] || this.trails[id] || null; + return this.nodes[id] || this.virtualNodes[id] || this.contexts[id] || this.virtualContexts[id] || this.virtualLinks[id] || this.links[id] || this.connectors[id] || this.refs[id] || this.trails[id] || this.actions[id] || null; } /** * Returns HK entities in this graph indexed by id. diff --git a/dist/index.js b/dist/index.js index 188c9cd..3bb9002 100644 --- a/dist/index.js +++ b/dist/index.js @@ -32,6 +32,7 @@ exports.CONNECTOR_TYPE = require("./types").CONNECTOR; exports.BIND_TYPE = require("./types").BIND; exports.INTERFACE = require("./types").INTERFACE; exports.TRAIL_TYPE = require("./types").TRAIL; +exports.ACTION_TYPE = require("./types").ACTION; exports.VIRTUAL_NODE_TYPE = require("./types").VIRTUAL_NODE; exports.VIRTUAL_CONTEXT_TYPE = require("./types").VIRTUAL_CONTEXT; exports.VIRTUAL_LINK_TYPE = require("./types").VIRTUAL_LINK; diff --git a/dist/link.d.ts b/dist/link.d.ts index df42d86..a68f7c6 100644 --- a/dist/link.d.ts +++ b/dist/link.d.ts @@ -54,6 +54,8 @@ declare class Link extends HKEntity { [x: string]: Object; }; }; + properties: any; + metaProperties: any; /** * Adds a new bind to this role; * @@ -91,6 +93,6 @@ declare class Link extends HKEntity { }; } declare namespace Link { - const type: string; + const type: "link"; } import HKEntity = require("./hkentity"); diff --git a/dist/node.d.ts b/dist/node.d.ts index 4547689..6711716 100644 --- a/dist/node.d.ts +++ b/dist/node.d.ts @@ -60,6 +60,14 @@ declare class Node extends HKEntity { }; }; }; + /** + * @public + * @type {Object. + */ + public properties: { + [x: string]: string | number | Object; + }; + metaProperties: any; /** * * @param {string} key Id of the interface @@ -88,7 +96,7 @@ declare class Node extends HKEntity { }; } declare namespace Node { - const type: string; + const type: "node"; } import HKEntity = require("./hkentity"); import FI = require("./fi/fi"); diff --git a/dist/reference.d.ts b/dist/reference.d.ts index 9656433..152372c 100644 --- a/dist/reference.d.ts +++ b/dist/reference.d.ts @@ -20,8 +20,15 @@ declare class Reference extends Node { * */ public ref: string | null; + type: "ref"; + serialize: () => { + id: any; + type: "ref"; + ref: any; + parent: any; + }; } declare namespace Reference { - const type: string; + const type: "ref"; } import Node = require("./node"); diff --git a/dist/roletypes.d.ts b/dist/roletypes.d.ts index b1e3c10..cd83553 100644 --- a/dist/roletypes.d.ts +++ b/dist/roletypes.d.ts @@ -2,8 +2,8 @@ * Copyright (c) 2016-present, IBM Research * Licensed under The MIT License [see LICENSE for details] */ -export var NONE: string; -export var SUBJECT: string; -export var OBJECT: string; -export var PARENT: string; -export var CHILD: string; +export const NONE: "n"; +export const SUBJECT: "s"; +export const OBJECT: "o"; +export const PARENT: "p"; +export const CHILD: "c"; diff --git a/dist/trail.d.ts b/dist/trail.d.ts index 6bb2f2a..948a90f 100644 --- a/dist/trail.d.ts +++ b/dist/trail.d.ts @@ -3,29 +3,83 @@ * Licensed under The MIT License [see LICENSE for details] */ export = Trail; -declare class Trail extends HKEntity { - static isValid(entity: any): boolean; - constructor(id: any, parent: any, ...args: any[]); +declare function Trail(id: any, actions: any, parent: any, ...args: any[]): void; +declare class Trail { + constructor(id: any, actions: any, parent: any, ...args: any[]); id: any; parent: any; + properties: any; metaproperties: any; interfaces: any; - children: any; - steps: any[] | undefined; - addStep(key: any, properties: any): void; - addInterface(key: any, type: any, properties: any): void; - createLinksFromSteps(): Connector[]; + actions: any; + type: "trail"; + updateAction(oldAction: any, newAction: any): void; + addAction(action: any): any; + removeAction(action: any): void; + action: any; + append(action: any): any; + prepend(action: any): any; + in(from?: null): any; + out(to?: null): any; + size(): number | undefined; + join(delimiter: any): any; + update(action: any, newAction: any): void; + remove(action: any): void; + getPrev(position: any, num?: number): any; + getNext(position: any, num?: number): any; + getPositionOf(action: any): number; + getActionAt(position: any): any; + search(eventId: null | undefined, filters: any): any; + loadActions(actions?: null): any; + toJSON(): { + id: any; + parent: any; + properties: any; + metaproperties: any; + interfaces: any; + actions: any[]; + type: "trail"; + }; serialize(): { id: any; parent: any; properties: any; metaproperties: any; interfaces: any; - type: string; + actions: any; + type: "trail"; }; } declare namespace Trail { - const type: string; + export const type: "trail"; + export { isValid }; + export { sort }; + export { Action }; + export { TrailNode }; +} +declare function isValid(entity: any): boolean; +declare function sort(actions?: null): any; +declare class Action { + constructor({ from, to, event, agent }?: { + from?: null | undefined; + to?: null | undefined; + event?: {} | undefined; + agent?: null | undefined; + }); + from: any; + to: any; + agent: any; + event: {}; + type: "action"; + id: any; + getTime(): number; + toString(): any; + toJSON(): any; +} +declare class TrailNode { + constructor(nodeId: any, nodeType: any, targetAnchor: any); + nodeId: any; + nodeType: any; + targetAnchor: any; + toString(): string; } -import HKEntity = require("./hkentity"); -import Connector = require("./connector"); diff --git a/dist/trail.js b/dist/trail.js index 2c1186c..2e2235d 100644 --- a/dist/trail.js +++ b/dist/trail.js @@ -1,113 +1,408 @@ -/** +/* * Copyright (c) 2016-present, IBM Research * Licensed under The MIT License [see LICENSE for details] */ 'use strict'; -const shortid = require('shortid'); +const { List, Item } = require('linked-list'); +// const shortid = require('shortid'); const Types = require('./types'); const HKEntity = require('./hkentity'); -const Connector = require('./connector'); -const Link = require('./link'); -const RoleTypes = require('./roletypes'); -const CONNECTOR_NAME = 'occurs'; -const vConnector = new Connector(CONNECTOR_NAME, 'f'); -vConnector.addRole('sub', RoleTypes.SUBJECT); -vConnector.addRole('obj', RoleTypes.OBJECT); -class Trail extends HKEntity { - constructor(id, parent) { - super(); - if (arguments[0] && typeof arguments[0] === 'object' && isValid(arguments[0])) { - let trail = arguments[0]; - this.id = trail.id || null; - this.parent = trail.parent || null; - this.properties = trail.properties || {}; - this.metaproperties = trail.metaproperties || {}; - this.interfaces = trail.interfaces || {}; - this.children = trail.children || []; - _loadSteps.call(this); +function Trail(id, actions, parent) { + if (arguments[0] && typeof arguments[0] === 'object' && isValid(arguments[0])) { + let trail = arguments[0]; + this.id = trail.id || null; + this.parent = trail.parent || null; + this.properties = trail.properties || {}; + this.metaproperties = trail.metaproperties || {}; + this.interfaces = trail.interfaces || {}; + this.actions = trail.actions || new List(); + if (this.actions && Object.keys(this.actions).length > 0) { + this.loadActions(); + } + } + else { + this.id = id || null; + this.parent = parent || null; + this.interfaces = {}; + this.properties = {}; + this.metaproperties = {}; + this.actions = actions || new List(); + } + this.type = Types.TRAIL; +} +Trail.prototype = Object.create(HKEntity.prototype); +//Trail.prototype.constructor = Trail; +// Trail.prototype = Object.assign(Trail.prototype, List.prototype); // Multiple inheritance with assign +// Update a given action in trail +Trail.prototype.updateAction = function (oldAction, newAction) { + oldAction.prepend(newAction); + oldAction.detach(); +}; +// Add an action to a trail respecting timestamp ordering +Trail.prototype.addAction = function (action) { + var current = this.actions.head; + while (current) { + if (new Date(current.event['timestamp']) - new Date(action.event['timestamp'])) { + return current.prepend(action); + } + current = current.next; + } + return this.actions.append(action); +}; +// Remove an action from trail using its reference +Trail.prototype.removeAction = function (action) { + var action; + if (typeof (action) === 'string') { + this.action = this.search(action); + //action not found + if (!this.action) { + return; + } + } + else { + this.action = action; + } + this.action.detach(); +}; +// `append` and `prepend` operations are +// handled by our linked list structure +Trail.prototype.append = function (action) { + return this.actions.append(action); +}; +Trail.prototype.prepend = function (action) { + return this.actions.prepend(action); +}; +// aliases `in` and `out` represent the input and +// output TrailNodes for a trail, which conceptually is +// done through the use of Port elements in Hyperknowledge +Trail.prototype.in = function (from = null) { + if (from) { + this.actions.head.from = from; + } + return this.actions.head.from; +}; +Trail.prototype.out = function (to = null) { + if (this.actions.tail) { + if (to) { + this.actions.tail.to = to; + } + return this.actions.tail.to; + } + if (to) { + this.actions.head.to = to; + } + return this.actions.head.to; +}; +Trail.prototype.size = function () { + if (this.actions.constructor === List) { + return this.actions.size; + } + else if (Array.isArray(this.actions)) { + return this.actions.length; + } +}; +Trail.prototype.join = function (delimiter) { + return this.actions.toArray().join(delimiter); +}; +// Update a given action in the trail +Trail.prototype.update = function (action, newAction) { + action.prepend(newAction); + action.detach(); +}; +// Remove an action from the trail using its reference +Trail.prototype.remove = function (action) { + var action; + if (typeof (action) === 'string') { + this.action = this.search(action); + //action not found + if (!this.action) { + return; + } + } + else { + this.action = action; + } + this.action.detach(); +}; +// Get `num` actions (or the available ones) before `position` +Trail.prototype.getPrev = function (position, num = 1) { + //check if it is a valid position + if (position >= this.actions.size || position <= 1 || this.actions.size <= 1) { + return; + } + // get actions + var action = this.actions.toArray()[position]; + var resultSet = []; + while (action && action.prev) { + if (resultSet.length >= num) { + if (resultSet.length == 1 && num == 1) { + return resultSet[0]; + } + return resultSet; } else { - this.id = id || null; - this.parent = parent || null; - this.interfaces = {}; - this.properties = {}; - this.metaproperties = {}; - this.children = []; - this.steps = []; - } - this.type = Types.TRAIL; - } - addStep(key, properties) { - let ts = properties.begin || new Date().toISOString(); - properties.begin = ts; - if (this.steps.length > 0) { - let lastStep = this.steps[this.steps.length - 1].key; - if (!this.interfaces[lastStep].properties.end) { - this.interfaces[lastStep].properties.end = ts; + resultSet.push(action.prev); + } + action = action.prev; + } + return resultSet; +}; +// Get `num` actions (or a n= this.actions.size || position <= 1 || this.actions.size <= 1) { + return; + } + // get actions + var action = this.actions.toArray()[position]; + var resultSet = []; + while (action && action.next) { + if (resultSet.length >= num) { + if (resultSet.length == 1 && num == 1) { + return resultSet[0]; } } - this.steps.push({ key: key, begin: ts }); - this.addInterface(key, 'temporal', properties); - } - addInterface(key, type, properties) { - this.interfaces[key] = { type: type, properties: properties }; - } - createLinksFromSteps() { - let vEntities = [vConnector]; - for (let key in this.interfaces) { - let interProp = this.interfaces[key].properties; - if (!interProp) - continue; - if (interProp.obj) { - let l = new Link(shortid(), vConnector.id, this.parent); - l.addBind('sub', interProp.obj, interProp.objInterface); - l.addBind('obj', this.id); - vEntities.push(l); + else { + resultSet.push(action.next); + } + action = action.next; + } + return resultSet; +}; +// Get the position of an action in the list, +// it might be useful for pagination. Returns +// -1 in case action is not found in the list +Trail.prototype.getPositionOf = function (action) { + //search action by eventId + var array = this.actions.toArray(); + for (var i = 0; i < this.actions.size; i++) { + if (array[i].event["id"] == action.event["id"]) { + return i; + } + } + return -1; +}; +// Get an action at specified position +// a position = 0 is equivalent to trail.head +Trail.prototype.getActionAt = function (position) { + //check if it is a valid position + if (position < 0 || position >= this.actions.size) { + return; + } + //return action by position in array + return this.actions.toArray()[position]; +}; +// Search for action(s) either by an event identifier or by filters +Trail.prototype.search = function (eventId = null, filters) { + if (!filters && eventId instanceof String) { + filters = { 'from': null, 'fromAnchor': 'lambda', 'to': null, 'toAnchor': 'lambda' }; + } + else if (!filters && eventId instanceof Object) { + filters = eventId; + eventId = null; + } + // nothing to be found + if (!eventId && !filters['from'] && !filters['to']) { + return; + } + // search actions by eventId + var array = this.actions.toArray(); + if (eventId) { + for (var i = 0; i < this.actions.size; i++) { + if (array[i].event["id"] == eventId) { + return array[i]; } } - return vEntities; - } - serialize() { - return { - id: this.id, - parent: this.parent, - properties: this.properties, - metaproperties: this.metaproperties, - interfaces: this.interfaces, - type: this.type - }; - } - static isValid(entity) { - let isValid = false; - if (entity && typeof (entity) === 'object' && !Array.isArray(entity)) { - if (entity.hasOwnProperty('type') && entity.type === Types.TRAIL && - entity.hasOwnProperty('id') && entity.hasOwnProperty('parent')) { - isValid = true; + return; + } + // search actions by filters + var resultSet = []; + for (var i = 0; i < this.actions.size; i++) { + if ((filters['from'] && filters['to']) && array[i].from == filters['from'] && array[i].to == filters['to']) { + resultSet.push(array[i]); + } + else if (array[i].from == filters['from'] || array[i].to == filters['to']) { + resultSet.push(array[i]); + } + } + return resultSet; +}; +Trail.prototype.loadActions = function (actions = null) { + // use array of actions if passed + if (actions && Array.isArray(actions)) { + if (actions[0].event.timestamp) { + this.actions = new List(...sort(actions)); + } + else { + this.actions = new List(...actions); + } + return this.actions; + } + let actionArray = []; + let actionIds = []; + for (let i in this.actions) { + // create Action object from json object + if (this.actions[i] && this.actions[i].hasOwnProperty("from") && + this.actions[i].hasOwnProperty("to") && + this.actions[i].hasOwnProperty("agent") && + this.actions[i].hasOwnProperty("event")) { + let from = new TrailNode(this.actions[i].from.nodeId, this.actions[i].from.nodeType, this.actions[i].from.targetAnchor); + let to = new TrailNode(this.actions[i].to.nodeId, this.actions[i].to.nodeType, this.actions[i].to.targetAnchor); + let event = { "id": this.actions[i].event.id, "type": this.actions[i].event.type, "properties": this.actions[i].event.properties, "timestamp": new Date(this.actions[i].event.timestamp) }; + let agent = this.actions[i].agent; + actionArray.push(new Action({ from, to, event, agent })); + } + else if (this.actions[i] && this.actions[i].hasOwnProperty("event") && this.actions[i].event.hasOwnProperty("timestamp")) { + // we got event's id and timestamp + actionArray.push(new Action({ event: { "id": i, "timestamp": new Date(this.actions[i].event.timestamp) } })); + } + else if (Array.isArray(this.actions)) { + // all we got is an Array with the event's id + actionIds.push(this.actions[i]); + } + else { + // all we got is event's id + actionIds.push(i); + } + } + // sort action items before creating list + if (actionArray.length > 0) { + this.actions = new List(...sort(actionArray)); + } + else if (actionIds.length > 0) { + this.actions = actionIds; + } +}; +function isValid(entity) { + let isValid = false; + if (entity && typeof (entity) === 'object' && !Array.isArray(entity)) { + if (entity.hasOwnProperty('type') && entity.type === Types.TRAIL && + entity.hasOwnProperty('id') && entity.hasOwnProperty('parent')) { + isValid = true; + } + } + return isValid; +} +function sort(actions = null) { + if (actions && Array.isArray(actions) && actions[0].event.timestamp.constructor === Date) { + // sort and return object array based on timestamp + return actions.sort(function (action1, action2) { + return action1.event['timestamp'] - action2.event['timestamp']; + }); + } + else if (actions && Array.isArray(actions) && (actions[0].event.timestamp.constructor === String || actions[0].event.timestamp.constructor === Number)) { + // sort Action array based on timestamp + return actions.sort(function (action1, action2) { + return new Date(action1.event['timestamp']) - new Date(action2.event['timestamp']); + }); + } +} +Trail.prototype.toJSON = function () { + let actionArray = []; + if (this.actions.constructor === List) { + //in case we have only action ids and timestamps + if (this.actions.head && (!this.actions.head.from && !this.actions.head.to && !this.actions.head.agent)) { + let action = this.actions.head; + while (action) { + actionArray.push(action.id); + action = action.next; } } - return isValid; + else { + actionArray = this.actions.toArray(); + } + } + else if (Array.isArray(this.actions)) { + actionArray = this.actions; + } + return { + id: this.id, + parent: this.parent, + properties: this.properties, + metaproperties: this.metaproperties, + interfaces: this.interfaces, + actions: actionArray, + type: this.type + }; +}; +Trail.prototype.serialize = function () { + let actions; + if (this.actions instanceof Array) { + actions = this.actions; + } + else { + actions = this.actions.toArray(); + } + return { + id: this.id, + parent: this.parent, + properties: this.properties, + metaproperties: this.metaproperties, + interfaces: this.interfaces, + actions: actions, + type: this.type + }; +}; +// TrailNode is basically an envelope for a hknode and a target anchor +class TrailNode { + constructor(nodeId, nodeType, targetAnchor) { + this.nodeId = nodeId; + this.nodeType = nodeType; + this.targetAnchor = targetAnchor; + } + toString() { + return this.nodeId + "#" + this.targetAnchor; } } -function _loadSteps() { - let steps = []; - for (let key in this.interfaces) { - let begin = new Date(Date.parse(this.interfaces[key].properties.begin)); - let end = new Date(Date.parse(this.interfaces[key].properties.end)); - this.interfaces[key].properties.begin = begin; - this.interfaces[key].properties.end = end; - steps.push({ key: key, begin: begin }); - } - steps.sort((a, b) => { - if (a.begin < b.begin) { - return -1; - } - if (a.begin > b.begin) { - return 1; - } - return 0; - }); - this.steps = steps; +// actions hold references to source and destination trail +// nodes (along with their respective target anchors), +// the related event and an agent (user, system or content). +class Action extends Item { + constructor({ from = null, to = null, event = {}, agent = null } = {}) { + super(); + this.from = from; + this.to = to; + this.agent = agent; + this.event = event; + this.type = Types.ACTION; + // create timestamp if needed + // if(!this.event['timestamp']) + // { + // this.event['timestamp'] = new Date().getTime(); + // } + // get event id or create a new one + if (!this.event['id'] || this.event['id'] == '') { + this.event['id'] = this.event.type + '_' + this.event.timestamp; + this.id = this.event['id']; + } + else { + this.id = this.event['id']; + } + } + getTime() { + return new Date(this.event['timestamp']).getTime(); + } + toString() { + if (this.from && this.to && this.agent && this.event && this.event.id) { + return JSON.stringify({ from: this.from, to: this.to, agent: this.agent, event: this.event, id: this.event.id }); + } + else { + return this.event.id; + } + } + toJSON() { + if (this.from && this.to && this.agent && this.event && this.event.id) { + return { from: this.from, to: this.to, agent: this.agent, event: this.event, id: this.event.id }; + } + else { + return this.event.id; + } + } } Trail.type = Types.TRAIL; -const isValid = Trail.isValid; +Trail.isValid = isValid; +Trail.sort = sort; +Trail.Action = Action; +Trail.TrailNode = TrailNode; module.exports = Trail; diff --git a/dist/types.d.ts b/dist/types.d.ts index a59c438..2948170 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -2,16 +2,17 @@ * Copyright (c) 2016-present, IBM Research * Licensed under The MIT License [see LICENSE for details] */ -export var NODE: string; -export var CONTEXT: string; -export var LINK: string; -export var CONNECTOR: string; -export var REFERENCE: string; -export var INTERFACE: string; -export var BIND: string; -export var TRAIL: string; -export var VIRTUAL_NODE: string; -export var VIRTUAL_CONTEXT: string; -export var VIRTUAL_LINK: string; -export var VIRTUAL_SOURCE_PROPERTY: string; +export const NODE: "node"; +export const CONTEXT: "context"; +export const LINK: "link"; +export const CONNECTOR: "connector"; +export const REFERENCE: "ref"; +export const INTERFACE: "interface"; +export const BIND: "bind"; +export const TRAIL: "trail"; +export const ACTION: "action"; +export const VIRTUAL_NODE: "virtualnode"; +export const VIRTUAL_CONTEXT: "virtualcontext"; +export const VIRTUAL_LINK: "virtuallink"; +export const VIRTUAL_SOURCE_PROPERTY: "virtualsrc"; export function isValidType(type: any): boolean; diff --git a/dist/types.js b/dist/types.js index 46f60eb..fff6183 100644 --- a/dist/types.js +++ b/dist/types.js @@ -11,6 +11,7 @@ exports.REFERENCE = 'ref'; exports.INTERFACE = 'interface'; exports.BIND = 'bind'; exports.TRAIL = 'trail'; +exports.ACTION = 'action'; exports.VIRTUAL_NODE = 'virtualnode'; exports.VIRTUAL_CONTEXT = 'virtualcontext'; exports.VIRTUAL_LINK = 'virtuallink'; diff --git a/dist/virtualcontext.js b/dist/virtualcontext.js index a9e4ac0..5d7bf58 100644 --- a/dist/virtualcontext.js +++ b/dist/virtualcontext.js @@ -5,7 +5,7 @@ */ const tslib_1 = require("tslib"); const types_1 = require("./types"); -const context_1 = (0, tslib_1.__importDefault)(require("./context")); +const context_1 = tslib_1.__importDefault(require("./context")); class VirtualContext extends context_1.default { /** Constructs a new virtual context object. * diff --git a/dist/virtuallink.js b/dist/virtuallink.js index dc4d63a..ccd6c5d 100644 --- a/dist/virtuallink.js +++ b/dist/virtuallink.js @@ -5,7 +5,7 @@ */ const tslib_1 = require("tslib"); const types_1 = require("./types"); -const link_1 = (0, tslib_1.__importDefault)(require("./link")); +const link_1 = tslib_1.__importDefault(require("./link")); class VirtualLink extends link_1.default { constructor(id, parent) { super(id, parent); diff --git a/dist/virtualnode.js b/dist/virtualnode.js index d91b4f9..1205bae 100644 --- a/dist/virtualnode.js +++ b/dist/virtualnode.js @@ -5,7 +5,7 @@ */ const tslib_1 = require("tslib"); const types_1 = require("./types"); -const node_1 = (0, tslib_1.__importDefault)(require("./node")); +const node_1 = tslib_1.__importDefault(require("./node")); class VirtualNode extends node_1.default { constructor(id, parent) { super(id, parent); diff --git a/package.json b/package.json index 293cd58..ba28f46 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "ninja-util": "^1.3.1", "request": "^2.88.0", "request-promise-native": "^1.0.8", - "shortid": "^2.2.14" + "shortid": "^2.2.14", + "linked-list": "3.0.2" }, "license": "MIT", "repository": { diff --git a/src/datasource/hkdatasource.js b/src/datasource/hkdatasource.js index 0859ac2..54bd3c1 100644 --- a/src/datasource/hkdatasource.js +++ b/src/datasource/hkdatasource.js @@ -522,7 +522,7 @@ class HKDatasource } }); } - + /** * Get entities from a context * @@ -584,6 +584,54 @@ class HKDatasource }); } + /** + * @callback GetTrailCallback + * @param {string} err An error object that indicate if the operation was succesful or not + * @param {object} trail + */ + + /** + * Fetch trail + * + * @param {string} trailId The trail id to retrieve their nested entities. + * @param {GetTrailCallback} callback Callback with the entities + */ + + fetchTrail(trailId, callback = () => {}) + { + let url = this.url + "repository/" + this.graphName + "/trail/" + encodeURIComponent(trailId); + + request.get(url, this.options, (err, res) => + { + // console.log(res.body); + if (!err) + { + if (requestCompletedWithSuccess(res.statusCode)) + { + try + { + let entities = convertEntities(res.body); + callback(null, entities[trailId]); + } + catch (exp) + { + callback(exp); + } + } + + else + { + callback(`Server responded with ${res.statusCode}. ${res.body}`); + } + } + + else + { + callback(err); + } + }); + } + /** * Filter entities using CSS pattern `(TODO: document it better)` * diff --git a/src/hkgraph.js b/src/hkgraph.js index cdfe44a..88ab655 100644 --- a/src/hkgraph.js +++ b/src/hkgraph.js @@ -13,6 +13,7 @@ const Reference = require("./reference"); const Trail = require("./trail"); const HKTypes = require("./types"); const shortId = require('shortid'); +const { Action } = require("./trail"); const VirtualContext = require("./virtualcontext"); const HKEntity = require("./hkentity"); const VirtualNode = require("./virtualnode"); @@ -32,6 +33,8 @@ class HKGraph this.connectors = {}; this.refs = {}; this.trails = {}; + this.actions = {}; + // Auxiliar maps this.bindsMap = {}; this.linkMap = {}; @@ -55,7 +58,8 @@ class HKGraph this.links.hasOwnProperty(id) || this.connectors.hasOwnProperty(id) || this.refs.hasOwnProperty(id) || - this.trails.hasOwnProperty(id); + this.trails.hasOwnProperty(id) || + this.actions.hasOwnProperty(id); } /** @@ -82,7 +86,7 @@ class HKGraph oldEntity.className = entity.className; } - if (entity.type === HKTypes.NODE || entity.type === HKTypes.REFERENCE || entity.type === HKTypes.CONTEXT || entity.type === HKTypes.VIRTUAL_NODE || entity.type === HKTypes.VIRTUAL_CONTEXT) + if (entity.type === HKTypes.NODE || entity.type === HKTypes.TRAIL || entity.type === HKTypes.REFERENCE || entity.type === HKTypes.CONTEXT || entity.type === HKTypes.VIRTUAL_NODE || entity.type === HKTypes.VIRTUAL_CONTEXT || entity.type === HKTypes.ACTION) { oldEntity.interfaces = entity.interfaces; } @@ -218,9 +222,25 @@ class HKGraph { newEntity = new Trail(entity); this.trails[entity.id] = newEntity; + this.contextMap[entity.id] = {}; + + if(this.orphans.hasOwnProperty(entity.id)) + { + this.contextMap[entity.id] = this.orphans[entity.id]; + delete this.orphans[entity.id]; + } } break; } + case HKTypes.ACTION: + { + if(entity instanceof Trail.Action) + { + newEntity = entity; + this.actions[entity.id] = newEntity; + } + break; + } case HKTypes.LINK: { if (Link.isValid(entity)) @@ -439,8 +459,13 @@ class HKGraph { /* delete children trails? */ delete this.trails[id]; + delete this.contextMap[entity.id]; break; } + case Action.type: + { + delete this.actions[id]; + } } if (this.orphans.hasOwnProperty(entity.parent)) @@ -612,7 +637,7 @@ class HKGraph c.id = null; return c; } - return this.nodes[id] || this.virtualNodes[id] || this.contexts[id] || this.virtualContexts[id] || this.virtualLinks[id] || this.links[id] || this.connectors[id] || this.refs[id] || this.trails[id] || null; + return this.nodes[id] || this.virtualNodes[id] || this.contexts[id] || this.virtualContexts[id] || this.virtualLinks[id] || this.links[id] || this.connectors[id] || this.refs[id] || this.trails[id] || this.actions[id] || null; } /** diff --git a/src/index.js b/src/index.js index 940e221..de2086e 100644 --- a/src/index.js +++ b/src/index.js @@ -35,6 +35,7 @@ exports.CONNECTOR_TYPE = require("./types").CONNECTOR; exports.BIND_TYPE = require("./types").BIND; exports.INTERFACE = require("./types").INTERFACE; exports.TRAIL_TYPE = require("./types").TRAIL; +exports.ACTION_TYPE = require("./types").ACTION; exports.VIRTUAL_NODE_TYPE = require("./types").VIRTUAL_NODE; exports.VIRTUAL_CONTEXT_TYPE = require("./types").VIRTUAL_CONTEXT; exports.VIRTUAL_LINK_TYPE = require("./types").VIRTUAL_LINK; diff --git a/src/linked-list/index.cjs b/src/linked-list/index.cjs new file mode 100644 index 0000000..5dc2d37 --- /dev/null +++ b/src/linked-list/index.cjs @@ -0,0 +1,290 @@ +// Creates a new `Iterator` for looping over the `List`. +class Iterator { + constructor(item) { + this.item = item + } + + // Move the `Iterator` to the next item. + next() { + this.value = this.item + this.done = !this.item + this.item = this.item ? this.item.next : undefined + return this + } +} + +// Creates a new `Item`: +// An item is a bit like DOM node: It knows only about its “parent” (`list`), +// the item before it (`prev`), and the item after it (`next`). +class Item { + // Prepends the given item *before* the item operated on. + prepend(item) { + var list = this.list + + if (!item || !item.append || !item.prepend || !item.detach) { + throw new Error( + 'An argument without append, prepend, or detach methods was given to `Item#prepend`.' + ) + } + + // If self is detached, return false. + if (!list) { + return false + } + + // Detach the prependee. + item.detach() + + // If self has a previous item... + if (this.prev) { + item.prev = this.prev + this.prev.next = item + } + + // Connect the prependee. + item.next = this + item.list = list + + // Set the previous item of self to the prependee. + this.prev = item + + // If self is the first item in the parent list, link the lists first item to + // the prependee. + if (this === list.head) { + list.head = item + } + + // If the the parent list has no last item, link the lists last item to self. + if (!list.tail) { + list.tail = this + } + + list.size++ + + return item + } + + // Appends the given item *after* the item operated on. + append(item) { + var list = this.list + + if (!item || !item.append || !item.prepend || !item.detach) { + throw new Error( + 'An argument without append, prepend, or detach methods was given to `Item#append`.' + ) + } + + if (!list) { + return false + } + + // Detach the appendee. + item.detach() + + // If self has a next item… + if (this.next) { + item.next = this.next + this.next.prev = item + } + + // Connect the appendee. + item.prev = this + item.list = list + + // Set the next item of self to the appendee. + this.next = item + + // If the the parent list has no last item or if self is the parent lists last + // item, link the lists last item to the appendee. + if (this === list.tail || !list.tail) { + list.tail = item + } + + list.size++ + + return item + } + + // Detaches the item operated on from its parent list. + detach() { + var list = this.list + + if (!list) { + return this + } + + // If self is the last item in the parent list, link the lists last item to + // the previous item. + if (list.tail === this) { + list.tail = this.prev + } + + // If self is the first item in the parent list, link the lists first item to + // the next item. + if (list.head === this) { + list.head = this.next + } + + // If both the last and first items in the parent list are the same, remove + // the link to the last item. + if (list.tail === list.head) { + list.tail = null + } + + // If a previous item exists, link its next item to selfs next item. + if (this.prev) { + this.prev.next = this.next + } + + // If a next item exists, link its previous item to selfs previous item. + if (this.next) { + this.next.prev = this.prev + } + + // Remove links from self to both the next and previous items, and to the + // parent list. + this.prev = this.next = this.list = null + + list.size-- + + return this + } +} + +Item.prototype.next = Item.prototype.prev = Item.prototype.list = null + +// Creates a new List: A linked list is a bit like an Array, but knows nothing +// about how many items are in it, and knows only about its first (`head`) and +// last (`tail`) items. +// Each item (e.g. `head`, `tail`, &c.) knows which item comes before or after +// it (its more like the implementation of the DOM in JavaScript). +class List { + // Creates a new list from the arguments (each a list item) passed in. + static of(...items) { + return appendAll(new this(), items) + } + + // Creates a new list from the given array-like object (each a list item) passed + // in. + static from(items) { + return appendAll(new this(), items) + } + + constructor(...items) { + appendAll(this, items) + } + + // Returns the list’s items as an array. + // This does *not* detach the items. + toArray() { + var item = this.head + var result = [] + + while (item) { + result.push(item) + item = item.next + } + + return result + } + + // Prepends the given item to the list. + // `item` will be the new first item (`head`). + prepend(item) { + if (!item) { + return false + } + + if (!item.append || !item.prepend || !item.detach) { + throw new Error( + 'An argument without append, prepend, or detach methods was given to `List#prepend`.' + ) + } + + if (this.head) { + return this.head.prepend(item) + } + + item.detach() + item.list = this + this.head = item + this.size++ + + return item + } + + // Appends the given item to the list. + // `item` will be the new last item (`tail`) if the list had a first item, and + // its first item (`head`) otherwise. + append(item) { + if (!item) { + return false + } + + if (!item.append || !item.prepend || !item.detach) { + throw new Error( + 'An argument without append, prepend, or detach methods was given to `List#append`.' + ) + } + + // If self has a last item, defer appending to the last items append method, + // and return the result. + if (this.tail) { + return this.tail.append(item) + } + + // If self has a first item, defer appending to the first items append method, + // and return the result. + if (this.head) { + return this.head.append(item) + } + + // …otherwise, there is no `tail` or `head` item yet. + item.detach() + item.list = this + this.head = item + this.size++ + + return item + } + + // Creates an iterator from the list. + [Symbol.iterator]() { + return new Iterator(this.head) + } +} + +List.prototype.size = 0 +List.prototype.tail = List.prototype.head = null + +// Creates a new list from the items passed in. +function appendAll(list, items) { + var index + var item + var iterator + + if (!items) { + return list + } + + if (items[Symbol.iterator]) { + iterator = items[Symbol.iterator]() + item = {} + + while (!item.done) { + item = iterator.next() + list.append(item && item.value) + } + } else { + index = -1 + + while (++index < items.length) { + list.append(items[index]) + } + } + + return list +} + +module.exports.Item = Item; +module.exports.List = List; \ No newline at end of file diff --git a/src/linked-list/index.d.ts b/src/linked-list/index.d.ts new file mode 100644 index 0000000..4cfe851 --- /dev/null +++ b/src/linked-list/index.d.ts @@ -0,0 +1,29 @@ +export namespace List{ + export class Item { + prev: this + next: this + list: List + + detach(): this + prepend(item: T): T + append(item: T): T + } +} +export class List implements Iterable { + static of(...items: T[]): List + static from(items: Iterable): List + + head: T | null + tail: T | null + size: number + + constructor(...items: T[]) + toArray(): T[] + prepend(item: T): T + append(item: T): T + [Symbol.iterator](): Iterator +} + +export type Item = List.Item; + + diff --git a/src/linked-list/license b/src/linked-list/license new file mode 100644 index 0000000..0c06d5b --- /dev/null +++ b/src/linked-list/license @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2014 Titus Wormer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/linked-list/package.json b/src/linked-list/package.json new file mode 100644 index 0000000..8a80e09 --- /dev/null +++ b/src/linked-list/package.json @@ -0,0 +1,115 @@ +{ + "_from": "git+ssh://git@github.ibm.com:keg-core/linked-list.git", + "_id": "linked-list@3.0.1", + "_inBundle": false, + "_integrity": "", + "_location": "/linked-list", + "_phantomChildren": {}, + "_requested": { + "type": "git", + "raw": "linked-list@git+ssh://git@github.ibm.com:keg-core/linked-list.git", + "name": "linked-list", + "escapedName": "linked-list", + "rawSpec": "git+ssh://git@github.ibm.com:keg-core/linked-list.git", + "saveSpec": "git+ssh://git@github.ibm.com:keg-core/linked-list.git", + "fetchSpec": "git@github.ibm.com:keg-core/linked-list.git", + "gitCommittish": null + }, + "_requiredBy": [ + "/" + ], + "_resolved": "git+ssh://git@github.ibm.com:keg-core/linked-list.git#778981762dbdcbf5f10b031345ce9756c29e0915", + "_spec": "linked-list@git+ssh://git@github.ibm.com:keg-core/linked-list.git", + "_where": "/Users/gabrielapinheiro/ghe/keg/hklib", + "author": { + "name": "Titus Wormer", + "email": "tituswormer@gmail.com", + "url": "https://wooorm.com" + }, + "bugs": { + "url": "https://github.com/wooorm/linked-list/issues" + }, + "bundleDependencies": false, + "contributors": [ + { + "name": "Titus Wormer", + "email": "tituswormer@gmail.com", + "url": "https://wooorm.com" + }, + { + "name": "Blake Embrey", + "email": "hello@blakeembrey.com" + }, + { + "name": "Regev Brody", + "email": "regevbr@gmail.com" + } + ], + "deprecated": false, + "description": "Minimalistic linked lists", + "devDependencies": { + "@types/tape": "^4.0.0", + "c8": "^7.0.0", + "prettier": "^2.0.0", + "remark-cli": "^9.0.0", + "remark-preset-wooorm": "^8.0.0", + "tape": "^5.0.0", + "xo": "^0.38.0" + }, + "files": [ + "index.cjs", + "index.d.ts" + ], + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + }, + "homepage": "https://github.com/wooorm/linked-list#readme", + "keywords": [ + "double", + "linked", + "list" + ], + "license": "MIT", + "main": "index.cjs", + "name": "linked-list", + "prettier": { + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "bracketSpacing": false, + "semi": false, + "trailingComma": "none" + }, + "remarkConfig": { + "plugins": [ + "preset-wooorm" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wooorm/linked-list.git" + }, + "scripts": { + "format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", + "test": "npm run format && npm run test-coverage", + "test-api": "node test.js", + "test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js" + }, + "sideEffects": false, + "type": "module", + "types": "index.d.ts", + "version": "3.0.1", + "xo": { + "prettier": true, + "rules": { + "no-var": "off", + "prefer-arrow-callback": "off", + "no-multi-assign": "off" + }, + "ignores": [ + "*.ts", + "linked-list.js" + ] + } +} diff --git a/src/linked-list/readme.md b/src/linked-list/readme.md new file mode 100644 index 0000000..20321ea --- /dev/null +++ b/src/linked-list/readme.md @@ -0,0 +1,368 @@ +# linked-list + +[![Build][build-badge]][build] +[![Coverage][coverage-badge]][coverage] +[![Downloads][downloads-badge]][downloads] +[![Size][size-badge]][size] + +Small double [linked list][wiki]. + +## Install + +This package is ESM only: Node 12+ is needed to use it and it must be `import`ed +instead of `require`d. + +Edit: We forked this opensource lib and modified it so we could use it with `require`, since some HK images (e.g. KES) are still based on Node 12 and all of them use required instead of import. + +[npm][]: + +```sh +npm install linked-list +``` + +## Use + +```js +import {List, Item} from 'linked-list' + +var item1 = new Item() +var item2 = new Item() +var item3 = new Item() +var list = new List(item1, item2, item3) + +list.head // => item1 +list.head.next // => item2 +list.head.next.next // => item3 +list.head.next.prev // => item1 +list.tail // => item3 +list.tail.next // => `null` +``` + +Subclassing: + +```js +import {List, Item} from 'linked-list' + +class Tokens extends List { + join(delimiter) { + return this.toArray().join(delimiter) + } +} + +class Token extends Item { + constructor(value) { + super() + this.value = value + } + + toString() { + return this.value + } +} + +var dogs = new Token('dogs') +var and = new Token('&') +var cats = new Token('cats') +var tokens = new Tokens(dogs, and, cats) + +console.log(tokens.join(' ')) // => 'dogs & cats' + +and.prepend(cats) +and.append(dogs) + +console.log(tokens.join(' ') + '!') // => 'cats & dogs!' +``` + +## API + +This package exports the following identifiers: `List`, `Item`. +There is no default export. + +### `List([items…])` + +```js +new List() +new List(new Item(), new Item()) +``` + +Create a new linked list. + +#### `List.from([items])` + +```js +List.from() +List.from([]) +List.from([new Item(), new Item()]) +``` + +Create a new `this` and adds the given array of items. +Ignores `null` or `undefined` values. +Throws an error when a given item has no `detach`, `append`, or `prepend` +methods. + +#### `List.of([items…])` + +```js +List.of() +List.of(new Item(), new Item()) +``` + +Creates a new linked list from the given arguments. +Defers to `List.from`. + +#### `List#append(item)` + +```js +var list = new List() +var item = new Item() + +list.head === null // => true +item.list === null // => true + +list.append(item) + +list.head === item // => true +item.list === list // => true +``` + +Appends an item to a list. +Throws an error when the given item has no `detach`, `append`, or `prepend` +methods. +Returns the given item. + +#### `List#prepend(item)` + +```js +var list = new List() +var item = new Item() + +list.prepend(item) +``` + +Prepends an item to a list. +Throws an error when the given item has no `detach`, `append`, or `prepend` +methods. +Returns the given item. + +#### `List#toArray()` + +```js +var item1 = new Item() +var item2 = new Item() +var list = new List(item1, item2) +var array = list.toArray() + +array[0] === item1 // => true +array[1] === item2 // => true +array[0].next === item2 // => true +array[1].prev === item1 // => true +``` + +Returns the items in the list in an array. + +#### `List#head` + +```js +var item = new Item() +var list = new List(item) + +list.head === item // => true +``` + +The first item in a list, and `null` otherwise. + +#### `List#tail` + +```js +var list = new List() +var item1 = new Item() +var item2 = new Item() + +list.tail === null // => true + +list.append(item1) +list.tail === null // => true, see note. + +list.append(item2) +list.tail === item2 // => true +``` + +The last item in a list, and `null` otherwise. +Note that a list with only one item has **no tail**, only a head. + +#### `List#size` + +```js +var list = new List() +var item1 = new Item() +var item2 = new Item() + +list.size === 0 // => true + +list.append(item1) +list.size === 1 // => true + +list.append(item2) +list.size === 2 // => true +``` + +The number of items in the list. + +### `Item()` + +```js +var item = new Item() +``` + +Creates a new linked list Item. + +#### `Item#append(item)` + +```js +var item1 = new Item() +var item2 = new Item() + +new List().append(item1) + +item1.next === null // => true + +item1.append(item2) +item1.next === item2 // => true +``` + +Adds the given item **after** the operated on item in a list. +Throws an error when the given item has no `detach`, `append`, or `prepend` +methods. +Returns false when the operated on item is not attached to a list, otherwise the +given item. + +#### `Item#prepend(item)` + +```js +var item1 = new Item() +var item2 = new Item() + +new List().append(item1) + +item1.prev === null // => true + +item1.prepend(item2) +item1.prev === item2 // => true +``` + +Adds the given item **before** the operated on item in a list. +Throws an error when the given item has no `detach`, `append`, or `prepend` +methods. +Returns false when the operated on item is not attached to a list, otherwise +the given item. + +#### `Item#detach()` + +```js +var item = new Item() +var list = new List(item) + +item.list === list // => true + +item.detach() +item.list === null // => true +``` + +Removes the operated on item from its parent list. +Removes references to it on its parent `list`, and `prev` and `next` items; +relinking them when possible. +Returns the operated on item. +Even when it was already detached. + +#### `Item#next` + +```js +var item1 = new Item() +var item2 = new Item() + +new List(item1) + +item1.next === null // => true +item2.next === null // => true + +item1.append(item2) + +item1.next === item2 // => true + +item1.detach() + +item1.next === null // => true +``` + +The items succeeding item, and `null` otherwise. + +#### `Item#prev` + +```js +var item1 = new Item() +var item2 = new Item() + +new List(item) + +item1.prev === null // => true +item2.prev === null // => true + +item1.append(item2) + +item1.prev === item1 // => true + +item2.detach() + +item2.prev === null // => true +``` + +The items preceding item, and `null` otherwise. + +#### `Item#list` + +```js +var item = new Item() +var list = new List() + +item.list === null // => true + +list.append(item) + +item.list === list // => true + +item.detach() + +item.list === null // => true +``` + +The items parent list, and `null` otherwise. + +## License + +[MIT][license] © [Titus Wormer][author] + + + +[build-badge]: https://github.com/wooorm/linked-list/workflows/main/badge.svg + +[build]: https://github.com/wooorm/linked-list/actions + +[coverage-badge]: https://img.shields.io/codecov/c/github/wooorm/linked-list.svg + +[coverage]: https://codecov.io/github/wooorm/linked-list + +[downloads-badge]: https://img.shields.io/npm/dm/linked-list.svg + +[downloads]: https://www.npmjs.com/package/linked-list + +[size-badge]: https://img.shields.io/bundlephobia/minzip/linked-list.svg + +[size]: https://bundlephobia.com/result?p=linked-list + +[npm]: https://docs.npmjs.com/cli/install + +[license]: license + +[author]: https://wooorm.com + +[wiki]: https://wikipedia.org/wiki/Linked_list diff --git a/src/trail.js b/src/trail.js index 8cbbd17..d23e156 100644 --- a/src/trail.js +++ b/src/trail.js @@ -1,29 +1,86 @@ -/** +/* * Copyright (c) 2016-present, IBM Research * Licensed under The MIT License [see LICENSE for details] */ 'use strict' -const shortid = require('shortid'); -const Types = require('./types'); -const HKEntity = require('./hkentity'); -const Connector = require('./connector'); -const Link = require('./link'); -const RoleTypes = require('./roletypes'); - -const CONNECTOR_NAME = 'occurs'; - -const vConnector = new Connector(CONNECTOR_NAME, 'f'); -vConnector.addRole('sub', RoleTypes.SUBJECT); -vConnector.addRole('obj', RoleTypes.OBJECT); +import {List, Item} from 'linked-list'; +const Types = require('./types'); +const HKEntity = require('./hkentity'); class Trail extends HKEntity { - constructor(id, parent) + /** + * Constructs a new trail object. + * + * @param {string | null} [id] Some id string for this trail. Deprecated: json object, which will deserialized as a Trail. + * @param {List | Array | null} [actions] Trail actions. + * @param {string | null} [parent] Parent id. + */ + + constructor(id = null, actions = null, parent = null) { super(); - if (arguments[0] && typeof arguments[0] === 'object' && isValid(arguments[0])) + + /** + * + * Id of this node. Might be null. + * + * @public + * @type {string | null} + * + */ + this.id = id; + + /** + * Trail actions. Might be null. + * + * @public + * @type {List | Array | null} + * + */ + this.actions = actions || new List(); + + /** + * Parent id. Might be null. + * + * @public + * @type {string | null} + * + */ + this.parent = parent; + + /** + * Type of this node. + * + * @public + * @type {string | null} + */ + this.type = Types.TRAIL; + + /** + * Interface attributed to this node. + * + * @public + * @type {Object.}>} + */ + this.interfaces = {}; + + /** + * @public + * @type {Object. + */ + this.properties = {}; + + /** + * @public + * @type {Object. + */ + this.metaproperties = {}; + + // TODO: this code seems to copy a trail passed as an id. Create a separate clone/json-deserialize function for that. + if(arguments[0] && typeof arguments[0] === 'object' && isValid(arguments[0])) { let trail = arguments[0]; @@ -32,87 +89,437 @@ class Trail extends HKEntity this.properties = trail.properties || {}; this.metaproperties = trail.metaproperties || {}; this.interfaces = trail.interfaces || {}; - this.children = trail.children || []; - _loadSteps.call(this); + this.actions = trail.actions || new List(); + + if (this.actions && Object.keys(this.actions).length > 0) { + this.loadActions(); + } } + } + + /** + * Update a given action in trail. + * + * @param {Trail.Action} oldAction + * @param {Trail.Action} newAction + */ + updateAction(oldAction, newAction) + { + oldAction.prepend(newAction); + oldAction.detach(); + } - else + /** + * Add an action to a trail respecting timestamp ordering. + * + * @param {Trail.Action} action + * @returns {Trail.Action} + */ + addAction(action) + { + var current = this.actions.head; + + while (current) { - this.id = id || null; - this.parent = parent || null; - this.interfaces = {}; - this.properties = {}; - this.metaproperties = {}; - this.children = []; - this.steps = []; + if (new Date(current.event['timestamp']) - new Date(action.event['timestamp'])) + { + return current.prepend(action); + } + current = current.next; } - this.type = Types.TRAIL; + return this.actions.append(action); } - addStep(key, properties) + /** + * Remove an action from trail using its reference. + * + * @param {Trail.Action} action + * @returns {Trail.Action} + */ + removeAction(action) { - let ts = properties.begin || new Date().toISOString(); - properties.begin = ts; + var action; + if(typeof(action) === 'string') { + this.action = this.search(action) + + //action not found + if(!this.action){ + return + } + } + else { + this.action = action; + } + + this.action.detach(); + } + + /** + * `append` and `prepend` operations are + * handled by our linked list structure. + * + * @param {Trail.Action} action + * @returns {Trail.Action} + */ + append(action) + { + return this.actions.append(action); + } + + /** + * + * @param {Trail.Action} action + * @returns {Trail.Action} + */ + prepend(action) + { + return this.actions.prepend(action); + } - if (this.steps.length > 0) + /** + * Aliases `in` and `out` represent the input and + * output TrailNodes for a trail, which conceptually is + * done through the use of Port elements in Hyperknowledge. + * + * @param {Trail.Action | null} from + * @returns {Trail.Action} + */ + in(from = null) + { + if (from) { - let lastStep = this.steps[this.steps.length - 1].key; - if (!this.interfaces[lastStep].properties.end) + this.actions.head.from = from; + } + + return this.actions.head.from; + } + + /** + * + * @param {Trail.Action | null} to + * @returns {Trail.Action} + */ + out(to = null) + { + if(this.actions.tail) + { + if (to) { - this.interfaces[lastStep].properties.end = ts; + this.actions.tail.to = to; } + + return this.actions.tail.to; + } + + if (to) + { + this.actions.head.to = to; + } + + return this.actions.head.to; + } + + /** + * + * @returns {number} + */ + size() + { + if (this.actions.constructor === List) + { + return this.actions.size; + } + else if (Array.isArray(this.actions)) + { + return this.actions.length; } + } - this.steps.push({ key: key, begin: ts }); - this.addInterface(key, 'temporal', properties); + /** + * + * @param {string} delimiter + * @returns {string} + */ + join(delimiter) + { + return this.actions.toArray().join(delimiter) } - addInterface(key, type, properties) + /** + * Update a given action in the trail. + * + * @param {Trail.Action} action + * @param {Trail.Action} newAction + */ + // + update(action, newAction) { - this.interfaces[key] = { type: type, properties: properties }; + action.prepend(newAction) + action.detach() } - createLinksFromSteps() + /** + * Remove an action from the trail using its reference. + * + * @param {Trail.Action | string} action + * @returns {Trail.Action | null} + */ + // + remove(action) { - let vEntities = [vConnector]; + var action + if(typeof(action) === 'string') { + this.action = this.search(action) + + //action not found + if(!this.action){ + return + } + } + else { + this.action = action + } + + this.action.detach() + } - for (let key in this.interfaces) + /** + * Get `num` actions (or the available ones) before `position`. + * + * If a number is not given, 1 is assumed. + * + * @param {number} position + * @param {number | null} num + * @returns {Array | null} + */ + getPrev = function(position, num=1) { - let interProp = this.interfaces[key].properties; - if (!interProp) - continue; + //check if it is a valid position + if (position >= this.actions.size || position <= 1 || this.actions.size <= 1){ + return; + } - if (interProp.obj) - { - let l = new Link(shortid(), vConnector.id, this.parent); - l.addBind('sub', interProp.obj, interProp.objInterface); - l.addBind('obj', this.id); - vEntities.push(l); + // get actions + var action = this.actions.toArray()[position] + var resultSet = [] + while(action && action.prev){ + if (resultSet.length >= num) { + if(resultSet.length == 1 && num == 1){ + return resultSet[0] + } + return resultSet + } + else { + resultSet.push(action.prev) + } + + action = action.prev } - } - return vEntities; + + return resultSet } - serialize() + /** + * Get `num` actions (or a n | null} + */ + getNext(position, num=1) { - return { - id: this.id, - parent: this.parent, - properties: this.properties, - metaproperties: this.metaproperties, - interfaces: this.interfaces, - type: this.type - }; + // check if it is a valid position + if (position >= this.actions.size || position <= 1 || this.actions.size <= 1){ + return; + } + + // get actions + var action = this.actions.toArray()[position] + var resultSet = [] + while(action && action.next){ + if (resultSet.length >= num) { + if(resultSet.length == 1 && num == 1){ + return resultSet[0] + } + } + else { + resultSet.push(action.next) + } + + action = action.next + } + + return resultSet + } + + /** + * Get the position of an action in the list, + * it might be useful for pagination. + * + * Returns -1 in case action is not found in the list. + * + * @param {Trail.Action} action + * @returns {number} + */ + + getPositionOf(action) + { + //search action by eventId + var array = this.actions.toArray(); + for (var i = 0; i < this.actions.size; i++) { + if(array[i].event["id"] == action.event["id"]){ + return i; + } + } + return -1; + } + + /** + * Get an action at specified position. + * + * Position = 0 is equivalent to trail.head. + * + * @param {number} position + * @returns {Trail.Action} + */ + getActionAt(position) + { + //check if it is a valid position + if (position < 0 || position >= this.actions.size ){ + return; + } + + //return action by position in array + return this.actions.toArray()[position] } + /** + * Search for action(s) either by an event identifier or by filters. + * + * @param {Object | string | null} eventId + * @param {Object} filters + * @return {Array | null} + */ + search(eventId = null, filters) + { + if (!filters && eventId instanceof String) { + filters = {'from': null, 'fromAnchor': 'lambda', 'to': null, 'toAnchor': 'lambda'} + } + else if (!filters && eventId instanceof Object){ + filters = eventId + eventId = null + } + + // nothing to be found + if (!eventId && !filters['from'] && !filters['to']){ + return + } + + // search actions by eventId + var array = this.actions.toArray(); + if (eventId){ + for (var i = 0; i < this.actions.size; i++) { + if(array[i].event["id"] == eventId){ + return array[i]; + } + } + return; + } + + // search actions by filters + var resultSet = [] + for (var i = 0; i < this.actions.size; i++) { + if((filters['from'] && filters['to']) && array[i].from == filters['from'] && array[i].to == filters['to']){ + resultSet.push(array[i]) + } + else if(array[i].from == filters['from'] || array[i].to == filters['to']){ + resultSet.push(array[i]) + } + } + return resultSet + } + + /** + * + * @param {Array | null} actions + * @return {List | null} + */ + loadActions(actions = null) + { + + // use array of actions if passed + if(actions && Array.isArray(actions)) + { + if(actions[0].event.timestamp) + { + this.actions = new List(...sort(actions)); + } + else { + this.actions = new List(...actions); + } + return this.actions; + } + + let actionArray = [] + let actionIds = [] + for (let i in this.actions) + { + // create Action object from json object + if (this.actions[i] && this.actions[i].hasOwnProperty("from") && + this.actions[i].hasOwnProperty("to") && + this.actions[i].hasOwnProperty("agent") && + this.actions[i].hasOwnProperty("event")) + { + let from = new TrailNode(this.actions[i].from.nodeId, this.actions[i].from.nodeType, this.actions[i].from.targetAnchor); + let to = new TrailNode(this.actions[i].to.nodeId, this.actions[i].to.nodeType, this.actions[i].to.targetAnchor); + let event = { "id": this.actions[i].event.id, "type": this.actions[i].event.type, "properties": this.actions[i].event.properties, "timestamp": new Date(this.actions[i].event.timestamp)}; + let agent = this.actions[i].agent; + + actionArray.push(new Action({from, to, event, agent})); + } + else if(this.actions[i] && this.actions[i].hasOwnProperty("event") && this.actions[i].event.hasOwnProperty("timestamp")) + { + // we got event's id and timestamp + actionArray.push(new Action({event: {"id": i, "timestamp": new Date(this.actions[i].event.timestamp)}})); + } + else if(Array.isArray(this.actions)) + { + // all we got is an Array with the event's id + actionIds.push(this.actions[i]); + } + else + { + // all we got is event's id + actionIds.push(i); + } + } + + // sort action items before creating list + if (actionArray.length > 0) + { + this.actions = new List(...sort(actionArray)); + } + else if (actionIds.length > 0) + { + this.actions = actionIds; + } + } + + /** + * + * @param {Object} entity + * @returns {boolean} + */ static isValid(entity) { let isValid = false; if (entity && typeof (entity) === 'object' && !Array.isArray(entity)) { if (entity.hasOwnProperty('type') && entity.type === Types.TRAIL && - entity.hasOwnProperty('id') && entity.hasOwnProperty('parent')) + entity.hasOwnProperty('id') && entity.hasOwnProperty('parent')) { isValid = true; } @@ -120,41 +527,241 @@ class Trail extends HKEntity return isValid; } -} + /** + * + * @param {Array | null} actions + * @returns {Array} + */ + static sort(actions = null) + { + if (actions && Array.isArray(actions) && actions[0].event.timestamp.constructor === Date) + { + // sort and return object array based on timestamp + return actions.sort(function(action1, action2) + { + return action1.event['timestamp'] - action2.event['timestamp']; + }); + } + else if (actions && Array.isArray(actions) && (actions[0].event.timestamp.constructor === String || actions[0].event.timestamp.constructor === Number)) + { + // sort Action array based on timestamp + return actions.sort(function(action1, action2) + { + return new Date(action1.event['timestamp']) - new Date(action2.event['timestamp']); + }); + } + } -function _loadSteps() -{ - let steps = []; - for (let key in this.interfaces) + /** + * + * @returns {Object.} + * + */ + toJSON() { - let begin = new Date(Date.parse(this.interfaces[key].properties.begin)); - let end = new Date(Date.parse(this.interfaces[key].properties.end)); + let actionArray = []; - this.interfaces[key].properties.begin = begin; - this.interfaces[key].properties.end = end; + if (this.actions.constructor === List) + { + //in case we have only action ids and timestamps + if(this.actions.head && (!this.actions.head.from && !this.actions.head.to && !this.actions.head.agent)) + { + let action = this.actions.head; - steps.push({ key: key, begin: begin }); + while(action) + { + actionArray.push(action.id); + action = action.next; + } + } + else { + actionArray = this.actions.toArray(); + } + } + else if (Array.isArray(this.actions)) + { + actionArray = this.actions; + } + + return { + id: this.id, + parent: this.parent, + properties: this.properties, + metaproperties: this.metaproperties, + interfaces: this.interfaces, + actions: actionArray, + type: this.type + }; } - steps.sort( - (a, b) => + /** + * + * @returns {Object.} + */ + serialize() + { + let actions; + if(this.actions instanceof Array) + { + actions = this.actions; + } + else { - if (a.begin < b.begin) + actions = this.actions.toArray(); + } + + return { + id: this.id, + parent: this.parent, + properties: this.properties, + metaproperties: this.metaproperties, + interfaces: this.interfaces, + actions: actions, + type: this.type + }; + } +} + +/** + * TrailNode is basically an envelope for a hknode and a target anchor. + */ +Trail.TrailNode = class TrailNode + { + /** + * Constructs a new Trail object. + * + * @param {string} [nodeId] Some id string for this TrailNode. + * @param {string} [nodeType] Type of the TrailNode. + * @param {string} [targetAnchor] + */ + constructor(nodeId, nodeType, targetAnchor) + { + /** + * + * Id of this TrailNode. + * + * @public + * @type {string} + * + */ + this.nodeId = nodeId; + + /** + * + * Type of this TrailNode. + * + * @public + * @type {string} + * + */ + this.nodeType = nodeType; + + /** + * @public + * @type {string} + */ + this.targetAnchor = targetAnchor; + } + + /** + * + * @return {string} + */ + toString() + { + return this.nodeId + "#" + this.targetAnchor; + } + + // toJSON() { + // return this.nodeId + "#" + this.targetAnchor; + // } +} + +/** + * actions hold references to source and destination trail + * nodes (along with their respective target anchors), + * the related event and an agent (user, system or content). + */ +Trail.Action = class Action extends Item + { + /** Constructs a new Action object. + * + * @param {Object<{from: Trail.Action | null, to: Trail.Action | null, event: Object | null, agent: string | null}>} + * + */ + constructor({from = null, to = null, event = {}, agent = null} = {}) + { + super(); + this.from = from; + this.to = to; + this.agent = agent; + this.event = event; + this.type = Types.ACTION; + + // create timestamp if needed + // if(!this.event['timestamp']) + // { + // this.event['timestamp'] = new Date().getTime(); + // } + + // get event id or create a new one + if(!this.event['id'] || this.event['id'] == '') { - return -1; + this.event['id'] = this.event.type + '_' + this.event.timestamp; + this.id = this.event['id']; } - if (a.begin > b.begin) + else { - return 1; + this.id = this.event['id']; } + } - return 0; - }); + /** + * + * @returns {number} + */ + getTime() + { + return new Date(this.event['timestamp']).getTime(); + } - this.steps = steps; + /** + * + * @returns {string} + */ + toString() + { + if(this.from && this.to && this.agent && this.event && this.event.id ) + { + return JSON.stringify({ from: this.from, to: this.to, agent: this.agent, event: this.event, id: this.event.id }); + } + else + { + return this.event.id; + } + } + + /** + * + * @returns {Object.} + */ + toJSON() + { + if(this.from && this.to && this.agent && this.event && this.event.id ) + { + return { from: this.from, to: this.to, agent: this.agent, event: this.event, id: this.event.id }; + } + else + { + return this.event.id; + } + } } Trail.type = Types.TRAIL; const isValid = Trail.isValid; +const sort = Trail.sort; +const Action = Trail.Action; +const TrailNode = Trail.TrailNode; module.exports = Trail; diff --git a/src/types.js b/src/types.js index abb1592..897ae77 100644 --- a/src/types.js +++ b/src/types.js @@ -13,6 +13,7 @@ exports.REFERENCE = 'ref'; exports.INTERFACE = 'interface'; exports.BIND = 'bind'; exports.TRAIL = 'trail'; +exports.ACTION = 'action'; exports.VIRTUAL_NODE = 'virtualnode'; exports.VIRTUAL_CONTEXT = 'virtualcontext'; exports.VIRTUAL_LINK = 'virtuallink';