hydracg / heracles.ts Goto Github PK
View Code? Open in Web Editor NEWReference implementation of a Hydra client in TypeScript
Home Page: https://hydracg.github.io/Heracles.ts/
License: MIT License
Reference implementation of a Hydra client in TypeScript
Home Page: https://hydracg.github.io/Heracles.ts/
License: MIT License
Current implementation of the Heracles.ts API does not take into account on how to create a correct request when an operation is a composition of:
This enforces on the API user additional knowledge to make a distinction between IRI template/non-IRI template requests before making a final server call.
Some more unified mechanism would come in handy which would just expect an operation and body resource to be passed regardless the fact that the operation needs (or not) an IRI template expanded, i.e.:
const client = new HydraClient();
const addMemberOperation =
.getApiDocumentation("http://temp.uri")
.getEntryPoint()
.collections.first()
.operations
.ofType("http://schema.org/AddAction").first();
client.invoke(addMemberOperation, myNewCollectionMember);
and the operation mentioned contains an IRI template that needs to be filled with values.
As @elf-pavlik stated in issue #8, currently Heracles.ts supports only JSON-LD RDF serialization. It is desired to add support for other serializations, i.e. by using N3.js for these media types:
It seems that there are API's that has a broken API documentation that does not provide entry point link (i.e. example API for hydra console: http://www.markus-lanthaler.com/hydra/event-api/).
While this behavior is in fact an incorrect implementation of the hydra vocabulary, it seems that for situation when an actual entry point's Url is provided for API discovery, it is possible to compensate that misbehavior.
Consider implementing that compensation as it may enable more APIs available in the wilderness.
I was trying to experiment with hydra client and found it was reference implementation. So i decided to use it. I followed the instructions given in the Readme. I wrote the following code:
import HydraClientFactory from "@hydra-cg/heracles.ts"; let hydraClient = HydraClientFactory.configure().withDefaults().andCreate(); const main = async () => { const resource = await hydraClient.getResource("http://localhost:8000/api"); } main();
And got the following error:
/home/priyanshu/Documents/heracles-demo/node_modules/@hydra-cg/heracles.ts/src/HydraClientFactory.js:44 .with(fetch.bind(window)); ^ ReferenceError: window is not defined at HydraClientFactory.withDefaults (/home/priyanshu/Documents/heracles-demo/node_modules/@hydra-cg/heracles.ts/src/HydraClientFactory.js:44:30) at Object.<anonymous> (/home/priyanshu/Documents/heracles-demo/main.ts:2:50) at Module._compile (internal/modules/cjs/loader.js:776:30) at Module.m._compile (/home/priyanshu/.nvm/versions/node/v10.16.0/lib/node_modules/ts-node/src/index.ts:858:23) at Module._extensions..js (internal/modules/cjs/loader.js:787:10) at Object.require.extensions.(anonymous function) [as .ts] (/home/priyanshu/.nvm/versions/node/v10.16.0/lib/node_modules/ts-node/src/index.ts:861:12) at Module.load (internal/modules/cjs/loader.js:653:32) at tryModuleLoad (internal/modules/cjs/loader.js:593:12) at Function.Module._load (internal/modules/cjs/loader.js:585:3) at Function.Module.runMain (internal/modules/cjs/loader.js:829:12)
Any Help will be appreciated. Thank you.
I'd propose to go with MIT. Thoughts? @alien-mcl
Should Heracle's remove hypermedia controls from the payload or not? If so, should it do so optionally? Or should this be done at a higher layer?
Please provide some "Quick Start" and examples of how to install and use Heracles.ts. The generated TypeDoc is not really helpful to get started.
PR #11 introduces in JSON-LD context
"http://www.w3.org/ns/hydra/core#Collection": "http://www.w3.org/ns/hydra/core#Collection",
"http://www.w3.org/ns/hydra/core#Resource": "http://www.w3.org/ns/hydra/core#Resource"
to my understanding to enforce full IRIs which later get matched against hydra
helper exported from namespaces
module. Possibly currently JSON-LD context, flattening of the response and use of hydra helper from namespaces module don't work together as good as they can.
@lanthaler also questioned the two lines above in JSON-LD context in #11 (comment)
I see this issue a distinct but related to #14
Hi,
I'm trying to loop over a collection and extract the various property values from the members of the collection. Let's say I have the following abbreviated response from my server:
"member": [
{
"@id": "example:2bfd5353-6842-48f7-9f8c-111474f57dfb",
"@type": "schema:person",
"label": "Person 1"
},
{
"@id": "example:108d8384-a55a-4b21-8511-654fb3830d5f",
"@type": "schema:person",
"label": "Person 2"
},
],
Then, I'm looping over the response.
const data = await Client.getResource(
"http://localhost:4000/persons"
);
for (const person of data.members) {
const iri = person.iri;
const label = person.label; // How can I do this?
}
As written in the comment above, how can I get properties apart from IRI and type from the members? I couldn't find an actual example in the documentation, tests, use cases and other places I was looking. Any help is greatly appreciated!
Best,
Daniel
This was discussed in #2 but merits an issue on its own. @elf-pavlik's current proposal is to implement something along the lines of
var operation = client.get("http://example.com")
.getApiDocumentation()
.getEntryPoint()
.selectCollection({
property: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
object: "http://schema.org/Event"
})
.getOperationOfType('http://schema.org/CreateAction');
with possible specialization such selectCollectionByMembersType
as a for selecting a collection based on the type of its members:
function selectCollectionByMembersType (membersType) {
return this.selectCollection({
property: "http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
object: membersType
}))
}
Hello.
When I call the following function:
const testHeraclesApiDoc = async () => {
let hydraClient = HydraClientFactory.configure()
.withDefaults()
.withAllLinks()
.andCreate();
const apiDoc = await hydraClient.getApiDocumentation("http://localhost:8080/api");
console.log(await apiDoc.getEntryPoint());
};
I get the following error:
Uncaught (in promise) Error: There was no Url provided.
getUrl HydraClient.js:111
getResource HydraClient.js:62
getEntryPoint factories.js:13
testHeraclesApiDoc App.tsx:13
HydraClient.js:111
testHeraclesApiDoc App.tsx:13
AsyncFunctionThrow self-hosted:698
I am trying to use heracles.ts in a react application that consumes a mockup hydra API wich explicitly defines its entry point. This is the json-ld output of the documentation path:
{
"@id": "api/vocab",
"@type": "hydra:ApiDocumentation",
"hydra:entrypoint": "http://localhost:8080/api",
"hydra:supportedClass": [
{
"@id": "api/vocab#EntryPoint",
"@type": [
"hydra:Class",
"sh:Shape"
],
"hydra:description": "The main entry point of the API",
"hydra:supportedOperation": {
"@type": "hydra:Operation",
"hydra:description": "The API main entry point",
"hydra:method": "GET",
"hydra:returns": {
"@id": "api/vocab#EntryPoint"
}
},
"hydra:supportedProperty": {
"@id": "api/vocab#personas",
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "api/vocab#personas-link",
"@type": "hydra:Link",
"rdfs:comment": "Enlace a la colección de personas",
"rdfs:label": "personas-link",
"rdfs:range": {
"@id": "api/vocab#PersonaCollection"
},
"hydra:supportedOperation": [
{
"@type": "hydra:Operation",
"hydra:method": "GET",
"hydra:returns": {
"@id": "api/vocab#PersonaCollection"
}
},
{
"@type": "hydra:Operation",
"hydra:expects": {
"@id": "http://www.personas-mexico.org/persona#Persona"
},
"hydra:method": "POST",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Persona"
}
}
]
}
},
"hydra:title": "EntryPoint"
},
{
"@id": "api/vocab#PersonaCollection",
"@type": [
"hydra:Class",
"hydra:Collection"
],
"hydra:description": "Coleción de todas las personas",
"hydra:supportedOperation": [],
"hydra:title": "PersonaCollection",
"lvz:memberClass": "http://www.personas-mexico.org/persona#Persona"
},
{
"@id": "http://www.personas-mexico.org/persona#Persona",
"@type": [
"hydra:Class",
"sh:Shape"
],
"hydra:description": "Una persona",
"hydra:supportedOperation": [
{
"@type": "hydra:Operation",
"hydra:method": "GET",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Persona"
}
},
{
"@type": "hydra:Operation",
"hydra:method": "DELETE"
}
],
"hydra:supportedProperty": [
{
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#tieneSexo",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
"rdfs:comment": "El género o sexo de la persona",
"rdfs:label": "tieneSexo",
"rdfs:range": {
"@id": "xsd:string"
},
"hydra:supportedOperation": []
},
"hydra:required": true
},
{
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#tieneNombre",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
"rdfs:comment": "El nombre de la persona",
"rdfs:label": "tieneNombre",
"rdfs:range": {
"@id": "xsd:string"
},
"hydra:supportedOperation": []
},
"hydra:required": true
},
{
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#tieneSegundoApellido",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
"rdfs:comment": "El segundo apellido de la persona",
"rdfs:label": "tieneSegundoApellido",
"rdfs:range": {
"@id": "xsd:string"
},
"hydra:supportedOperation": []
},
"hydra:required": false
},
{
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#tienePrimerApellido",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
"rdfs:comment": "El primer apellido de la persona",
"rdfs:label": "tienePrimerApellido",
"rdfs:range": {
"@id": "xsd:string"
},
"hydra:supportedOperation": []
},
"hydra:required": true
}
],
"hydra:title": "Persona"
},
{
"@id": "http://www.personas-mexico.org/persona#Medico",
"@type": [
"hydra:Class",
"sh:Shape"
],
"hydra:description": "Un médico",
"hydra:supportedOperation": [
{
"@type": "hydra:Operation",
"hydra:method": "GET",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Medico"
}
},
{
"@type": "hydra:Operation",
"hydra:method": "DELETE"
}
],
"hydra:supportedProperty": [
{
"@id": "http://www.personas-mexico.org/persona#info-basica-link",
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#info-basica",
"@type": "hydra:Link",
"rdfs:comment": "Información básica de una persona",
"rdfs:label": "info-basica",
"rdfs:range": {
"@id": "http://www.personas-mexico.org/persona#Persona"
},
"hydra:supportedOperation": [
{
"@type": "hydra:Operation",
"hydra:method": "GET",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Persona"
}
},
{
"@type": "hydra:Operation",
"hydra:expects": {
"@id": "http://www.personas-mexico.org/persona#Persona"
},
"hydra:method": "POST",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Persona"
}
}
]
},
"hydra:readonly": true
},
{
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#tieneCedulaProfesional",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
"rdfs:comment": "La cédula profesional de un médico",
"rdfs:label": "tieneCedulaProfesional",
"rdfs:range": {
"@id": "xsd:string"
},
"hydra:supportedOperation": []
},
"hydra:required": true
}
],
"hydra:title": "Medico"
},
{
"@id": "http://www.personas-mexico.org/persona#Paciente",
"@type": [
"hydra:Class",
"sh:Shape"
],
"hydra:description": "Un paciente",
"hydra:supportedOperation": [
{
"@type": "hydra:Operation",
"hydra:method": "GET",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Paciente"
}
},
{
"@type": "hydra:Operation",
"hydra:method": "DELETE"
}
],
"hydra:supportedProperty": [
{
"@id": "http://www.personas-mexico.org/persona#info-basica-link",
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#info-basica",
"@type": "hydra:Link",
"rdfs:comment": "Información básica de una persona",
"rdfs:label": "info-basica",
"rdfs:range": {
"@id": "http://www.personas-mexico.org/persona#Persona"
},
"hydra:supportedOperation": [
{
"@type": "hydra:Operation",
"hydra:method": "GET",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Persona"
}
},
{
"@type": "hydra:Operation",
"hydra:expects": {
"@id": "http://www.personas-mexico.org/persona#Persona"
},
"hydra:method": "POST",
"hydra:returns": {
"@id": "http://www.personas-mexico.org/persona#Persona"
}
}
]
},
"hydra:readonly": true
},
{
"@type": "hydra:SupportedProperty",
"hydra:property": {
"@id": "http://www.personas-mexico.org/persona#tieneFechaDeNacimiento",
"@type": "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
"rdfs:comment": "La fecha de nacimiento de la persona",
"rdfs:label": "tieneFechaDeNacimiento",
"rdfs:range": {
"@id": "xsd:date-time"
},
"hydra:supportedOperation": []
},
"hydra:required": true
}
],
"hydra:title": "Paciente"
}
],
"lvz:entrypointClass": "http://localhost:8080/api/vocab#EntryPoint",
"@context": {
"@vocab": "http://localhost:8080/api/vocab#",
"@base": "http://localhost:8080/",
"hydra": "http://www.w3.org/ns/hydra/core#",
"rdfs": "http://www.w3.org/2000/01/rdf-schema#",
"xsd": "http://www.w3.org/2001/XMLSchema#",
"sh": "http://www.w3.org/ns/shacl#"
}
}
From what I see the error is generated because the ApiDocumentation
object obtained does not set the entryPoint
property. This is the output of the object:
I don't know if this is a bug or maybe I'm missing something.
Hi everybody,
Some months ago I published a Hydra client library we use for API Platform's client-side tools: https://github.com/dunglas/api-doc-parser
It looks very similar to what you are doing (but uses ES6 + Flow instead of TypeScript). It is already used by two of our tools:
I looked quickly to your implementation, and it looks really similar to ours. The main difference is that we designed our library with an abstraction layer to be able to consume other formats like Swagger or JSONAPI (but it's not done yet).
Can we find a way to collaborate? It's useless to duplicate the effort.
When JsonLdHypermediaProcessor
flattens the payload it should probably always use context provided to the client by the developer using the client and never the one from the payload returned from the server.
Developer using a client has to have full control over JSON-LD context used to flatten paylad to use aliases from that context in the code. Context in the payload (response from the server) should stay considered as not under control of the developer using client and the code should not make any assumptions about that context and only rely on context directly provided to the client by the developer.
Possibly related to what @alien-mcl mentioned in #11 (comment)
Ahh. It was some time ago, but I think this was to force either framing or flattening to enforce those urls. For some reason these were incorrectly processed.
Currently client returns a received resource from a client.getResource(url)
call.
While the hypermedia
property provides all the hydra related details that were discovered, current approach hides all the physical response parameters, especially headers (including content type).
Consider adding those details to either the hypermedia
container or some other property designated.
Currently Heracles extracts things like operations into a hypermedia
property. The proposal is to either use hydra
instead or directly use Hydra terms like member
.
The reason I don't like hypermedia is because it is the combination of the controls and the information etc. This comment from Roy T. Fielding probably describes best what I want to say: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven#comment-718
When I say hypertext, I mean the simultaneous presentation of information and controls such that the information becomes the affordance through which the user (or automaton) obtains choices and selects actions.
What we try to do here is to extract the controls out of the hypertext/hypermedia.
I thought about controls
too but things like controls.member
don't seem to make too much sense. Since this is the Hydra reference client, I wouldn't be too worried about using the term hydra for this.
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
This repository currently has no open or pending branches.
package.json
@rdfjs/parser-n3 ^2.0.0
@rdfjs/serializer-jsonld ^2.0.0
isomorphic-fetch 3.0.0
jsonld ^8.0.0
parse-link-header 2.0.0
uri-templates 0.2.0
urijs 1.19.11
@types/body-parser 1.19.5
@types/express 4.17.21
@types/jasmine 5.1.4
@types/js-md5 0.7.2
@types/node 20.12.11
@types/parse-link-header 2.0.3
@types/sinon 17.0.3
@types/uri-templates 0.1.34
babel-core 6.26.3
babel-loader 9.1.3
babel-preset-env 1.7.0
babel-preset-es2017 6.24.1
babel-preset-stage-0 6.24.1
body-parser 1.20.2
express 4.19.2
istanbul-instrumenter-loader 3.0.1
jasmine 5.1.0
jasmine-sinon 0.4.0
js-md5 0.8.3
karma 6.4.3
karma-chrome-launcher 3.2.0
karma-coverage-istanbul-reporter 3.0.3
karma-coveralls 2.1.0
karma-jasmine 5.1.0
karma-jasmine-sinon 1.0.4
karma-sinon 1.0.5
karma-source-map-support 1.4.0
karma-sourcemap-loader 0.4.0
karma-summary-reporter 4.0.1
karma-webpack 5.0.1
prettier 3.2.5
raw-loader 4.0.2
sinon 17.0.2
source-map-loader 5.0.0
ts-loader 9.5.1
ts-node 10.9.2
tslint 6.1.3
tslint-eslint-rules 5.4.0
tslint-language-service 0.9.9
typedoc 0.25.13
typedoc-plugin-markdown 4.0.1
typescript 5.4.5
webpack 5.91.0
webpack-karma-jasmine 4.0.0
src/N3/package.json
@rdfjs/parser-n3 ^2.0.0
@rdfjs/serializer-jsonld ^2.0.0
@hydra-cg/heracles.ts 0.5.1
.travis.yml
node 10
node 9
node 8
node 7
Currently heracles.ts uses memberTemplate for collection's templated operations. This predicate never reached a spec (still at PR stage) and I think it will never do so.
Consider removing it in favour of templated link's supported operations.
There is an option of having a lazy payload processing implementation that would carry a response object received from the 'fetch' API and use the client's facilities to process that response on demand once proper properties or methods are invoked.
This may delay or in some cases prevent unnecessary processing.
I looked at the tests and all the IDs there are relative to the server, e.g. "@id": "/api/people"
, However, my server returns json-ld with a domain name "@id": "http://mydomain.com/api/people",
What's the correct way to use heracles on a localhost?
Currently I'm rewriting the getResource-Function to point any requests to the test-server running on localhost. The entrypoint's hypermedia gets a mixture of both: "iri"-property is from localhost, "@id" is "mydomain". Here's my HydraClient-initialization:
private static initHydra(): HydraClientAPI { let rv: HydraClientAPI = new HydraClientAPI(); let factory: HydraClientFactory = new HydraClientFactory(); rv.hc = factory.withDefaults().andCreate() as HydraClient; rv.hc.getResource = async (urlOrResource) => { //START getUrl let url: any = urlOrResource; if (typeof url === "object") { url = !!url.target ? url.target.iri : url.iri; } if (!!!url) { throw new Error(HydraClient.noUrlProvided); } //END getUrl serverURLMap.forEach((val, key) => { if ((url as string).startsWith(key)) { url = (url as string).replace(key, val); } }); const response = await fetch(url); if (response.status !== 200) { throw new Error(HydraClient.invalidResponse + response.status); } const hypermediaProcessor = rv.hc.getHypermediaProcessor(response); if (!hypermediaProcessor) { throw new Error(HydraClient.responseFormatNotSupported); } const result = await hypermediaProcessor.process(response, rv.hc); Object.defineProperty(result, "iri", { value: response.url }); return result; }; rv.setHydraApiDoc(hydraApiDocURL); return rv; }
Currently the client cannot process correctly multiple API documentations (i.e. linked by the LINK header). This should be corrected so multiple API documentations can be exposed in the client's API.
I wanted to find out about the status of Heracles.ts relative to the spec, especially with regards to releases on NPM. Is there a particular release that ties to the current draft?
Current implementation adds as links only those predicates that are either known to be hydra:Link
or hydra:TemplatedLink
or are explicitely defined as so within the obtained graph. This seems to be to strict for most of the APIs, thus some configurable policy would come in handy.
This was raised in PR #11:
expect(this.result.hypermedia).toEqual([
{
iri: "http://temp.uri/api/events",
isA: [hydra.Collection],
totalItems: 1,
members: [
{
iri: "http://temp.uri/api/events/1",
isA: [],
"http://schema.org/endDate": "2017-04-19",
"http://schema.org/eventDescription": "Some event 1",
"http://schema.org/eventName": "Event 1",
"http://schema.org/startDate": "2017-04-19"
}
]
}, {
iri: "http://temp.uri/api/events/1",
isA: [],
"http://schema.org/endDate": "2017-04-19",
"http://schema.org/eventDescription": "Some event 1",
"http://schema.org/eventName": "Event 1",
"http://schema.org/startDate": "2017-04-19"
}, {
iri: 'some:named.graph',
isA: []
}
]);
});
I made the following comment:
I think this shows the leaky nature of this interface quite clearly. It returns a more or less random set of data. I'd expect to be able to get a list of links to related resources and a list of operations that I then can invoke.
Something like
links = this.result.getLinks(); --> returns an array of Link instances which have the property, the target (and perhaps a few additional properties describing the target) links[0].getProperty() --> examplevocab:events links[0].getTarget() --> /api/events ...
This link could then simply be passed to the client
this.client.getResource(links[0])
or, perhaps more intuitive,links[0].retrieveTarget(this.client)
Current implementation allows to gather collection's members, but whether it is a complete one, or just a partial view, client will discover afterwards. It could be useful to have a mechanism that will fetch all collection's members regardless underlying implementation details (full or parial view).
Both hydra:Class
and hydra:ApiDocumentation
use:
/**
* Gets a title of this API documentation.
* @readonly
* @returns {string}
*/
readonly displayName?: string;
However, the spec uses hydra:title
. Is this difference meaningful? If not, I think it should probably be renamed in the next breaking version because it could cause some confusion for new users.
Also: maybe the Typescript documentation should link to the relevant properties in the Hydra spec.
Heracles sometimes mints blank node IDs without avoiding collisions with already existing blank node IDs. We either need to do that or stop minting such identifiers as they shouldn't be necessary in the first place.
Hi everyone,
in a personal project I'm using Heracles, and it runs into an issue with TypesCollection.
The issue comes from directly extending Array, so I've done some research on the matter:
Hope you all had a good start into 2018!
I renamed the old Heracles and I can transfer ownership on the NPM package.
What do you guys think?
Currently it uses JSON-LD framing to get to a specific JSON-LD representation.
... or at least reconfigure it.
I try to embrace my own code reformatted by this component and I find it difficult.
My IDE's defaults shows a break line at 120 chars long line, but prettier mostly uses half of it.
I know that someone may need to print it with a legacy 80 chars long needle printer on a toilette paper or just switched from COBOL console, but these setting makes my Full HD 31 inch monitor pretty useless.
I don't know what readability preferences were used by creators, but when I try to write my code to be fluent-like where each line tries to be a sentence, it's a mess at the end.
It's my personal opinion, but please try to write/read some code reformatted with these settings before using it. For me, it's pain in the ... you know where.
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.