Featurevisor

Extending Featurevisor

Datasource and adapters

Featurevisor reads and writes definitions through a datasource. By default it uses the files in your repository, and an adapter can replace them with another store.

Plugins receive the datasource as datasource. Its methods work the same whatever the format or the adapter. The source has the full signatures.

Definitions

Every kind of definition has five methods. For features:

const keys = await datasource.listFeatures()
const exists = await datasource.featureExists('checkout')
const checkout = await datasource.readFeature('checkout')
await datasource.writeFeature('checkout', checkout)
await datasource.deleteFeature('checkout')

The other kinds use their own names in the same places:

KindMethods
FeatureslistFeatures, featureExists, readFeature, writeFeature, deleteFeature
Global variableslistVariables, variableExists, readVariable, writeVariable, deleteVariable
SegmentslistSegments, segmentExists, readSegment, writeSegment, deleteSegment
AttributeslistAttributes, attributeExists, readAttribute, writeAttribute, deleteAttribute
SchemaslistSchemas, schemaExists, readSchema, writeSchema, deleteSchema
TargetslistTargets, targetExists, readTarget, writeTarget, deleteTarget
TestslistTests, readTest, writeTest, deleteTest
ScenarioslistScenarios, scenarioExists, readScenario, writeScenario, deleteScenario

Global variable methods work on files in variables/, not on variables inside a feature.

Dependencies

getRequiredFeaturesChain() returns a Set of the feature and every feature its conditions reference, however indirectly:

await datasource.getRequiredFeaturesChain('chatFileUpload')
// Set { 'chatFileUpload', 'liveChat', 'pricing' }

getRequiredFeaturesChainForVariable() returns the features a global variable depends on, through its root conditions, its overrides, any composition sources and the other global variables those read. It throws if the variable does not exist:

await datasource.getRequiredFeaturesChainForVariable(
'supportEmail',
)
// Set { 'checkout' }

Nested attributes

listFlattenedAttributes() lists attributes with the properties of object attributes in dot form:

await datasource.listFlattenedAttributes()
// ['account', 'account.locale', 'account.seats', 'city', ...]

Test and scenario files

Test and scenario keys come from their paths under tests/, without .spec, such as features.checkout. Use the keys listTests() and listScenarios() return.

  • getTestSourcePath(key) returns the path of a test's file, including an existing .spec.yml name.
  • getScenarioSourcePath(key) does the same for a scenario, and throws if the adapter cannot give one.
  • getTestSpecName(key) builds a file name such as features.checkout.yml from the key and the parser's extension, without looking for the file.

All three return a string, without a promise.

Revision and state

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

The revision is a string. Convert it to a number to increase it.

const state = await datasource.readState('production')
await datasource.writeState('production', state)

Pass false instead of an environment name in a project without environments.

Datafiles

listDatafiles() lists built datafiles, each with its path relative to the datafiles directory and its size and gzipSize in bytes. A datafile is read and written by its environment, or false, and its target:

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

datafilesDir in the options overrides datafilesDirectoryPath for that call.

History

listHistoryEntries() lists the Git history of one definition, or of the whole project when called without arguments:

const entries = await datasource.listHistoryEntries(
'feature',
'checkout',
)

The type is one of feature, segment, attribute, schema, variable, target, test or scenario. readCommit() reads one commit, optionally narrowed to one definition:

const commit = await datasource.readCommit(hash)
const change = await datasource.readCommit(hash, 'feature', 'checkout')

Sets

In a project with sets, a datasource works on one set at a time. One created without a set reads and writes the project root.

const sets = await datasource.listSets()
const current = datasource.getSet()
const storefront = datasource.forSet('storefront')
const keys = await storefront.listFeatures()

forSet() returns a new datasource with that set's directories. A plugin that supports sets should honour --set and choose the set it names, or it reads the wrong tree.

getConfig() returns the resolved configuration for the datasource, set included, and getExtension() returns the parser's extension, such as yml.

Adapters

The default adapter, FilesystemAdapter, keeps definitions as files in the repository. Write your own only to store them somewhere else.

Write an adapter

Extend Adapter from @featurevisor/core, and implement every method below:

adapters/custom-adapter.ts
import { Adapter } from '@featurevisor/core'
export class CustomAdapter extends Adapter {
// implement every method in the table
}
MethodReturnsDoes
listSets()Promise<string[]>Lists the sets
listEntities(entityType)Promise<string[]>Lists the keys of one kind
entityExists(entityType, entityKey)Promise<boolean>Checks that a definition exists
readEntity<T>(entityType, entityKey)Promise<T>Reads a parsed definition
writeEntity<T>(entityType, entityKey, entity)Promise<T>Stores a definition and returns it
deleteEntity(entityType, entityKey)Promise<void>Deletes a definition
readState(environment)Promise<ExistingState>Reads build state
writeState(environment, existingState)Promise<void>Stores build state
readDatafile(options)Promise<DatafileContent>Reads a built datafile
writeDatafile(datafileContent, options)Promise<void>Stores a built datafile
readRevision()Promise<string>Reads the revision
writeRevision(revision)Promise<void>Stores the revision
listHistoryEntries(entityType?, entityKey?)Promise<HistoryEntry[]>Lists project or definition history
readCommit(commitHash, entityType?, entityKey?)Promise<Commit>Reads a commit, optionally for one definition

entityType takes the types listed under History. State methods take an environment name or false, and datafile methods take the options shown under Datafiles.

These are optional:

  • listDatafiles(). Without it, the datasource returns an empty list.
  • getTestSourcePath(testKey) and getEntityPath(entityType, entityKey). Without them, test paths are built from the configuration.
  • getScenarioSourcePath(scenarioKey). There is no fallback for scenarios.

The datasource creates your adapter with the resolved configuration and, optionally, the project root. Use them to follow each set's paths. Adapter has the exact types, and FilesystemAdapter is a complete example.

Use an adapter

Set it in the configuration:

featurevisor.config.js
const { CustomAdapter } = require('./adapters/custom-adapter')
module.exports = {
adapter: CustomAdapter,
}
Previous
Plugins