Created
December 16, 2019 18:38
-
-
Save tpluscode/ca40c4e834650b2b70d998f3fb940adf to your computer and use it in GitHub Desktop.
Typed clownface
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WARNING | |
// | |
// Ugly implementation and not exactly like in the examples. but similar | |
import { Literal, NamedNode } from 'rdf-js' | |
import Clownface from 'clownface/lib/Clownface' | |
import rdf from 'rdf-ext' | |
import { TypedClownfaceEntity } from './TypedClownfaceEntity' | |
const trueLiteral: Literal = rdf.literal(true) | |
type PropRef = string | NamedNode | |
type TypedEntityConstructor = new (...args: any[]) => TypedClownfaceEntity | |
interface AccessorOptions { | |
path?: PropRef | PropRef[]; | |
as?: 'term' | TypedEntityConstructor; | |
array?: boolean; | |
} | |
interface LiteralAccessorOption extends AccessorOptions { | |
type?: typeof Boolean; | |
} | |
function predicate (cf: Clownface, termOrString: PropRef) { | |
if (typeof termOrString === 'string') { | |
return cf.namedNode(termOrString) | |
} else { | |
return cf.node(termOrString) | |
} | |
} | |
function getNode (cf: Clownface, path: Clownface[]) { | |
return path | |
.reduce((node, prop) => { | |
return node.out(prop) | |
}, cf) | |
} | |
function getPredicate (cf: any, name: PropertyKey): NamedNode { | |
return (cf.constructor).__ns[name.toString()] | |
} | |
function getPath (protoOrDescriptor: any, cf: Clownface, path: PropRef | PropRef[], name: PropertyKey) { | |
return (path ? Array.isArray(path) ? path : [ path ] : [ getPredicate(protoOrDescriptor, name) ]) | |
.map(termOrString => predicate(cf, termOrString)) | |
} | |
export function property (options: AccessorOptions = {}) { | |
const Type = options.as || 'term' | |
return (protoOrDescriptor: any, name?: PropertyKey): any => { | |
Object.defineProperty(protoOrDescriptor, name, { | |
get (this: Clownface): any { | |
const node = getNode(this, getPath(protoOrDescriptor, this, options.path, name)) | |
const values = node.map(term => { | |
if (Type === 'term') { | |
return term.term | |
} | |
return new Type(term) | |
}) | |
if (options.array === true) { | |
return values | |
} | |
if (values.length > 1) { | |
throw new Error(`Multiple terms found where 0..1 was expected`) | |
} | |
return values[0] | |
}, | |
set (this: Clownface, value: any) { | |
const path = getPath(protoOrDescriptor, this, options.path, name) | |
let node: Clownface | |
node = path.length === 1 ? this : getNode(this, path.slice(path.length - 1)) | |
const lastPredicate = path[path.length - 1] | |
node.deleteOut(lastPredicate) | |
.addOut(lastPredicate, value) | |
}, | |
}) | |
} | |
} | |
export function literal (options: LiteralAccessorOption = {}) { | |
return (protoOrDescriptor: any, name?: PropertyKey): any => { | |
Object.defineProperty(protoOrDescriptor, name, { | |
get (this: Clownface): any { | |
const node = getNode(this, getPath(protoOrDescriptor, this, options.path, name)) | |
if (options.type === Boolean) { | |
return trueLiteral.equals(node.term) | |
} | |
return node.value | |
}, | |
set (this: Clownface, value: any) { | |
const path = getPath(protoOrDescriptor, this, options.path, name) | |
let node: Clownface | |
node = path.length === 1 ? this : getNode(this, path.slice(path.length - 1)) | |
const lastPredicate = path[path.length - 1] | |
node.deleteOut(lastPredicate) | |
.addOut(lastPredicate, value) | |
}, | |
}) | |
} | |
} | |
export function namespace (ns: any) { | |
return (classOrDescriptor: any) => { | |
classOrDescriptor.__ns = ns | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Clownface from 'clownface/lib/Clownface' | |
import { namespace, literal, property } from './typedClownface' | |
import ns from '@rdfjs/namespace' | |
class Table extends Clownface { | |
// path - one or more predicates to traverse | |
// as - constructor to wrap the node with (another subclass of Clownface) | |
// array - how to handle multiple objects | |
// | |
// equivalent to | |
// this.out(dataCube.source) | |
// .out(dataCube.column) | |
// .map(cf => new Column(cf)) | |
@property({ path: [ dataCube.source, dataCube.column ], as: Column, array: true }) | |
public readonly columns: Column[] | |
} | |
// class decorator which acts like JSON-LD @base | |
@namespace(ns('http://schema.org/')) | |
class Column extends Clownface { | |
// without path param, uses JS prop name for predicate | |
// equivalent to | |
// this.out(this.namedNode('http://schema.org/name')).value | |
@literal() | |
public name: string | |
// type param defines built-in cast of the literal value | |
@literal({ path: csvw.suppressOutput, type: Boolean }) | |
public suppressed: boolean | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import $rdf = require('rdf-ext') | |
import clownface = require('clownface') | |
import { Table } from './types' | |
import ns from '@rdfjs/namespace' | |
import { prefixes } from '@zazuko/rdf-vocabularies' | |
const rdf = ns(prefixes.rdf) | |
const table = new Table({ dataset: $rdf.dataset(), term: $rdf.namedNode('http://example.com/table' }) | |
// an entity is still a clownface object | |
const types = table.out(rdf.type).terms | |
// access columns like they were native objects | |
table.columns.forEach((column, i) => { | |
// setters also work | |
column.name = `Column ${i}` | |
column.suppressed = true | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment