hypermedia-app / alcaeus Goto Github PK
View Code? Open in Web Editor NEWHydra core hypermedia library
Home Page: https://alcaeus.hydra.how
License: MIT License
Hydra core hypermedia library
Home Page: https://alcaeus.hydra.how
License: MIT License
import { Hydra } from 'alcaeus'
const response = await Hydra.loadResource('xyz')
[...response].map(resource => ({
id: resource.id,
isBlank: resource.isAnonymous
}))
To build hydra terms one has to write
import { Core } from 'alcaeus/lib/es/Constants'
const Class = Core.Vocab('Class')
The import is rather awkward. Should be exported from package main
import { Vocab } from 'alcaeus'
const Class = Vocab('Class')
Collection members don't get assigned the proper RDF type, which makes it impossible to use mixins for them.
Currently, to fetch resources, the client has to be used directly every time
const rep = await client.loadResource('http://www.markus-lanthaler.com/hydra/api-demo/issues/');
const issue = (await client.loadResource(rep.root.members[0].id)).root
It could be useful to have a simple method on each object which would follow the self-link (@id
) and return the representation.
const rep = await client.loadResource('http://www.markus-lanthaler.com/hydra/api-demo/issues/');
const issue = await rep.root.members[0].load()
Until 0.4.3 alcaeus attempted to escape resource identifiers to to avoid problems with internationalised URIs.
Turns out that this was done only partially, so effectively a graph would get split but breaking correspondence between JSON-LD objects. For example requesting http://example.com/with space
would give the following in-memory object
{
"@id": "http://example.com/with%20space",
"next": {
"@id": "http://example.com/with space"
}
}
Noticing how the two identifiers are actually same but represented with different string, this could lead to wrong objects being returned from a resource.
The fix is to never escape ids, keeping them as returned by JSON-LD processor. Instead, they will be attempted to be escaped when getting a single resource so that the following code should return the same resource both times:
import { Hydra } from 'alcaeus';
const rep = await Hydra.loadResource('http://example.com/with space');
rep.get('http://example.com/with space');
rep.get('http://example.com/with%20space');
Older browsers need polyfills for heracles to work. I'd like to add those pop\lyfills to tests on Travis to confirm they work
By importing hydra (import {Hydra} from 'alcaeus';
) I get this error
[!] Error: 'promises' is not exported by node_modules/jsonld/lib/index.js
https://rollupjs.org/guide/en/#error-name-is-not-exported-by-module
node_modules/alcaeus/lib/es/MediaTypeProcessors/RdfProcessor.js (2:9)
1: import JsonLdSerializer from '@rdfjs/serializer-jsonld';
2: import { promises as jsonld } from 'jsonld';
^
3: import $rdf from 'rdf-ext';
4: import stringToStream from 'string-to-stream';
Error: 'promises' is not exported by node_modules/jsonld/lib/index.js
at error (/app/node_modules/rollup/dist/shared/node-entry.js:5400:30)
at Module.error (/app/node_modules/rollup/dist/shared/node-entry.js:9820:16)
at handleMissingExport (/app/node_modules/rollup/dist/shared/node-entry.js:9721:28)
at Module.traceVariable (/app/node_modules/rollup/dist/shared/node-entry.js:10159:24)
at ModuleScope.findVariable (/app/node_modules/rollup/dist/shared/node-entry.js:8766:39)
at FunctionScope.findVariable (/app/node_modules/rollup/dist/shared/node-entry.js:3065:38)
at ChildScope.findVariable (/app/node_modules/rollup/dist/shared/node-entry.js:3065:38)
at MemberExpression.bind (/app/node_modules/rollup/dist/shared/node-entry.js:6530:49)
at CallExpression$1.bind (/app/node_modules/rollup/dist/shared/node-entry.js:3152:23)
at CallExpression$1.bind (/app/node_modules/rollup/dist/shared/node-entry.js:6718:15)
yarn init
yarn add rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs
rollup.config.js
with this content:import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
module.exports = {
input: 'main.js',
output: {
file: 'bundle.js',
format: 'es'
},
plugins: [ resolve({ browser: true }), commonjs() ]
};
main.js
:import { Hydra as client } from 'alcaeus';
client.loadResource('https://sources.test.wikibus.org/');
rollup main.js --config rollup.config.js
Is this package event meant to be used in browser? No demos on website does not work either.
And it isn't specified anywhere what is the primary platform for this package.
Currently a full build, minified, bundling all dependencies is a whopping 632 KB!
This could be reduce by removing or replacing the dependency on:
The easiest would be to replace lodash with native code or minimal polyfills.
I'm not sure whether there is a good alternative for rdf-ext at the moment. However, rdf-formats-common
is a big packages and individual parsers and serializers could be loaded on demand.
When there is no ApiDocumentation and I access the property operations
on a HydrarResource
, alcaeus fails with: TypeError: Cannot read property 'getOperations' of undefined
.
When there is no ApiDocumentation and I access the property operations
on a HydrarResource
, alcaeus either
As a newcomer this shows that an API has to implement ApiDocumentation in order to conform to Hydra.
The operation mixing is not getting applied because the shouldApply
is not exported
Currently they are only IView
which is not truthful enough
Here is the error when I try to import heracles
Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
Currently RDF literals are simply returned as strings regardless of the @type
.
They should be out of the box transformed in their appropriate type and this mechanism should be extensible to allow custom data types to be handled
Adding the well-known symbol will give nicer debugging output instead of whatever the result of applying mixins
It is possible that the server returns a representation of a different resource either by redirecting or by setting the Content-Location
header. In such case Heracles should honour the header or the redirect URI to look for the root object in the representation
Hi, I'm really interested in using Hydra in a new project, but I'm having trouble figuring out the best frontend client library.
I've found this project and Heracles.ts, the "Reference implementation of a Hydra client in TypeScript."
I don't really have too much time to figure out the technical differences -- on first glance they seem very similar.
I'd really appreciate a quick explanation of the major differences. Thannks!
ping @tpluscode @alien-mcl
Currently Alcaeus silently skips processing of representation which is not of a media type recognised by any media type processor.
This should be handled somewhat differently or at least an additional piece of information added to HydraResponse
so that the caller can get more information
While not exactly Hydra-specific, it would be good to add a convenient way to access Link
headers attached to loaded resource
I would like to have more specific ways to find collections in an API, e.g. to say something like
getCollections(Movie)
to find collections that manage specific types.
Originally posted by @angelo-v in #63 (comment)
Loading http://www.markus-lanthaler.com/hydra/api-demo/issues/2
returns an incomplete object.
Looks like only the @id
and @type
are preserved and the rest somehow lost...
Manages block never get initialized because the mixin is not loaded by default
Should concatenate all operations from all types
Alcaeus does not recognise subclassOf
statements for supported classes, which might greatly simplify the explicitness of an ApiDocumentation
.
For example, where currently it's necessary to duplicated supported properties and operations:
vocab:Issue a hydra:Class ;
hydra:supportedOperation api:GetIssue ;
hydra:supportedProperty [
hydra:property vocab:title
] , [
hydra:property vocab:assignee
] , [
hydra:property vocab:status
] .
vocab:DraftIssue a hydra:Class ;
hydra:supportedOperation api:GetIssue, api:UpdateDraft ;
hydra:supportedProperty [
hydra:property vocab:title
] , [
hydra:property vocab:assignee
] , [
hydra:property vocab:status
] .
It should instead be possible for DraftIssue
to "inherit" from Issue
vocab:Issue a hydra:Class ;
hydra:supportedOperation api:GetIssue ;
hydra:supportedProperty [
hydra:property vocab:title
] , [
hydra:property vocab:assignee
] , [
hydra:property vocab:status
] .
# all operations and properties merged with those of vocab:Issue
# practically `a hydra:Class` should also be unneeded
vocab:DraftIssue a hydra:Class ;
rdfs:subclassOf vocab:Issue ;
hydra:supportedOperation api:UpdateDraft .
To simplify clients' implementation it will be beneficial to add a simple getter on the IRdfProperty
mixin which returns true if a given property is a hydra:Link
This will be used to limit dereferncable (clickable) links in consumer applications for example when rendering the entrypoint or such as in hypermedia-app/generic.hypermedia.app#6
Hydra can also include operations inlined in the representation
{
"@id": "Person",
"hydra:operation": [
{ "@type": "hydra:DeleteOperation" },
{ "@type": "hydra:ReplaceOperation" }
]
}
They should be available on the object along with the Supported Operations from the included in the hydra:ApiDocumentation
It would be useful for HydraResource
(IResource
?) to include an indexer so that it's type safe to select child resources.
Currently resource['property']
returns causes:
TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'HydraResource'.
No index signature with a parameter of type 'string' was found on type 'HydraResource'.
Adding { [ prop: string ]: HydraResource }
to the interface could be useful
I tried using heracles with just the angular quickstart tutorial, and so far Visual Studio doesn't recognize heracles typings unless I add this line to "typings/index.d.ts":
/// <reference path="../node_modules/heracles/heracles.d.ts" />
When running
typings install npm~heracles --global
it says there's no entry for heracles.
So far, when trying to install/run heracles just through the steps in the readme, it won't work. Could you update it, so that newcomers have a simpler start?
And thanks a lot for publishing this repository, looks like a good project!
As surfaced but HydraCG/api-examples#4, Alcaeus needs to natively support hydra:collection
property.
I can think of two ways to add it to the API.
First would be to implicitly treat hydra:collection
as a hydra:Link
property. I would add a getLinks()
method to the base resource type so which gathers explicit links and collections in a single array.
const response = await Hydra.loadResource(url)
const resource = response.root
// will return collections and links defined as `hydra:Link`
resource.getLinks()
I favour this approach as it will play nice with the generic app and is one less place too look at when processing Hydra representations.
Alternatively, a dedicated property/method could be introduced which only accesses the related collections.
I do feel though that it's just bloat.
It is necessary to allow app authors to authenticate against their API so that the calls can be properly authorised.
To do that, I'll want to provide a hook where the request can be modified before it goes to the server. This will be the point where consumers will add the Authorization
header among other things.
When same property appears on two supported classes implemented by a the same resource.
const { Hydra } = require('alcaeus')
const rep = await Hydra.loadResource('https://sources.test.wikibus.org/book/1081')
rep.root.getProperties()
.reduce((res, tuple) => {
return {
...res,
[tuple.supportedProperty.property.id]: (res[tuple.supportedProperty.property.id] || 0) + 1
}
}, {})
Currently Alcaeus is very eager to load and process everything it gets.
It works fine for small datasets but apparently the API exposed by @bergos has a large API doc with 4400+ resources. To process that takes over 40 seconds.
The bottleneck turns out to be a little needed function which finds reverse links between resources such as are not easily visible in a free structure. There are two ways to improve that:
Expected Behaviour
The following code (based on the project documentation) should not throw an error when run in a Node environment:
File src/index.js
:
const { Hydra } = require('alcaeus')
console.log('testing')
Actual Behaviour
The command node src/index.js
results in the following syntax error:
/Users/ejbyne/projects/json-ld/hydra-client-test/src/alcaeus.js:1
(function (exports, require, module, __filename, __dirname) { import { Hydra } from 'alcaeus'
^
SyntaxError: Unexpected token {
at new Script (vm.js:79:7)
at createScript (vm.js:251:10)
at Object.runInThisContext (vm.js:303:10)
at Module._compile (internal/modules/cjs/loader.js:656:28)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
at Module.load (internal/modules/cjs/loader.js:598:32)
at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
at Function.Module._load (internal/modules/cjs/loader.js:529:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:741:12)
at startup (internal/bootstrap/node.js:285:19)
Workaround
The main
entrypoint for the Alcaeus module is configured in package.json
as lib/index.js
. This code includes ES modules, which are not yet fully supported by Node. I would not expect to need to use Babel to compile my project's dependencies to ES5 (and also came across some problems when I tried to this).
The module is however already compiled to ES5 in the package's dist
folder. I was able to get my code running by changing the import as follows:
const { Hydra } = require('alcaeus/dist/alcaeus')
console.log('testing')
Requested Fix
Either the above workaround should be made clear in the documentation or, preferably, the entrypoint for the module should point to Node-compatible code. It should not be necessary for the published code to include ES5, ES6 and TypeScript versions of the source code - ES5 alone should suffice.
** Context **
▶ npm -v
6.4.1
▶ node -v
v10.12.0
OS: Linux, MacOS
Related to #35, which fixed ResourceGraph.root
so that is attempts to decode inconsistent escaping of requested URI
Same has to be done to ExactIdMatchSelector
The client should not try to fetch the api documentation if there is no Link
header (or is invalid)
Currently it will attempt a /null
call.
Hi, could you add types into the package itself or create them in @types/alcaeus ? Thanks
When a request to /foo
gets redirected to /bar
I would somewhat assume that latter should be the root resource.
Currently, however, /foo
will take precedence and if the redirected response contains both resource, the former will be returened
The manages block
let's the server inform the client what kind of members to expect within a collection. Not unlike generics in some languages like C#
class Person
{
public string Name { get; set; }
public string LastName { get; set; }
}
var friends = List<Person>();
Such could be equivalent to having the Person
as supported class in the API Documentation and used with the collection's manages block
{
"supportedClass": {
"@id": "Person",
"supportedProperty": [
{
"property": {
"@id": "schema:givenName",
"range": "xsd:string"
},
}
{
"property": {
"@id": "schema:familyName",
"range": "xsd:string"
}
}
]
}
}
{
"@id": "/friends",
"@type": "Collection",
"manages": {
"subject": "/Tomasz",
"property": "foaf:knows",
"object": "Person"
}
}
Calling Operation#invoke
performs a request but the body is completely ignored.
Given The client at https://client.example
loads an API entrypoint at http://api.example/
And The Link-Header of the response contains a relative link to the API doc: Link: </doc>; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"
When the client loads the API doc
Then it uses the absolute URI http://api.example/doc
Then it uses the absolute URI http://client.example/doc
https://tools.ietf.org/html/rfc3986#section-5.1.3
5.1.3. Base URI from the Retrieval URI
If no base URI is embedded and the representation is not encapsulated
within some other entity, then, if a URI was used to retrieve the
representation, that URI shall be considered the base URI. Note that
if the retrieval was the result of a redirected request, the last URI
used (i.e., the URI that resulted in the actual retrieval of the
representation) is the base URI.
We defined an API which also contains the links to the objects. It was designed like this to reduce the number of requests required to use the API. It looks like Alcaeus can only handle objects which are connected to triples with a subject matching the resource IRI. It would be great to have an option to also access the objects not connected to the resource IRI.
Two ideas how it could be implemented:
loadResource
returns also array objects to manually search for the wanted objectMost of the operations in our API return a lot of triples. This is a good candidate for testing.
If the content-type
contains a charset like application/ld+json; charset=utf-8
, the parser is not found. I think in this line the charset should be cropped. In rdf-fetch
it's done like this.
Example to reproduce the error:
const Hydra = require('alcaeus').Hydra
Hydra.loadResource('http://ld.stadt-zuerich.ch/api').then(res => {
console.log(res)
}).catch((err) => {
console.error(err)
})
Need a root selector which will attempt to get the correct root for resource returned for a different URI, such as when the actual id does include a trailing slash and the requested URL did not
const rep = await client.loadResource('http://wikibus-test.gear.host/');
/*
{
"@id": "http://wikibus-test.gear.host"
}
*/
// will be null
rep.root;
// and should be same as
rep.get('http://wikibus-test.gear.host');
I see this library relies on window.fetch
, do you think we could safely switch to isomorphic-fetch
to have the full support of server-side?
Hydra vocabulary clearly states that hydra:collection
is a hydra:Link
. Whatever tool performs the assertion regarding hydra vocabulary must understand it's semantics.
Originally posted by @alien-mcl in HydraCG/api-examples#8
Enabling typings generation (#32) forced me to make some members public on exported classes. I will change them back once microsoft/TypeScript#17293 is fixed.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.