diff --git a/src/graph-types.js b/src/graph-types.js index a87bec704..2a3416309 100644 --- a/src/graph-types.js +++ b/src/graph-types.js @@ -17,6 +17,23 @@ * limitations under the License. */ +const IDENTIFIER_PROPERTY_ATTRIBUTES = { + value: true, + enumerable: false, + configurable: false, + writable: false +} + +const NODE_IDENTIFIER_PROPERTY = '__isNode__' +const RELATIONSHIP_IDENTIFIER_PROPERTY = '__isRelationship__' +const UNBOUND_RELATIONSHIP_IDENTIFIER_PROPERTY = '__isUnboundRelationship__' +const PATH_IDENTIFIER_PROPERTY = '__isPath__' +const PATH_SEGMENT_IDENTIFIER_PROPERTY = '__isPathSegment__' + +function hasIdentifierProperty (obj, property) { + return (obj && obj[property]) === true +} + /** * Class for Node Type. */ @@ -54,7 +71,7 @@ class Node { for (let i = 0; i < this.labels.length; i++) { s += ':' + this.labels[i] } - let keys = Object.keys(this.properties) + const keys = Object.keys(this.properties) if (keys.length > 0) { s += ' {' for (let i = 0; i < keys.length; i++) { @@ -68,6 +85,21 @@ class Node { } } +Object.defineProperty( + Node.prototype, + NODE_IDENTIFIER_PROPERTY, + IDENTIFIER_PROPERTY_ATTRIBUTES +) + +/** + * Test if given object is an instance of {@link Node} class. + * @param {Object} obj the object to test. + * @return {boolean} `true` if given object is a {@link Node}, `false` otherwise. + */ +function isNode (obj) { + return hasIdentifierProperty(obj, NODE_IDENTIFIER_PROPERTY) +} + /** * Class for Relationship Type. */ @@ -114,7 +146,7 @@ class Relationship { */ toString () { let s = '(' + this.start + ')-[:' + this.type - let keys = Object.keys(this.properties) + const keys = Object.keys(this.properties) if (keys.length > 0) { s += ' {' for (let i = 0; i < keys.length; i++) { @@ -128,6 +160,21 @@ class Relationship { } } +Object.defineProperty( + Relationship.prototype, + RELATIONSHIP_IDENTIFIER_PROPERTY, + IDENTIFIER_PROPERTY_ATTRIBUTES +) + +/** + * Test if given object is an instance of {@link Relationship} class. + * @param {Object} obj the object to test. + * @return {boolean} `true` if given object is a {@link Relationship}, `false` otherwise. + */ +function isRelationship (obj) { + return hasIdentifierProperty(obj, RELATIONSHIP_IDENTIFIER_PROPERTY) +} + /** * Class for UnboundRelationship Type. * @access private @@ -181,7 +228,7 @@ class UnboundRelationship { */ toString () { let s = '-[:' + this.type - let keys = Object.keys(this.properties) + const keys = Object.keys(this.properties) if (keys.length > 0) { s += ' {' for (let i = 0; i < keys.length; i++) { @@ -195,6 +242,21 @@ class UnboundRelationship { } } +Object.defineProperty( + UnboundRelationship.prototype, + UNBOUND_RELATIONSHIP_IDENTIFIER_PROPERTY, + IDENTIFIER_PROPERTY_ATTRIBUTES +) + +/** + * Test if given object is an instance of {@link UnboundRelationship} class. + * @param {Object} obj the object to test. + * @return {boolean} `true` if given object is a {@link UnboundRelationship}, `false` otherwise. + */ +function isUnboundRelationship (obj) { + return hasIdentifierProperty(obj, UNBOUNT_RELATIONSHIP_IDENTIFIER_PROPERTY) +} + /** * Class for PathSegment Type. */ @@ -225,6 +287,21 @@ class PathSegment { } } +Object.defineProperty( + PathSegment.prototype, + PATH_SEGMENT_IDENTIFIER_PROPERTY, + IDENTIFIER_PROPERTY_ATTRIBUTES +) + +/** + * Test if given object is an instance of {@link PathSegment} class. + * @param {Object} obj the object to test. + * @return {boolean} `true` if given object is a {@link PathSegment}, `false` otherwise. + */ +function isPathSegment (obj) { + return hasIdentifierProperty(obj, PATH_SEGMENT_IDENTIFIER_PROPERTY) +} + /** * Class for Path Type. */ @@ -260,4 +337,30 @@ class Path { } } -export { Node, Relationship, UnboundRelationship, Path, PathSegment } +Object.defineProperty( + Path.prototype, + PATH_IDENTIFIER_PROPERTY, + IDENTIFIER_PROPERTY_ATTRIBUTES +) + +/** + * Test if given object is an instance of {@link Path} class. + * @param {Object} obj the object to test. + * @return {boolean} `true` if given object is a {@link Path}, `false` otherwise. + */ +function isPath (obj) { + return hasIdentifierProperty(obj, PATH_IDENTIFIER_PROPERTY) +} + +export { + Node, + isNode, + Relationship, + isRelationship, + UnboundRelationship, + isUnboundRelationship, + Path, + isPath, + PathSegment, + isPathSegment +} diff --git a/src/record.js b/src/record.js index 0fc9fffba..88e03b6a3 100644 --- a/src/record.js +++ b/src/record.js @@ -20,7 +20,7 @@ import { newError } from './error' function generateFieldLookup (keys) { - let lookup = {} + const lookup = {} keys.forEach((name, idx) => { lookup[name] = idx }) @@ -85,6 +85,26 @@ class Record { } } + /** + * Run the given function for each field in this record. The function + * will get three arguments - the value, the key and this record, in that + * order. + * + * @param {function(value: Object, key: string, record: Record)} visitor the function to apply on each field + * and return a value that is saved to the returned Array. + * + * @returns {Array} + */ + map (visitor) { + const resultArray = [] + + for (let i = 0; i < this.keys.length; i++) { + resultArray.push(visitor(this._fields[i], this.keys[i], this)) + } + + return resultArray + } + /** * Generates an object out of the current Record * diff --git a/src/spatial-types.js b/src/spatial-types.js index 60ce04fd4..fc96ceb7c 100644 --- a/src/spatial-types.js +++ b/src/spatial-types.js @@ -62,11 +62,11 @@ export class Point { toString () { return this.z || this.z === 0 ? `Point{srid=${formatAsFloat(this.srid)}, x=${formatAsFloat( - this.x - )}, y=${formatAsFloat(this.y)}, z=${formatAsFloat(this.z)}}` + this.x + )}, y=${formatAsFloat(this.y)}, z=${formatAsFloat(this.z)}}` : `Point{srid=${formatAsFloat(this.srid)}, x=${formatAsFloat( - this.x - )}, y=${formatAsFloat(this.y)}}` + this.x + )}, y=${formatAsFloat(this.y)}}` } } @@ -77,7 +77,8 @@ function formatAsFloat (number) { Object.defineProperty(Point.prototype, POINT_IDENTIFIER_PROPERTY, { value: true, enumerable: false, - configurable: false + configurable: false, + writable: false }) /** diff --git a/src/temporal-types.js b/src/temporal-types.js index f9d68a3a4..7c524244c 100644 --- a/src/temporal-types.js +++ b/src/temporal-types.js @@ -28,7 +28,8 @@ import { newError } from './error' const IDENTIFIER_PROPERTY_ATTRIBUTES = { value: true, enumerable: false, - configurable: false + configurable: false, + writable: false } const DURATION_IDENTIFIER_PROPERTY = '__isDuration__' diff --git a/test/record.test.js b/test/record.test.js index 3070f182d..8fdb4083e 100644 --- a/test/record.test.js +++ b/test/record.test.js @@ -113,4 +113,17 @@ describe('#unit Record', () => { // Then expect(result).toEqual([['Bob', 'name', record], [45, 'age', record]]) }) + + it('should allow map function for the record', () => { + // Given + const record = new Record(['name', 'age'], ['Bob', 45]) + + // When + const result = record.map((value, key, rec) => { + return [value, key, rec] + }) + + // Then + expect(result).toEqual([['Bob', 'name', record], [45, 'age', record]]) + }) }) diff --git a/test/types/graph-types.test.ts b/test/types/graph-types.test.ts index 5cb7d1dd5..3ba5d67c9 100644 --- a/test/types/graph-types.test.ts +++ b/test/types/graph-types.test.ts @@ -22,7 +22,12 @@ import { Path, PathSegment, Relationship, - UnboundRelationship + UnboundRelationship, + isNode, + isPath, + isPathSegment, + isRelationship, + isUnboundRelationship } from '../../types/graph-types' import Integer, { int } from '../../types/integer' @@ -32,6 +37,7 @@ const node1Id: Integer = node1.identity const node1Labels: string[] = node1.labels const node1Props: object = node1.properties const isNode1: boolean = node1 instanceof Node +const isNode1B: boolean = isNode(node1) const node2: Node = new Node(2, ['Person', 'Employee'], { name: 'Alice' @@ -48,6 +54,7 @@ const rel1End: Integer = rel1.end const rel1Type: string = rel1.type const rel1Props: object = rel1.properties const isRel1: boolean = rel1 instanceof Relationship +const isRel1B: boolean = isRelationship(rel1) const rel2: UnboundRelationship = new UnboundRelationship(int(1), 'KNOWS', { since: 12345 @@ -58,6 +65,7 @@ const rel2Id: Integer = rel2.identity const rel2Type: string = rel2.type const rel2Props: object = rel2.properties const isRel2: boolean = rel2 instanceof UnboundRelationship +const isRel2B: boolean = isUnboundRelationship(rel2) const rel4: Relationship = new Relationship(2, 3, 4, 'KNOWS', { since: 12345 @@ -82,6 +90,7 @@ const pathSegment1Start: Node = pathSegment1.start const pathSegment1Rel: Relationship = pathSegment1.relationship const pathSegment1End: Node = pathSegment1.end const isPathSegment1: boolean = pathSegment1 instanceof PathSegment +const isPathSegment1B: boolean = isPathSegment(pathSegment1) const pathSegment2: PathSegment = new PathSegment(node2, rel4, node2) const pathSegment2Start: Node = pathSegment2.start @@ -94,6 +103,7 @@ const path1End: Node = path1.end const path1Segments: PathSegment[] = path1.segments const path1Length: number = path1.length const isPath1: boolean = path1 instanceof Path +const isPath1B: boolean = isPath(path1) const path2: Path = new Path(node2, node2, [pathSegment2]) const path2Start: Node = path2.start diff --git a/types/graph-types.d.ts b/types/graph-types.d.ts index fe689a312..8c6b37451 100644 --- a/types/graph-types.d.ts +++ b/types/graph-types.d.ts @@ -73,12 +73,27 @@ declare class Path { constructor(start: Node, end: Node, segments: PathSegment[]) } +declare function isNode(obj: object): boolean + +declare function isRelationship(obj: object): boolean + +declare function isUnboundRelationship(obj: object): boolean + +declare function isPath(obj: object): boolean + +declare function isPathSegment(obj: object): boolean + export { Node, Relationship, UnboundRelationship, Path, PathSegment, + isNode, + isRelationship, + isUnboundRelationship, + isPath, + isPathSegment, NumberOrInteger, StandardDate } diff --git a/types/record.d.ts b/types/record.d.ts index 2622a9419..9f6205ba6 100644 --- a/types/record.d.ts +++ b/types/record.d.ts @@ -19,6 +19,8 @@ declare type Visitor = (value: any, key: string, record: Record) => void +declare type MapVisitor = (value: any, key: string, record: Record) => T + declare class Record { keys: string[] length: number @@ -31,6 +33,8 @@ declare class Record { forEach(visitor: Visitor): void + map(visitor: MapVisitor): T[] + toObject(): object get(key: string | number): any