Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/igrep/svelte-store-tree

Provides writable/readable stores that can 'zoom' into the part of the store value (so called "nested store").
https://github.com/igrep/svelte-store-tree

state-management svelte

Last synced: about 5 hours ago
JSON representation

Provides writable/readable stores that can 'zoom' into the part of the store value (so called "nested store").

Awesome Lists containing this project

README

        

# svelte-store-tree

[![npm version](https://badge.fury.io/js/svelte-store-tree.svg)](https://badge.fury.io/js/svelte-store-tree)
[![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
![CI](https://github.com/igrep/svelte-store-tree/actions/workflows/ci.yaml/badge.svg)

Current status: Experimental.

Provides writable/readable stores that can 'zoom' into a part of the store
value (so-called "nested stores"). It enables us to manage the state of the app
in a single object while keeping the independence of every child component.

# Example

```typescript
import { writableTree, Refuse, into, isPresent } from 'svelte-store-tree';
import type { WritableTree } from 'svelte-store-tree';

type SomeRecord = {
id: number;
name: string;
contact: {
phone: string;
urls: string[];
};
favoriteColor: Color | undefined;
};

type Color = [number, number, number];

// Create a `WritableTree`
const someRecord: WritableTree = writableTree({
id: 0,
name: 'Y. Y',
contact: {
phone: '+81-00-0000-0000',
urls: [
'https://the.igreque.info',
'https://github.com/igrep',
],
},
favoriteColor: undefined
});

// Subscribe as an ordinary store.
someRecord.subscribe((newUser) => {
console.log('Updated the user', newUser);
});

// `zoom` with the `into` Accessor;
// Create a store that subscribes only a specific field of the object
const name = someRecord.zoom(into('name'));
const contact = someRecord.zoom(into('contact'));
const favoriteColor = someRecord.zoom(into('favoriteColor'));

name.subscribe((newName) => {
console.log('Updated the name', newName);
});
contact.subscribe((newContact) => {
console.log('Updated the contact', newContact);
});
favoriteColor.subscribe((newColor) => {
console.log('Updated the color', newColor);
});

// We can apply `zoom` deeper:
const urls = contact.zoom(into('urls'));

// Notifies the subscribers of `someRecord`, `contact`, and `urls`.
// ** Changes are propagated only to the direct subscribers, and the ancestors'. **
// ** Not to the the siblings' to avoid extra rerendering of the subscribing components. **
urls.update((u) => [...u, 'https://twitter.com/igrep']);

// If your record contains a union type, the `choose` method is useful.
// Pass a function that returns a `Refuse` (a unique symbol provided by this library)
// if the value doesn't satisfy the condition.
const favoriteColorNonUndefined =
favoriteColor.choose((color) => color ?? Refuse);

// Now, favoriteColorNonUndefined is typed as `WritableTree`,
// while favoriteColor is `WritableTree`.

// As a shortcut for a nullable type, svelte-store-tree provides
// the `isPresent` function used with `choose`:
const favoriteColorNonUndefined2 = favoriteColor.choose(isPresent);

favoriteColorNonUndefined.subscribe((newColor) => {
console.log('Updated the color', newColor);
});

// Notifies the subscribers of `someRecord`, `favoriteColor`, and `favoriteColorNonUndefined`.
favoriteColor.set([0xC0, 0x10, 0x10]);

// Notifies the subscribers of `someRecord`, and `favoriteColor` (not `favoriteColorNonUndefined`).
favoriteColor.set(undefined);
```

# Working Example App


Run on CodeSandbox

![Example App running on CodeSandbox](./docs/codesandbox.png "Example App running on CodeSandbox")

# Installation

```bash
$ npm install --save svelte-store-tree
```

# API

```typescript
// Core API
export function writableTree

(
value: P,
start: StartStopNotifier

= noop,
): WritableTree

;

export function readableTree

(
value: P,
start: StartStopNotifier

= noop,
): ReadableTree

/// Types related to the Core API
export type StoreTreeCore

= {
zoom(accessor: Accessor

): WritableTree;
zoomNoSet(readChild: (parent: P) => C | Refuse): ReadableTree;
choose(readChild: (parent: P) => P_ | Refuse): WritableTree;
};
export type ReadableTree

= Readable

& StoreTreeCore

;
export type WritableTree

= Writable

& StoreTreeCore

;

export const Refuse: unique symbol = Symbol();
export type Refuse = typeof Refuse;

/// Utility function to help the `StoreTreeCore.prototype.choose` method
export function isPresent

(parent: P): NonNullable

| Refuse;

// Accessor API
export class Accessor

{
constructor(readChild: (parent: P) => C | Refuse, writeChild: (parent: P, newChild: C) => void);
readChild: (parent: P) => C | Refuse;
writeChild: (parent: P, newChild: C) => void;
and(other: Accessor): Accessor

;
};

/// Various Utility Accessors
export function into

(key: K): Accessor

;
export function intoMap(key: K): Accessor, V>;
```