Skip to content

Commit

Permalink
Merge pull request #40 from zazuko/env-combine
Browse files Browse the repository at this point in the history
Improve Environment extensibility
  • Loading branch information
tpluscode authored Jan 18, 2024
2 parents 1a4249f + 7b1dda0 commit fde3143
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 97 deletions.
19 changes: 19 additions & 0 deletions .changeset/slow-emus-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@zazuko/env": major
"@zazuko/env-node": major
---

Change the method of creating derived environments:

1. The additional factories have been removed from the `create` function.
2. To create a derived environment, use the constructor overload which takes a parent environment as the second argument in addition to the array of additional factories.

For example,

```js
import env from '@zazuko/env';
import Environment from '@zazuko/env/Environment.js';
import MyFactory from './my-factory.js';

const myEnv = new Environment([MyFactory], { parent: env });
```
16 changes: 1 addition & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 5 additions & 26 deletions packages/env-node/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,15 @@
import formats from '@rdfjs/formats'
import type { FactoryConstructor } from '@rdfjs/environment/Environment.js'
import Environment from '@rdfjs/environment'
import DataFactory from '@rdfjs/data-model/Factory.js'
import NamespaceFactory from '@rdfjs/namespace/Factory.js'
import FormatsFactory from '@rdfjs/formats/Factory.js'
import TermMapFactory from '@rdfjs/term-map/Factory.js'
import TermSetFactory from '@rdfjs/term-set/Factory.js'
import NsBuildersFactory from '@tpluscode/rdf-ns-builders'
import ClownfaceFactory from 'clownface/Factory.js'
import TraverserFactory from '@rdfjs/traverser/Factory.js'
import DatasetFactory from '@zazuko/env/lib/DatasetFactory.js'
import baseEnv from '@zazuko/env'
import Environment from '@zazuko/env/Environment.js'
import FsUtilsFactory from '@zazuko/rdf-utils-fs/Factory.js'
import FetchFactory from '@rdfjs/fetch-lite/Factory.js'

export function create<F extends FactoryConstructor>(...additionalFactories: F[]) {
// TODO: improve types so that it's not necessary to duplicate this from `@zazuko/env`
export function create() {
const env = new Environment([
DataFactory,
DatasetFactory,
FormatsFactory,
NamespaceFactory,
NsBuildersFactory,
ClownfaceFactory,
TermMapFactory,
TermSetFactory,
TraverserFactory,
FsUtilsFactory,
FetchFactory,
...additionalFactories,
])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(<any>env).formats.import(formats)
], { parent: baseEnv })
env.formats.import(formats)
return env
}

