@klaudhaus/superstate
v0.4.1
Published
Extensible state specification and management
Downloads
1
Readme
SuperState
Supercharging the state of model-driven development
About
Objective
The aim of SuperState is to build upon the basic concepts of a data model by providing further information about the types and fields in the model and a way of managing and processing that information in a running system. This promotes modelling business logic close to the type system rather than being scattered throughout application code, and facilitates generic implementations of user interface elements, integration services etc. The objective is to support and extend the TypeScript and GraphQL type systems, but the approach could also be applied to other languages.
Open Source License
SuperState is published under a permissive open source license (MIT) and may be embedded, extended or forked for any purpose. Community input is welcome but there are no implied warranties or service level agreements for things such as feature requests or issue resolution. If you are interested in using SuperState in mission critical environments, Klaudhaus offers support, training and development services, please get in touch at https://klaudhaus.com.
Why SuperState?
The SuperState project aims to formalise a set of reusable patterns for extending data type schemas with additional capabilities around things such as validation rules, presentation information, integration status and so on. Such concerns are recurrent across diverse information systems, and having a standard approach for encoding them close to the data model definition provides a number of benefits.
Firstly, the rules are more implementation-independent, decoupled from choices about specific application and distribution technologies (e.g. web vs native, React vs Vue, etc.) allowing easier reuse in different contexts.
Secondly, with agreement about generic standards, greater reusability can be built into things such as visual components and integration services such that they can auto-adapt across new systems and data types.
Thirdly, the knowledge that is embodied within such generically represented business rules can be validated, preserved and evolved to support the broader organisational mission over time. It becomes a quantifiable and accountable asset to the company, representing a growing and protected body of learning about the model and processes upon which business value is based.
Policy Modules
SuperState extends the standard type system by adding criteria defined in Policies. These define the content of typed Specifications and extended state Properties. This standardised pattern for state management supports pluggable implementations for functionality.
For an example of a policy module, see @klaudhaus/sup-validation
. For a simple example of an application that uses the SuperState with this validation policy module, see @klaudhaus/superstate-sample
.
Concepts
SuperState extends a standard type system in accordance with policies provided by pluggable modules, which cover the types of Specifications, the extended state's Properties and, potentially, the available processes (state transitions).
As a conceptual example, take a system which declares a basic Contact type with name, phone, email and age fields. On its own, the standard type system can manage the declaration of objects of this type, and check field access to make sure they are valid. SuperState might be set up with a policy that states "each field can have a label which is a string, and a validation rule that matches its datatype". There would be a SuperState Specification for the Contact type which would add the labels and validation rules to the individual fields, and a Properties definition describing the related runtime state of any individual field, such as whether it is valid and an error message if it is not. The module that provides the validation-related policy elements can also provide validation processes that can be triggered for any field in the model - because they all conform to the same Specifications and have common Properties. By extending this pattern across all data types we end up with a well-structured definition of the fundamentals of an information system.
Types
SuperState builds on the well-established principles of a type system. The following examples will use these TypeScript types:
type Contact = {
name: string
age: number
member: boolean
address: Address
phone: string
email: string
}
type Address = {
street: string
suburb: string
state: AuState
postcode: string
}
export enum AuState { NSW, SA, QLD, VIC, WA, NT, ACT, TAS }
SuperState refers to the data members of types as "fields", so Contact has an age field, for example, as well as an address field of type Address.
Policies
A Policy defines the additional information that can be added to the data model. It would normally be made up of modular components such as Validation policy and Presentation policy. Due to the lack of higher-kinded types in TypeScript, these are not declared as separate policy types, but each declare their own properties within a standard namespace called Policy, with interfaces Specification and Properties. The Policy namespace is defined in such a way that these properties may be generic in the type of any individual field and/or in type of the parent object in which a field exists. In this way, it's possible to have something like, say, a validation function and have field-level type checking when that policy setting is applied in a specification.
Specification Policy
The specification policy defines the additional information that can be added to the data model. The SuperState package includes some default policy examples such as Validation, which defines a structure for specifying data validation constraints, and Presentation, which includes basics such as field labels. The PresentationPolicy looks like this:
export type PresentationPolicy = {
label: string
inputType?: "text" | "email"
}
Policies can be generically typed so that the extra information is assured to match the type of its associated field. For example the ValidationPolicy defines a validator property to be a function that validates the field's data type.
export type ValidationPolicy<T> = {
validator?: Validator<T>
}
A system can define it's own policies, and can combine them to make an overall policy such as the following:
export type SystemPolicy<T> = PresentationPolicy & ValidationPolicy<T>
Specifications
Specifications are the definition of what the policy settings are for individual fields within the data model. A general specification type is defined from the system policy as follows:
export type SystemSpec<T> = FieldSpec<SystemPolicy<T>, T>
Using this type, specifications can be defined for the system in question.
And the following
A TypeSpec can be defined which encapsulates validation information as follows:
const contactSpec = TypeSpec<Contact> {
name: {
label: "Name",
inputType
}
}
State
The runtime structure of a SuperState of Contact with the Policy settings described above can be obtained by passing an instance of Contact along with its TypeSpec:
import { superstate } from "superstate"
const contact = {
name: "Tim",
phone: "",
email: "",
age: 10
}
const contactState = superstate(contactSpec, contact)
contact.name // [object]
contact.name.value // "Tim" - each field's superstate has a value
contact.name.valid // true -
contact.name.errorMessage
Validation Policy
Validators and Constraints
The included ValidationPolicy provides a way of specifying rules about what values a field can have. These rules are provided in the form of a Validator<V>
where V
is the type of data to be validated. These are often created using the Constraint pattern - a Constraint<C, V>
is a function that takes a configuration of type C
and returns a validator for type V
. So, for example, to validate teh minimum length of strings, use minLength
which is of type Constraint<number, string>
- it is configured with a number (the minimum length to check for) and is applied to values of type string.
Systems can provide their own Constraints to define new kinds of validation rule.
Where a validation applies to multiple