Tungsten

Type-safe global store

Your store is shaped by creating a Store Schema. This Schema specifies the types and default values of store entries.
import {
type ComponentPropsBase, type ComponentStateConstraint,
registerStore, BaseSimpleStoreComponent, BaseStatefulStoreComponent
} from 'tungsten'
// define a store schema, specifying entry types and default values
class StoreSchema {
username: string = 'Bob'
profilePicture: string = '/images/profile-picture.jpg'
}
Base store components are created using this Schema. This is boilerplate that you do not need to change.
import {
type ComponentPropsBase, type ComponentStateConstraint,
registerStore, BaseSimpleStoreComponent, BaseStatefulStoreComponent
} from 'tungsten'
import { StoreSchema } from './StoreSchema.js'
const getStore = registerStore(StoreSchema)
export abstract class SimpleStoreComponent<
Props extends TungstenComponentPropsBase = object,
RouteData = undefined
> extends BaseSimpleStoreComponent<Props, RouteData, StoreSchema> {
store = getStore(this)
}
export abstract class StatefulStoreComponent<
Props extends TungstenComponentPropsBase = object,
State extends TungstenComponentStateConstraint = object,
RouteData = undefined
> extends BaseStatefulStoreComponent<Props, State, RouteData, StoreSchema> {
store = getStore(this)
}
The registerStore call in the code above passes the Schema to Tungsten, which creates the store. Each entry in the schema becomes an object with 4 methods. A simplified version of an entry object looks like this (typed, but types not shown):
entry: {
#value: initialValue,
#subscribers: [],
get = () => #value,
set = (newValue) => {
#value = newValue
notifySubscribers(subscribers)
},
subscribe = () => {
#subscribers.push(callingComponent)
},
sync = () => {
#subscribers.push(callingComponent)
return #value
}
}
Once we have the store schema and base class defined, we can easily create a new component with store access
import { SimpleStoreComponent } from '../StoreComponents.js'
export class Profile extends SimpleStoreComponent {
content = () => <section>
<img src={this.store.profilePicture.sync()} /><br />
<span>{this.store.username.sync()}</span>
</section>
style = Sass`
img
height: 4em
`
}
By using
this.store.profilePicture.sync()
this.store.username.sync()
we get the current values for those store entries, and also cause the Profile to update with the new values if they are changed. Here is a demo.
import { BindableInput } from 'tungsten'
import { SimpleStoreComponent } from '../StoreComponents.js'
export class UpdateProfile extends SimpleStoreComponent {
username = this.store.username.get()
update = () => this.store.username.set(this.username)
content = () => <section>
<label htmlFor='username'>Update Username:</label>&nbsp;
<BindableInput name="username" bind={[this as UpdateProfile, 'username']} /><br />
<button onClick={this.update}>Update</button>
</section>
}
<Profile /><br />
<UpdateProfile /><br />

Bob