-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #36 from zazuko/kopflos
Kopflos
- Loading branch information
Showing
29 changed files
with
1,498 additions
and
5 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--- | ||
sidebar_position: 1 | ||
--- | ||
|
||
# Data-Centric APIs | ||
|
||
Data-centric APIs are designed to provide a structured and standardized way to access and | ||
manipulate data. Unlike traditional APIs that focus on specific functionalities or services, | ||
data-centric APIs emphasize the data itself, making it easier to integrate, query, and manage data | ||
across different systems. These APIs often leverage semantic web technologies, such as RDF and SPARQL, | ||
to enable rich data interactions and ensure interoperability between diverse data sources. | ||
|
||
## Kopflos | ||
|
||
[Kopflos](./kopflos) is Zazuko's data-centric API platform that provides a flexible solution for managing and | ||
exposing data as APIs. The name, "Kopflos," is a German word that means "headless," reflecting the | ||
platform's focus on data rather than presentation. However, Kopflos is flexible enough to accommodate | ||
various data models and use cases, making it suitable for a wide range of applications, full-featured | ||
websites using the latest Web technologies. |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
label: Kopflos | ||
position: 2 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
label: Explanations | ||
position: 3 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
--- | ||
title: Request pipeline | ||
sidebar_position: 1 | ||
--- | ||
|
||
# Kopflos request pipeline | ||
|
||
``` | ||
Incoming Request | ||
│ | ||
└─▶ Kopflos handler | ||
│ | ||
4**/5** ◀─┴─▶ Resource Shape Lookup | ||
│ | ||
4**/5** ◀─┴─▶ Resource Loader Lookup | ||
│ | ||
4**/5** ◀─┴─▶ Load Resource | ||
│ | ||
4** ◀─┴─▶ Authorization | ||
│ | ||
400 ◀─┴─▶ Validation | ||
│ | ||
4**/5** ◀─┴─▶ (User handler) | ||
│ | ||
└─▶ Reply | ||
``` | ||
|
||
## Incoming request | ||
|
||
Incoming request is handled by the server library, such as express or fastify and then forwarded to Kopflos. | ||
|
||
## Kopflos handler | ||
|
||
Kopflos handler is the main entry point for all incoming requests. It is responsible for orchestrating the request pipeline. | ||
|
||
## Resource Shape Lookup | ||
|
||
Resource Shape Lookup executes SPARQL against the `default` query endpoint to find the shape targeting the requested resource. | ||
|
||
:::tip | ||
See also: [How to Select which resources should be served by the API](../how-to/resource-shape.md) | ||
::: | ||
|
||
## Resource Loader Lookup + Load Resource | ||
|
||
When the Resource Shape is found, a resource loader is selected based from `kopflos:resourceLoader` property, going bottom-up from the Resource/Property Shape to the share `kopflos:Config` resource. | ||
|
||
It is used to load the requested resource's Core Representation. | ||
|
||
:::info | ||
The Core Representation are the triples returned by the resource loader. Typically, that would be the result of a SPARQL `DESCRIBE` query or contents of resource's "own graph". | ||
::: | ||
|
||
:::warning | ||
By default, a loader which returns the resource's own graph is used. | ||
::: | ||
|
||
## Authorization | ||
|
||
Not implemented yet. | ||
|
||
## Validation | ||
|
||
Not implemented yet. | ||
|
||
## User handler | ||
|
||
Finally, the handler is executed. If no handler is defined, and the request method is a GET, the resource's Core Representation is returned. | ||
|
||
The result of the handler is forwarded back to the server library to be sent as a response. |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
label: How-To | ||
position: 2 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# Use express middleware | ||
|
||
The package `@kopflos-cms/express` allows you to use existing express middleware in your Kopflos project. | ||
To do that, add a plugin `@kopflos-cms/express/middleware` to your `kopflos.config.js` file with `before` | ||
and `after` keys. | ||
|
||
The `after` middleware are only executed when kopflos handler returns a `404` status code or throws an error. | ||
|
||
## Middleware from NPM packages | ||
|
||
In your `kopflos.config.js` file, you can add references to other modules, such as `cors` or `compression`. | ||
|
||
The requirements are that every such module default-exports a function which returns the actual middleware. | ||
|
||
To provide an additional config, add a two element array instead of just the module name, as shown below | ||
on the example of `compression`. | ||
|
||
```javascript | ||
export default { | ||
// ...other settings | ||
plugins: { | ||
'@kopflos-cms/express/middleware': { | ||
before: [ | ||
'cors', | ||
['compression', { level: 9 }], | ||
], | ||
}, | ||
}, | ||
} | ||
``` | ||
|
||
You can also reference named exports by adding a `#name` after the modue name. For example, | ||
to use [express-rate-limit](https://www.npmjs.com/package/express-rate-limit), you can do the following: | ||
|
||
```javascript | ||
export default { | ||
// ...other settings | ||
plugins: { | ||
'@kopflos-cms/express/middleware': { | ||
before: [ | ||
['express-rate-limit#rateLimit', { | ||
windowMs: 15 * 60 * 1000, | ||
limit: 100, | ||
}], | ||
], | ||
}, | ||
}, | ||
} | ||
``` | ||
|
||
## Your own middleware or NPM packages which do not export a function | ||
|
||
If you want to include your own middleware or a package which does not export a function, you can do so by | ||
exporting a function from your own module. | ||
|
||
```js | ||
// ./lib/my-middleware.js | ||
export default function myMiddleware() { | ||
return (req, res, next) => { | ||
// your middleware code here | ||
next(); | ||
}; | ||
} | ||
``` | ||
|
||
Then, you can reference it in your `kopflos.config.js` file by the absolute path: | ||
|
||
```javascript | ||
import * as url from 'node:url' | ||
|
||
export default { | ||
// ...other settings | ||
plugins: { | ||
'@kopflos-cms/express/middleware': { | ||
before: [ | ||
url.fileURLToPath(new URL('./lib/my-middleware.js', import.meta.url)), | ||
], | ||
}, | ||
}, | ||
} | ||
``` | ||
:::warning | ||
The path must be absolute, thus the usage of `import.meta.url` to resolve the path relative to the | ||
config file itself. For that reason, this method will only work with configuration files written as | ||
code. (for commonjs, you can use `__dirname` instead) | ||
::: |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
# Serve RDF in data-bound HTML | ||
|
||
This guide will show you how to serve HTML templates bound to RDF data. | ||
|
||
:::warning | ||
These features are a work in progress. | ||
::: | ||
|
||
## Prerequisites | ||
|
||
In an existing kopflos application, install the following dependencies: | ||
|
||
```sh | ||
npm install @kopflos-labs/html-template @kopflos-labs/handlebars | ||
``` | ||
|
||
## Create a template | ||
|
||
A template is a combination of HTML `<template>` elements and Handlebars expressions. It is | ||
important, | ||
however, that the HTML is only processed on the server and not in the browser. This is where the | ||
handlebars package comes in, providing a familiar syntax to fill in the templates with your data. | ||
|
||
First, create a new HTML file, for example `/templates/page.html` which will be used to serve a page | ||
with a title and a body simple. We will be serving instances of the `schema:Person` class. | ||
|
||
```html | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<template target-class="schema:Person"> | ||
<template property="schema:name"> | ||
<title>{{ pointer.value }}</title> | ||
</template> | ||
</template> | ||
</head> | ||
<body> | ||
<template target-class="schema:Person"> | ||
<h1>{{ valueof schema:name }}</h1> | ||
|
||
<template property="schema:address"> | ||
<dl> | ||
<dt>Street</dt> | ||
<dd>{{ valueof schema:streetAddress }}</dd> | ||
<dt>City</dt> | ||
<dd>{{ valueof schema:addressLocality }}</dd> | ||
<dt>Postal Code</dt> | ||
<dd>{{ valueof schema:postalCode }}</dd> | ||
</dl> | ||
</template> | ||
</template> | ||
</body> | ||
</html> | ||
``` | ||
|
||
The template elements are used to find the correct data to fill in the placeholders. | ||
|
||
Similarly to [SHACL's `sh:targetClass`](https://www.w3.org/TR/shacl/#targetClass), the | ||
`target-class` | ||
attribute is used to find the root node in the template data. It is required on the top level | ||
template | ||
element. | ||
|
||
Templates with a `property` attribute are used to navigate the data graph, just like | ||
[Property Shapes](https://www.w3.org/TR/shacl/#property-shapes). | ||
|
||
Inside any template, you can use template binding expressions to fill in the data. | ||
|
||
:::tip | ||
Here we use handlebars, but it is possible to replace it with other templating languages. | ||
::: | ||
|
||
The current node in the data graph is available as `pointer`. The `valueof` helper is used to output | ||
value of a property, thus being an alternative to nested templates with `property` attributes. | ||
For example, since complex properties are supported, | ||
|
||
```html | ||
<img src="{{ valueof schema:image/schema:tumbnail/schema:contentUrl }}"> | ||
``` | ||
|
||
is a terser equivalent to: | ||
|
||
```html | ||
|
||
<template property="schema:image"> | ||
<template property="schema:thumbnail"> | ||
<template property="schema:contentUrl"> | ||
<img src="{{ pointer.value }}"> | ||
</template> | ||
</template> | ||
</template> | ||
``` | ||
|
||
## Create a page resource shape | ||
|
||
To serve the template, we need to create [Resource Shape](./resource-shape.md) which will | ||
act as a dynamic resource, meaning that it will serve resources which do no actually exist in the | ||
store. Instead, other resource data will be fetched and the page itself is only a URL pattern. In | ||
other words, a single resource shape will match and serve multiple resources. | ||
|
||
```turtle | ||
PREFIX sh: <http://www.w3.org/ns/shacl#> | ||
PREFIX kl: <https://kopflos.described.at/> | ||
PREFIX code: <https://code.described.at/> | ||
<person-page> | ||
a kl:ResourceShape ; | ||
kl:api <> ; | ||
sh:target | ||
[ | ||
a kl:PatternedTarget ; | ||
kl:regex "/page/person/(?<id>\\w+)$" ; | ||
] ; | ||
kl:handler | ||
[ | ||
a kl:Handler ; | ||
kl:method "GET" ; | ||
code:implementedBy | ||
( | ||
[ | ||
a code:EcmaScriptModule ; | ||
code:link <node:@kopflos-cms/serve-file#default> ; | ||
code:arguments ( "templates/person.html" ) ; | ||
] | ||
[ | ||
a code:EcmaScriptModule ; | ||
code:link <node:@kopflos-labs/html-template#default> ; | ||
code:arguments | ||
( | ||
[ | ||
a code:EcmaScriptModule ; | ||
code:link <node:@kopflos-labs/handlebars#default> ; | ||
] | ||
[ | ||
a code:EcmaScriptModule ; | ||
code:link <file:lib/templateData.js#describe> ; | ||
] | ||
"/person/${id}"^^code:EcmaScriptTemplateLiteral | ||
) ; | ||
] | ||
) | ||
] ; | ||
. | ||
``` | ||
|
||
The patterned target is used to match the URL pattern similar to `/page/person/:id` where `:id` is | ||
stored as a [request subject variable](../reference/request-handlers#subject-variables). | ||
|
||
The handler is a sequence of modules which will load the template file using `@kopflos-cms/serve-file` | ||
and the process the templates using `@kopflos-labs/html-template` and `@kopflos-labs/handlebars`. | ||
|
||
## Create a data source | ||
|
||
Finally, you need to create the `lib/templateData.js#describe` module which will provide the data | ||
for the template: | ||
|
||
```ts | ||
import type { TemplateDataFunc } from '@kopflos-labs/html-template' | ||
|
||
export const describe = (resourcePath: string): TemplateDataFunc => ({ env }) => { | ||
return env.sparql.default.stream.query.construct(` | ||
BASE <${env.kopflos.config.baseIri}> | ||
DESCRIBE <${resourcePath}>`) | ||
} | ||
``` | ||
|
||
The implementation is a minimal query to describe the person resource, whose identifier is taken from | ||
the resource pattern. Notice how it's declaratively set in the handler as a template literal and | ||
forwarded to the `describe` function. |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Load API description | ||
|
||
A Kopflos API is composed of triples that describe the API's structure and behavior. These triples need to be loaded into the API's store before the API can be used. | ||
|
||
## Load from named graphs | ||
|
||
The simplest and recommended way to load the API is to use named graphs. A single API can be loaded from multiple named graphs, and there can be multiple APIs served from a single Kopflos instance. Use the code below to load the API from named graphs: | ||
|
||
```js | ||
import Kopflos from '@kopflos-cms/core' | ||
|
||
let config | ||
const api = new Kopflos(config) | ||
await Kopflos.fromGraphs(api, 'http://example.com/api1', 'http://example.com/api2', 'http://example.com/shared') | ||
``` | ||
|
||
:::tip | ||
[RDF/JS NamedNode](https://rdf.js.org/data-model-spec/#namednode-interface) objects can be used as well. | ||
::: | ||
|
||
The given named graphs will be loaded from the default SPARQL endpoint. | ||
|
||
## Initialise with preloaded data | ||
|
||
If you have the API triples in memory, you can initialise the API with it directly: | ||
|
||
```typescript | ||
import Kopflos from '@kopflos-cms/core' | ||
|
||
let config | ||
let dataset: DatasetCore | ||
|
||
const api = new Kopflos(config, { dataset }) | ||
``` |
Oops, something went wrong.