Skip to content

Commit

Permalink
refactor: no env dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
tpluscode committed Mar 30, 2024
1 parent a3b0e26 commit 80d30db
Show file tree
Hide file tree
Showing 19 changed files with 233 additions and 128 deletions.
5 changes: 5 additions & 0 deletions .changeset/gorgeous-mirrors-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kopflos-cms/core": patch
---

`Api` exported also as interface
5 changes: 5 additions & 0 deletions .changeset/new-poems-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kopflos-cms/core": minor
---

RDF/JS Environment must be passed explicitly when initialising an API
13 changes: 7 additions & 6 deletions Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import EcmaScriptLoader from 'rdf-loader-code/ecmaScript.js'
import LoaderRegistryImpl, { LoaderRegistry } from 'rdf-loaders-registry'
import EcmaScriptModuleLoader from 'rdf-loader-code/ecmaScriptModule.js'
import EcmaScriptLiteralLoader from 'rdf-loader-code/ecmaScriptLiteral.js'
import type { NamedNode, Quad_Graph } from '@rdfjs/types'
import type { DatasetExt } from '@zazuko/env'
import type { NamedNode, Quad_Graph, DatasetCore } from '@rdfjs/types'
import addAll from 'rdf-dataset-ext/addAll.js'
import fromStream from 'rdf-dataset-ext/fromStream.js'
import { replaceDatasetIRI } from './lib/replaceIRI.js'
import Factory from './lib/factory.js'

