diff --git a/docs/apis/kopflos/how-to/resource-loaders.md b/docs/apis/kopflos/how-to/resource-loaders.md new file mode 100644 index 0000000..41be747 --- /dev/null +++ b/docs/apis/kopflos/how-to/resource-loaders.md @@ -0,0 +1,139 @@ +# Configure resource loaders + +As explained on the [Request pipeline page](../explanations/request-pipeline.md), early in the request pipeline, Kopflos selects a resource loader to load the requested resource's Core Representation. + +Resource Loaders can be declared on instances of `kopflos:ResourceShape`, `kopflos:Api`, and `kopflos:Config` resources. + +## Predefined loaders + +Kopflos recognizes two loaders out of the box. They are identified by their shorthand identifiers. + +### Own graph loader + +The "Own Graph" loader is the default. +It loads the requested resource from the same graph where the request was made. +For example, +a request to `https://example.org/person/1` will load the entire named graph `https://example.org/person/1` as Core Representation. + +```turtle +PREFIX kopflos: + +<> + # a kopflos:ResourceShape ; # or + # a kopflos:Api ; # or + # a kopflos:Config ; + kopflos:resourceLoader kopflos:OwnGraphLoader ; +. +``` + +### `DESCRIBE` loader + +The `DESCRIBE` loader is a built-in loader +that uses the SPARQL `DESCRIBE` query to load the requested resource. +The loader will issue a `DESCRIBE` query to the default SPARQL endpoint. + +```turtle +PREFIX kopflos: + +<> + # a kopflos:ResourceShape ; # or + # a kopflos:Api ; # or + # a kopflos:Config ; + kopflos:resourceLoader kopflos:DescribeLoader ; +. +``` + +## Loader precedence + +Kopflos selects a loader based on the `kopflos:resourceLoader` property, +going bottom-up from the Resource Shape to the shared `kopflos:Config` resource. + +```turtle +PREFIX sh: +PREFIX kopflos: +PREFIX ex: + +ex:PersonShape + a kopflos:ResourceShape ; + kopflos:api ex:PeopleApi ; + sh:targetNode ex:Person ; + kopflos:resourceLoader ex:ResourceLoader1 ; +. + +ex:AddressShape + a kopflos:ResourceShape ; + kopflos:api ex:PeopleApi ; + sh:targetNode ex:Person ; +. + +ex:PeopleApi + a kopflos:Api ; + kopflos:config ex:Config ; + kopflos:resourceLoader ex:ResourceLoader2 ; +. + +ex:ArticleShape + kopflos:api ex:PublishingApi ; + sh:targetNode ex:Article ; +. + +ex:PublishingApi + a kopflos:Api ; + kopflos:config ex:Config ; +. + +ex:Config + a kopflos:Config ; + kopflos:resourceLoader ex:ResourceLoader3 ; +. +``` + +The algorithm is straightforward. For any given matched resource shape, Kopflos will: +1. Check if the resource shape has a `kopflos:resourceLoader` property. If it does, Kopflos will use the loader specified in the property. +2. If the resource shape does not have a `kopflos:resourceLoader` property, Kopflos will look for the `kopflos:resourceLoader` property in the resource's API resource. +3. If the API resource does not have a `kopflos:resourceLoader` property, Kopflos will look for the `kopflos:resourceLoader` property in the linked `kopflos:Config` resource. + +In the example above: +- If a requested resource matches the `ex:PersonShape`, Kopflos will use `ex:ResourceLoader1` from the resource shape itself. +- If a requested resource matches the `ex:AddressShape`, Kopflos will use `ex:ResourceLoader2` from the API resource. +- If a requested resource matches the `ex:ArticleShape`, Kopflos will use `ex:ResourceLoader3` from the Config resource. + +## Implementing and using custom Resource Loaders + +A loader is a function that takes the requested URI +(in the form of a [NamedNode](https://rdf.js.org/data-model-spec/#namednode-interface)) +and returns a Core Representation as an [RDF/JS Stream](https://rdf.js.org/stream-spec/#stream-interface). +It also takes a second argument: the instance of Kopflos that is processing the request. + +For example, here is a loader which sends a request query to a Stardog database, +with a query hint to ensure that the Concise Bounded Description (CBD) is returned. + +```ts +// src/resource-loaders/stardog-loaders.ts +import type { ResourceLoader } from '@kopflos-cms/core' + +export const CBD: ResourceLoader = async (uri, kopflos) => { + const query = ` +#pragma describe.strategy cbd +DESCRIBE <${uri.value}>` + + return kopflos.env.sparql.default.stream.query.construct(query) +} +``` + +To use it as the default in an API, declare the loader in the API's configuration: + +```turtle +PREFIX code: +PREFIX kopflos: +PREFIX ex: + +ex:Config + a kopflos:Config ; + kopflos:resourceLoader + [ + a code:EcmaScriptModule ; + code:link ; + ] ; +. +```