Featurevisor

Advanced

Datasource & Adapters

By default, Featurevisor CLI uses the file system for reading and writing data in your project, given it's a Git repository after all. But the configuration API allows you to switch to any source via adapters.

Accessing datasource

It's unlikely that you will make use of the Datasource API yourself directly, unless you are a plugin developer.

The datasource object allows you to read and write data from/to the Featurevisor project, so that you don't have to deal with the file system (or any other custom source of your project data) directly.

You can refer to the full datasource API for more details.

Datasource methods

Once you have access to the datasource object, you can use the following methods from its instance.

Most definitions follow the same pattern of list, exists, read, write, and delete methods.

Revision

See state files for more details.

const revision = await datasource.readRevision()
await datasource.writeRevision(revision + 1)

Features

See features for more details.

const features = await datasource.listFeatures()
const fooFeatureExists = await datasource.featureExists('foo')
const fooFeature = await datasource.readFeature('foo')
await datasource.writeFeature('foo', { ...fooFeature, ...newData })
await datasource.deleteFeature('foo')

To collect the full chain of features that a feature depends on via its required property:

const requiredKeys = await datasource.getRequiredFeaturesChain('foo')

It returns a Set of feature keys, and it also covers dependencies of dependencies.

Segments

See segments for more details.

const segments = await datasource.listSegments()
const fooSegmentExists = await datasource.segmentExists('foo')
const fooSegment = await datasource.readSegment('foo')
await datasource.writeSegment('foo', { ...fooSegment, ...newData })
await datasource.deleteSegment('foo')

Attributes

See attributes for more details.

const attributes = await datasource.listAttributes()
const fooAttributeExists = await datasource.attributeExists('foo')
const fooAttribute = await datasource.readAttribute('foo')
await datasource.writeAttribute('foo', { ...fooAttribute, ...newData })
await datasource.deleteAttribute('foo')

Attributes of type object can declare nested properties. To list every attribute key together with those nested properties in their dot separated form:

const keys = await datasource.listFlattenedAttributes()
// ["userId", "device", "device.os", "device.version"]

Groups

See groups for more details.

const groups = await datasource.listGroups()
const fooGroupExists = await datasource.groupExists('foo')
const fooGroup = await datasource.readGroup('foo')
await datasource.writeGroup('foo', { ...fooGroup, ...newData })
await datasource.deleteGroup('foo')

Schemas

See schemas for more details.

const schemas = await datasource.listSchemas()
const fooSchemaExists = await datasource.schemaExists('foo')
const fooSchema = await datasource.readSchema('foo')
await datasource.writeSchema('foo', { ...fooSchema, ...newData })
await datasource.deleteSchema('foo')

Targets

See targets for more details.

const targets = await datasource.listTargets()
const fooTargetExists = await datasource.targetExists('foo')
const fooTarget = await datasource.readTarget('foo')
await datasource.writeTarget('foo', { ...fooTarget, ...newData })
await datasource.deleteTarget('foo')

Tests

See testing for more details.

const tests = await datasource.listTests()
const fooTest = await datasource.readTest('foo')
await datasource.writeTest('foo', { ...fooTest, ...newData })
await datasource.deleteTest('foo')

To find the spec file name that belongs to a test key:

const specName = await datasource.getTestSpecName('foo')

State

See state files for more details.

const existingState = await datasource.readState(environment)
datasource.writeState(environment, { ...existingState, ...newState })

If your project has no environments configured, pass false instead of an environment name:

const existingState = await datasource.readState(false)

Datafiles

See building datafiles for more details.

const datafiles = await datasource.listDatafiles()

Each entry contains the datafile path relative to the datafiles directory, together with its size and gzipSize in bytes.

Individual datafiles are read and written with an options object, where environment is either an environment name or false for projects without environments:

const datafile = await datasource.readDatafile({
environment: 'production',
target: 'web',
})
await datasource.writeDatafile(datafileContent, {
environment: 'production',
target: 'web',
})

An optional datafilesDir property overrides the configured datafilesDirectoryPath for that call.

History

To get history of changes made to a specific entity:

const fooChanges = await datasource.listHistoryEntries('feature', 'foo')

The first argument for entity type can be one of:

  • feature
  • segment
  • attribute
  • group
  • schema
  • target
  • test

Both arguments are optional. Omitting them lists the history of the whole project.

A specific commit can also be read, optionally narrowed down to one entity:

const commit = await datasource.readCommit(commitHash)
const fooCommit = await datasource.readCommit(commitHash, 'feature', 'foo')

Sets

If your project uses sets, the datasource is always scoped to a single set. A datasource created without a set reads and writes the root project.

const sets = await datasource.listSets()
const currentSet = datasource.getSet()
const storefront = datasource.forSet('storefront')
const storefrontFeatures = await storefront.listFeatures()

forSet() returns a new datasource instance with that set's own configuration and directory paths applied. Plugins that support sets should honour the --set option and scope their datasource accordingly, otherwise they end up reading the wrong tree.

Project configuration

const config = datasource.getConfig()
const extension = datasource.getExtension()

getConfig() returns the fully processed configuration that applies to this datasource, including any set specific overrides. getExtension() returns the file extension of the configured parser, like yml or json.

Adapters

Because a Featurevisor project is a Git repository by default, Featurevisor CLI ships with a default adapter that reads and writes data from/to the file system which is called FilesystemAdapter.

You don't have to configure this adapter explicitly anywhere, unless you are writing a custom one.

Writing a custom adapter

You can write your own custom datasource adapter as follows:

adapters/custom-adapter.ts
import { Adapter } from '@featurevisor/core'
export class CustomAdapter extends Adapter {
// ...implement the methods here
}

Refer to the implementation of FilesystemAdapter to understand more.

Using a custom adapter

You can swap out the default file system adapter with your custom adapter via you configuration file as found in featurevisor.config.js:

featurevisor.config.js
const { CustomAdapter } = require('./adapters/custom-adapter')
module.exports = {
environments: ['staging', 'production'],
tags: ['web', 'mobile'],
adapter: CustomAdapter,
}
Previous
Plugins