interface ApiInit<D extends DatasetExt = DatasetExt> {
interface ApiInit<D extends DatasetCore = DatasetCore> {
term?: NamedNode
dataset?: D
graph?: NamedNode
Expand All @@ -17,7 +18,7 @@ interface ApiInit<D extends DatasetExt = DatasetExt> {
factory: Factory<D>
}

export interface Api<D extends DatasetExt = DatasetExt> {
export interface Api<D extends DatasetCore = DatasetCore> {
env: Factory<D>
initialized: boolean
path: string
Expand All @@ -29,7 +30,7 @@ export interface Api<D extends DatasetExt = DatasetExt> {
init(): Promise<void>
}

export default class Impl<D extends DatasetExt = DatasetExt> implements Api<D> {
export default class Impl<D extends DatasetCore = DatasetCore> implements Api<D> {
initialized: boolean
path: string
codePath: string
Expand Down Expand Up @@ -75,7 +76,7 @@ export default class Impl<D extends DatasetExt = DatasetExt> implements Api<D> {

fromFile(filePath: string) {
this.tasks.push(async () => {
this.env.dataset().addAll.call(this.dataset, await this.env.dataset().import(this.env.fromFile(filePath)))
addAll(this.dataset, await fromStream(this.env.dataset(), this.env.fromFile(filePath)))
})

return this
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,27 @@ const store = new FlatMultiFileStore({
```

An `Api` object contains the dataset of the API documentation, where to find it and where to find the code.
The static method `.fromFile` reads the dataset from the given file and creates an `Api` object with the given options.
The static method `.fromFile` reads the dataset from the given file and creates an `Api` object with the given options.

```javascript
import rdf from '@zazuko/env-node'

const api = await Api.fromFile('api.ttl', {
factory: rdf,
path: '/api',
codePath: __dirname
})
```

The `factory` parameter is required, and must be an [RDF/JS Environment](https://npm.im/@rdfjs/environment), providing the following factories compatible with the following:

- [DatasetCoreFactory](https://npm.im/@rdfjs/dataset)
- [DataFactory](https://npm.im/@rdfjs/data-model)
- [TermSetFactory](https://npm.im/@rdfjs/term-set)
- [ClownfaceFactory](https://npm.im/clownface)
- [NsBuildersFactory](https://npm.im/@tpluscode/rdf-ns-builders)
- [FsUtilsFactory](https://npm.im/@rdfjs/@zazuko/rdf-utils-fs)

Once both objects are created, the middleware can be used:

```javascript
Expand All @@ -45,7 +57,7 @@ app.listen(9000)
The operations must implement a [Express routing handler](http://expressjs.com/en/starter/basic-routing.html) interface (`(req, res, next) => {}`).
@kopflos-cms/core adds the [@rdfjs/express-handler](https://github.com/rdfjs-base/express-handler) to handle incoming and outgoing RDF data.
For `GET` requests with a matching IRI Template, the `.dataset()` and `.quadStream()` as defined by `express-handler` are also available to read the given variables.
Additionally there is a `hydra` property assigned to `req` that contains more data about the request:
Additionally, there is a `hydra` property assigned to `req` that contains more data about the request:

```javascript
req.hydra = {
Expand Down
22 changes: 12 additions & 10 deletions StoreResourceLoader.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import type { NamedNode, Store } from '@rdfjs/types'
import type { DatasetCore, NamedNode, Store } from '@rdfjs/types'
import { isNamedNode } from 'is-graph-pointer'
import fromStream from 'rdf-dataset-ext/fromStream.js'
import toStream from 'rdf-dataset-ext/toStream.js'
import Factory from './lib/factory.js'
import { PropertyResource, Resource, ResourceLoader } from './index.js'

export default class StoreResourceLoader implements ResourceLoader {
export default class StoreResourceLoader<D extends DatasetCore> implements ResourceLoader<D> {
readonly store: Store
private env: Factory
private env: Factory<D>

constructor({ store, env }: { store: Store; env: Factory }) {
constructor({ store, env }: { store: Store; env: Factory<D> }) {
this.store = store
this.env = env
}

async load(term: NamedNode): Promise<Resource | null> {
const dataset = await this.env.dataset().import(this.store.match(null, null, null, term))
async load(term: NamedNode): Promise<Resource<D> | null> {
const dataset = await fromStream(this.env.dataset(), this.store.match(null, null, null, term))

if (dataset.size === 0) {
return null
Expand All @@ -30,7 +32,7 @@ export default class StoreResourceLoader implements ResourceLoader {
return dataset
},
quadStream() {
return dataset.toStream()
return toStream(dataset)
},
types: this.env.termSet(types.terms),
}
Expand All @@ -42,9 +44,9 @@ export default class StoreResourceLoader implements ResourceLoader {
return resource ? [resource] : []
}

async forPropertyOperation(term: NamedNode): Promise<PropertyResource[]> {
const dataset = await this.env.dataset().import(this.store.match(null, null, term, null))
const result: PropertyResource[] = []
async forPropertyOperation(term: NamedNode): Promise<PropertyResource<D>[]> {
const dataset = await fromStream(this.env.dataset(), this.store.match(null, null, term, null))
const result: PropertyResource<D>[] = []

for (const quad of dataset) {
if (quad.subject.termType !== 'NamedNode') continue
Expand Down
2 changes: 2 additions & 0 deletions examples/blog/server.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from 'express'
import FlatMultiFileStore from 'rdf-store-fs/FlatMultiFileStore.js'
import rdf from '@zazuko/env-node'
import hydraBox from '../../middleware.js'
import Api from '../../Api.js'
import ResourceStore from './lib/ResourceStore.js'
Expand All @@ -13,6 +14,7 @@ async function main() {
})

const api = await Api.fromFile('api.ttl', {
factory: rdf,
path: '/api',
codePath: __dirname,
})
Expand Down
36 changes: 16 additions & 20 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,39 @@ import type { Readable } from 'stream'
import type * as RDF from '@rdfjs/types'
import type { GraphPointer } from 'clownface'
import type { Request } from 'express'
import type { Dataset } from '@zazuko/env/lib/Dataset.js'
import middleware from './middleware.js'
import Api from './Api.js'
import type { Api } from './Api.js'

export interface Resource {
export { default as middleware } from './middleware.js'
export { default as Api } from './Api.js'

export interface Resource<D extends RDF.DatasetCore = RDF.DatasetCore> {
term: RDF.NamedNode
prefetchDataset: RDF.DatasetCore
dataset(): Promise<Dataset>
dataset(): Promise<D>
quadStream(): RDF.Stream & Readable
types: Set<RDF.NamedNode>
}

export interface PropertyResource extends Resource {
export interface PropertyResource<D extends RDF.DatasetCore = RDF.DatasetCore> extends Resource<D> {
property: RDF.Quad_Predicate
object: RDF.NamedNode
}

export interface PotentialOperation {
resource: Resource | PropertyResource
export interface PotentialOperation<D extends RDF.DatasetCore = RDF.DatasetCore> {
resource: Resource<D> | PropertyResource<D>
operation: GraphPointer
}

export interface HydraBox {
api: Api
export interface HydraBox<D extends RDF.DatasetCore = RDF.DatasetCore> {
api: Api<D>
term: RDF.NamedNode
store: RDF.Store
resource: Resource & { clownface(): Promise<GraphPointer<RDF.NamedNode, Dataset>> }
resource: Resource<D> & { clownface(): Promise<GraphPointer<RDF.NamedNode, D>> }
operation: GraphPointer
operations: PotentialOperation[]
}

export interface ResourceLoader {
forClassOperation(term: RDF.NamedNode, req: Request): Promise<Resource[]>
forPropertyOperation(term: RDF.NamedNode, req: Request): Promise<PropertyResource[]>
operations: PotentialOperation<D>[]
}

export default {
middleware,
Api,
export interface ResourceLoader<D extends RDF.DatasetCore = RDF.DatasetCore> {
forClassOperation(term: RDF.NamedNode, req: Request): Promise<Resource<D>[]>
forPropertyOperation(term: RDF.NamedNode, req: Request): Promise<PropertyResource<D>[]>
}
5 changes: 2 additions & 3 deletions lib/factory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import type { Environment } from '@rdfjs/environment/Environment.js'
import type{ DataFactory, DatasetCoreFactory, Quad } from '@rdfjs/types'
import type { DataFactory, DatasetCoreFactory, Quad, DatasetCore } from '@rdfjs/types'
import type { TermSetFactory } from '@rdfjs/term-set/Factory.js'
import type ClownfaceFactory from 'clownface/Factory.js'
import type NsBuildersFactory from '@tpluscode/rdf-ns-builders'
import type { DatasetExt } from '@zazuko/env'
import type FsUtilsFactory from '@zazuko/rdf-utils-fs/Factory.js'

type Factory<D extends DatasetExt = DatasetExt> = Environment<DatasetCoreFactory<Quad, Quad, D> | DataFactory | TermSetFactory | ClownfaceFactory | NsBuildersFactory | FsUtilsFactory>
type Factory<D extends DatasetCore = DatasetCore> = Environment<DatasetCoreFactory<Quad, Quad, D> | DataFactory | TermSetFactory | ClownfaceFactory | NsBuildersFactory | FsUtilsFactory>

export default Factory
3 changes: 2 additions & 1 deletion lib/middleware/iriTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import uriTemplateRoute from 'uri-template-route'
import { RequestHandler, Router } from 'express'
import type { GraphPointer } from 'clownface'
import type { NamedNode } from '@rdfjs/types'
import toStream from 'rdf-dataset-ext/toStream.js'
import log from '../log.js'
import { Api } from '../../Api.js'
import Factory from '../factory.js'
Expand Down Expand Up @@ -75,7 +76,7 @@ function middleware(pointer: GraphPointer, rdf: Factory): RequestHandler {
}

req.quadStream = () => {
return templateParams.dataset.toStream()
return toStream(templateParams.dataset)
}

next()
Expand Down
4 changes: 1 addition & 3 deletions lib/middleware/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { RequestHandler, Router, Response } from 'express'
import type { GraphPointer, MultiPointer } from 'clownface'
import createError from 'http-errors'
import { isGraphPointer } from 'is-graph-pointer'
import type { NamedNode } from '@rdfjs/types'
import type { Dataset } from '@zazuko/env/lib/Dataset.js'
import { code } from '../namespaces.js'
import log from '../log.js'
import { HydraBoxMiddleware } from '../../middleware.js'
Expand Down Expand Up @@ -111,7 +109,7 @@ const prepareOperation: RequestHandler = function (req, res, next) {

req.hydra.resource = {
...resource,
async clownface(): Promise<GraphPointer<NamedNode, Dataset>> {
async clownface() {
return req.hydra.api.env.clownface({
term: this.term,
dataset: await this.dataset(),
Expand Down
12 changes: 6 additions & 6 deletions middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { asyncMiddleware } from 'middleware-async'
import { defer } from 'promise-the-world'
import rdfHandler from '@rdfjs/express-handler'
import setLink from 'set-link'
import type { Store } from '@rdfjs/types'
import type { DatasetCore, Store } from '@rdfjs/types'
import apiHeader from './lib/middleware/apiHeader.js'
import iriTemplate from './lib/middleware/iriTemplate.js'
import operation from './lib/middleware/operation.js'
Expand All @@ -29,14 +29,14 @@ export interface HydraBoxMiddleware {
operations?: RequestHandler | RequestHandler[] | undefined
}

interface Options {
interface Options<D extends DatasetCore> {
baseIriFromRequest?: boolean
loader?: ResourceLoader
loader?: ResourceLoader<D>
store?: Store
middleware?: HydraBoxMiddleware
}

function middleware(api: Api, { baseIriFromRequest, loader, store, middleware = {} }: Options) {
function middleware<D extends DatasetCore>(api: Api<D>, { baseIriFromRequest, loader, store, middleware = {} }: Options<D>) {
const init = defer()
const router = Router()

Expand All @@ -51,7 +51,7 @@ function middleware(api: Api, { baseIriFromRequest, loader, store, middleware =

debug(`${req.method} to ${term.value}`)

req.hydra = <HydraBox>{
req.hydra = <HydraBox<D>>{
api,
store,
term,
Expand Down Expand Up @@ -92,7 +92,7 @@ function middleware(api: Api, { baseIriFromRequest, loader, store, middleware =
if (loader) {
router.use(resource({ loader }))
} else if (store) {
router.use(resource({ loader: new StoreResourceLoader({ store }) }))
router.use(resource({ loader: new StoreResourceLoader({ store, env: api.env }) }))
} else {
throw new Error('no loader or store provided')
}
Expand Down
Loading

0 comments on commit 80d30db

Please sign in to comment.