Expand Down
14 changes: 3 additions & 11 deletions packages/env-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"type": "module",
"main": "index.js",
"scripts": {
"prepack": "tsc"
"prepack": "tsc",
"test": "mocha --package ../../package.json"
},
"repository": {
"url": "git://github.com/zazuko/env.git",
Expand All @@ -27,19 +28,10 @@
"author": "Zazuko GmbH",
"license": "MIT",
"dependencies": {
"@rdfjs/dataset": "^2.0.1",
"@rdfjs/data-model": "^2.0.1",
"@rdfjs/environment": "^1.0.0",
"@rdfjs/fetch-lite": "^3.2.2",
"@rdfjs/formats": "^4.0.0",
"@rdfjs/namespace": "^2.0.0",
"@rdfjs/term-map": "^2.0.0",
"@rdfjs/term-set": "^2.0.1",
"@rdfjs/traverser": "^0.1.2",
"@tpluscode/rdf-ns-builders": "^4.1.0",
"@zazuko/env": "^1.11.1",
"@zazuko/rdf-utils-fs": "^3.3.0",
"clownface": "^2.0.1"
"@zazuko/rdf-utils-fs": "^3.3.0"
},
"peerDependencies": {
"@types/rdfjs__fetch-lite": "^3.0.6"
Expand Down
27 changes: 27 additions & 0 deletions packages/env/Environment.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Environment, FactoryConstructor } from '@rdfjs/environment/Environment.js'
import { DerivedEnvironment } from './lib/extend.js'

type Narrow<T> =
| (T extends infer U ? U : never)
| Extract<T, number | string | boolean | bigint | symbol | null | undefined | []>
| ([T] extends [[]] ? [] : { [K in keyof T]: Narrow<T[K]> });

type ReturnFactory<C> = C extends FactoryConstructor<infer X> ? X : C;
type Distribute<U> = U extends any ? ReturnFactory<U> : never;

interface EnvironmentCtor {
new<F extends ReadonlyArray<FactoryConstructor<any>>>(
factories: Narrow<F>,
options?: { bind: boolean },
): Environment<Distribute<F[number]>>

new<F extends ReadonlyArray<FactoryConstructor<any>>, E extends Environment<any>>(
factories: Narrow<F>,
options: { parent?: E; bind?: boolean },
): DerivedEnvironment<E, Environment<Distribute<F[number]>>>
}

declare const environment: EnvironmentCtor

export default environment
38 changes: 38 additions & 0 deletions packages/env/Environment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { extend } from './lib/extend.js'

export default class Environment {
constructor(factories, { parent, bind = false } = {}) {
this._parent = parent
this._factories = factories.slice()

const extended = parent ? extend(parent, this) : this

for (const factory of this._factories) {
if (typeof factory.prototype.init === 'function') {
factory.prototype.init.call(extended)
}

for (const method of factory.exports || []) {
if (bind) {
this[method] = factory.prototype[method].bind(extended)
} else {
this[method] = factory.prototype[method]
}
}
}

return extended
}

clone() {
const env = new Environment(this._factories, this._parent)

for (const factory of env._factories) {
if (typeof factory.prototype.clone === 'function') {
factory.prototype.clone.call(env, this)
}
}

return env
}
}
30 changes: 16 additions & 14 deletions packages/env/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Zazuko's [RDF/JS Environment](https://github.com/rdfjs-base/environment)

Like `rdf-ext`, with the addition of `@tpluscode/rdf-ns-builders`
Like `rdf-ext`, with some additional features.

```js
import env from '@zazuko/env'
Expand All @@ -16,18 +16,6 @@ const sheldonCooper = env.clownface()
.addOut(env.ns.schema.knows, tbbt.leonard)
```

Also, provides TypeScript types for the default environment and a mapped type,
which helps declare derived environment type, which combines existing envirnment
with additional factories

```ts
import { create, DefaultEnv, DerivedEnvironment } from '@zazuko/env'

class MyFactory {}

const myEnv: DerivedEnvironment<MyFactory> = create(MyFactory)
```

## Additional features

### Dataset
Expand All @@ -43,7 +31,7 @@ Serializers are not added out of the box and the need to be imported first

```js
import rdf from '@zazuko/env'
import formats from '@rdfjs/formats-common'
import formats from '@rdfjs/formats'

rdf.formats.import(formats)

Expand All @@ -53,3 +41,17 @@ const dataset = rdf.dataset()

const turtle = await dataset.serialize({ format: 'text/turtle' })
```

#### Extending environments

This package adds the ability to create environments on top of existing ones. To do that, pass the parent environment to the constructor options.

```js
import env from '@zazuko/env';
import Environment from '@zazuko/env/Environment.js';
import MyFactory from './my-factory.js';

const myEnv = new Environment([MyFactory], { parent: env });
```

The `myEnv` will have all the factories from `env` and `MyFactory`.
8 changes: 3 additions & 5 deletions packages/env/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import Environment from '@rdfjs/environment'
import type { FactoryConstructor, Environment as IE } from '@rdfjs/environment/Environment.js'
import DataFactory from '@rdfjs/data-model/Factory.js'
import NamespaceFactory from '@rdfjs/namespace/Factory.js'
import FormatsFactory from '@rdfjs/formats/Factory.js'
Expand All @@ -8,11 +6,12 @@ import TermSetFactory from '@rdfjs/term-set/Factory.js'
import NsBuildersFactory from '@tpluscode/rdf-ns-builders'
import ClownfaceFactory from 'clownface/Factory.js'
import TraverserFactory from '@rdfjs/traverser/Factory.js'
import Environment from './Environment.js'
import DatasetFactory from './lib/DatasetFactory.js'

export type DerivedEnvironment<Env, Ex> = Env extends IE<infer F> ? IE<Ex | F> : never
export { DerivedEnvironment } from './lib/extend.js'

export function create<F extends FactoryConstructor>(...additionalFactories: F[]) {
export function create() {
return new Environment([
DataFactory,
DatasetFactory,
Expand All @@ -23,7 +22,6 @@ export function create<F extends FactoryConstructor>(...additionalFactories: F[]
TermMapFactory,
TermSetFactory,
TraverserFactory,
...additionalFactories,
])
}

Expand Down
29 changes: 29 additions & 0 deletions packages/env/lib/extend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Environment } from '@rdfjs/environment/Environment.js'

type Factories<E> = E extends Environment<infer F> ? F : never;
type Distribute<U> = U extends any ? Factories<U> : never;

type CombinedEnvironment<E extends ReadonlyArray<Environment<any>>> = Environment<Distribute<E[number]>>
export type DerivedEnvironment<Env extends Environment<unknown>, Ex extends Environment<unknown>> = CombinedEnvironment<[Env, Ex]>

export function extend<E extends Environment<any>, P extends Environment<any>>(self: E, parent: P) {
const envs = [self, parent]

return new Proxy({}, {
get(target, prop) {
const value = envs.find(env => prop in env)
if (value) {
return value[prop as keyof E]
}
},
set(target, prop, value) {
envs[0][prop as keyof E] = value

return true
},
has(target, prop) {
return envs.some(env => prop in env)
},
}) as any as DerivedEnvironment<P, E>
}
34 changes: 34 additions & 0 deletions packages/env/test/Environment-extend.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import DataFactory from '@rdfjs/data-model/Factory.js'
import DatasetFactory from '@rdfjs/dataset/Factory.js'
import { expect } from 'chai'
import { schema } from '@tpluscode/rdf-ns-builders'
import Environment from '../Environment.js'
import { TestFactory } from './TestFactory.js'

describe('Environment with parent', () => {
it('merges all factories', () => {
// given
const dataEnv = new Environment([DataFactory])

// when
const env = new Environment([DatasetFactory], { parent: dataEnv })

// then
expect(env).to.have.property('dataset')
expect(env).to.have.property('namedNode')
expect(env.namedNode('http://schema.org/Person')).to.deep.eq(schema.Person)
})

it('merge multiple levels', () => {
// given
const dataEnv = new Environment([DataFactory])
const datasetEnv = new Environment([DatasetFactory], { parent: dataEnv })

// when
const env = new Environment([TestFactory], { parent: datasetEnv })

// then
const test = env.test()
expect(test.size).to.eq(1)
})
})
Loading

0 comments on commit fde3143

Please sign in to comment.