npm package: @sophosoft/nano-state
Description: NanoState: efficient, effective, and extensible state management
Latest version: 0.3.0

# nano-state

Efficient, effective, and extensible state management: less is more.

## installation
```sh
npm i @sophosoft/nano-state
# or
yarn add @sophosoft/nano-state
```

## usage
`nano-state` is built from the ground-up with TypeScript to leverage strong typing.  The target ECMAScript version is `es5`.  While the examples below may be contrived, they should illustrate normal usage.

### state
Extend the `State` type to define interfaces for your various states:
```ts
import { State } from '@sophosoft/nano-state'

interface UserState extends State {
    displayName: string
    email: string
    id?: string
    visits: number
}
```

### context
A `Context` provides exclusive managment of state.  Implement getters and mutation methods as necessary in the context.
```ts
import { Context } from '@sophosoft/nano-state'

class UserContext extends Context<UserState> {
    public constructor() {
        // initialize default state
        super({
            displayName: 'Guest',
            email: '',
            visits: 0
        })
    }

    public get DisplayName(): string {
        return this.state.displayName
    }

    // ...

    public login(id: string, name: string, email: string, visits: number): void {
        this.state.displayName = name
        this.state.email = email
        this.state.id = id
        this.state.visits = visits + 1
        this.notify('User:LoggedIn', { id: this.state.id })
    }

    public async cache(manager: CacheManager): Promise<void> {
        await manager.write('user', this.state)
        this.notify('User:Cached')
    }

    // ...
}
```
> Note the usage of `this.notify(string, payload?)`.  This is the internal event system that is described below.

### subscription
The `Subscription` abstract class allows middleware to react to one or more events.  The contract includes one `async handle(event: Event<EventPayload>)` method, however a strategy pattern may be used to handle multiple events when necessary.  By default, a subscription will listen to all events using the wildcard (`*`) event id.  Events are described next.
```ts
import { Event, EventPayload, Subscription } from '@sophosoft/nano-state'

class LogSubscription extends Subscription {
    public async handle(event: Event<EventPayload>): Promise<void> {
        console.log(event)
    }
}
```
```ts
import { Subscription } from '@sophosoft/nano-state'

class AuthNSubscription extends Subscription {
    public constructor() {
        super(['AuthN:LogIn', 'AuthN:LogOut'])
    }

    public async handle(event: AuthNEvent): Promise<void> {
        if (event.id === 'AuthN:LogIn') {
            return this.handleLogIn(event)
        } else if (event.id === 'AuthN:LogOut') {
            return this.handleLogOut(event)
        }
    }

    // ...
}
```

#### event
Extend the `Event` and `EventPayload` types to define specific event payloads.

Events contain an `id`, `store`, and an optional `payload`.  The `Store` is descibed next and may be used by subscriptions to access various contexts and trigger additional events.  By default, the payload may be undefined, which will cause syntax errors if payload properties are accessed without proper typing.
```ts
import { Event, EventPayload } from '@sophosoft/nano-state'

interface UserLoginPayload extends EventPayload {
    id: string
    name: string
    email: string
    visits: number
}

interface UserLoginEvent extends Event<UserLoginPayload> {
    payload: UserLoginPayload
}
```

### store
The `Store` class provides access to all of the contexts in an application and dispatches events to all subscriptions.  It is intended to be a singleton, globally accessible throughout an application.

Contexts are mapped by aliases, which allow third-party libraries to be integrated with domain-specific names.  In order to minimize the inital overhead of registering multiple Contexts, a factory method can be used to lazy-load a Context only when it is requested via `Store.getContext()`.  In this way, a Context is not instanciated until it's needed, and then only once:  The resulting instance is stored for subsequent requests.

When an event is triggered, subscriptions are notified in the order in which they were registered.
```ts
import { Store, ContextMap, Subscription } from '@sophosoft/nano-state'

// perhaps imported from a config file
const contexts: ContextMap = {
    'authn': new AuthNContext(),
    'user': () => new UserContext(), // lazy-loading
    // ...
}

// perhaps also imported
const subscriptions: Subscription[] = [
    new LogSubscription(),
    new AuthNSubscription(),
    // ...
]

class AppStore extends Store {
    public static readonly Instance: AppStore = new AppStore()

    private constructor() {
        super(contexts, subscriptions)
    }
}
```
Using the store within an application is then relatively trivial.
```ts
import { AppStore } from '@/store/AppStore'
import { UserContext } from '@/user/UserContext'

const store: AppStore = AppStore.Instance

const user: UserContext = store.getContext<UserContext>('user')

console.debug(`${user.DisplayName} is the current user`)

store.trigger('AuthN:LogOut', { id: user.Id })
```