diff --git a/README.md b/README.md
index 433c9306..5e059ca4 100644
--- a/README.md
+++ b/README.md
@@ -2,104 +2,42 @@
⚠️ Expect API changes until v1.0.0 ⚠️
-Current version: 0.2.1. Follows [semver](https://semver.org/).
+Current version: 0.3.0. Follows [semver](https://semver.org/).
-Bundle Size: 12kb minified & gzipped.
+Bundle Size: 14kb minified & gzipped.
-A minimalist & flexible toolkit for interactive islands & state management in hypermedia-driven web applications.
+A minimalist yet powerful toolkit for interactive islands in web applications.
-## Table of Contents
+## Features include:
-- [Motivation](#motivation)
-- [Key Features](#key-features)
-- [Who is this for?](#who-is-this-for)
-- [Philosophy](#philosophy)
-- [Get Started](#get-started--view-examples)
-- [Key Concepts / API](#key-concepts--api)
- - [ReactiveElement Class, Observable Objects, and HTML Tagged Templates](#reactiveelement-class-observable-objects-and-html-tagged-templates)
- - [Basics of Observables & Templates](#basics-of-observables--templates)
- - [Basics of Computed Properties & Effects](#basics-of-computed-properties--effects)
- - [cami.store(initialState)](#camistoreinitialstate)
- - [html](#html)
-- [Examples](#examples)
-- [Dev Usage](#dev-usage)
+* **Reactive Web Components**: Offers `ReactiveElement`, an extension of `HTMLElement` that automatically defines observables without any boilerplate. It also supports deeply nested updates, array manipulations, and observable attributes.
+* **Async State Management**: It allows you to fetch, cache, and update data from APIs with ease. The `query` method enables fetching data and caching it with configurable options such as stale time and refetch intervals. The `mutation` method offers a way to perform data mutations and optimistically update the UI for a seamless user experience.
+* **Streams & Functional Reactive Programming (FRP)**: Uses Observable Streams for managing asynchronous events, enabling complex event processing with methods like `map`, `filter`, `debounce`, and more. This allows for elegant composition of asynchronous logic.
+* **Cross-component State Management with a Singleton Store**: Uses a singleton store to share state between components. Redux DevTools compatible.
+* **Dependency Tracking**: Uses a dependency tracker to track dependencies between observables and automatically update them when their dependencies change.
-## Motivation
-
-I wanted a minimalist javascript library that has no build steps, great debuggability, and didn't take over my front-end.
-
-My workflow is simple: I want to start any application with normal HTML/CSS, and if there were fragments or islands that needed to be interactive (such as dashboards & calculators), I needed a powerful enough library that I could easily drop in without rewriting my whole front-end. Unfortunately, the latter is the case for the majority of javascript libraries out there.
-
-That said, I like the idea of declarative templates, uni-directional data flow, time-travel debugging, and fine-grained reactivity. But I wanted no build steps (or at least make 'no build' the default). So I created Cami.
-
-## Key Features:
-
-- Reactive Web Components: We suggest to start any web application with normal HTML/CSS, then add interactive islands with Cami's reactive web components. Uses fine-grained reactivity with observables, computed properties, and effects. Also supports for deeply nested updates. Uses the Light DOM instead of Shadow DOM.
-- Tagged Templates: Declarative templates with lit-html. Supports event handling, attribute binding, composability, caching, and expressions.
-- Store / State Management: When you have multiple islands, you can use a singleton store to share state between them, and it acts as a single source of truth for your application state. Redux DevTools compatible.
-- Easy Immutable Updates: Uses Immer under the hood, so you can update your state immutably without excessive boilerplate.
-- Anti-Features: You can't be everything to everybody. So we made some hard choices: No Build Steps, No Client-Side Router, No JSX, No Shadow DOM. We want you to build an MPA, with mainly HTML/CSS, and return HTML responses instead of JSON. Then add interactivity as needed.
-
-## Who is this for?
-
-- **Lean Teams or Solo Devs**: If you're building a small to medium-sized application, I built Cami with that in mind. You can start with `ReactiveElement`, and once you need to share state between components, you can add our store. It's a great choice for rich data tables, dashboards, calculators, and other interactive islands. If you're working with large applications with large teams, you may want to consider other frameworks.
-- **Developers of Multi-Page Applications**: For folks who have an existing server-rendered application, you can use Cami to add interactivity to your application, along with other MPA-oriented libraries like HTMX, Unpoly, Turbo, or TwinSpark.
-
-## Philosophy
+Below covers three examples: a simple counter component to get you started, a shopping cart component to demonstrate server & client state management, and a registration form that demonstrates streams & functional reactive programming.
-- **Less Code is Better**: In any organization, large or small, team shifts are inevitable. It's important that the codebase is easy to understand and maintain. This allows any enterprising developer to jump in, and expand the codebase that fits their specific problems.
-
-## Get Started & View Examples
-
-To see some examples, just do the following:
-
-```bash
-git clone git@github.com:kennyfrc/cami.js.git
-cd cami.js
-bun install --global serve
-bunx serve
-```
-
-Open http://localhost:3000 in your browser, then navigate to the examples folder. In the examples folder, you will find a series of examples that illustrate the key concepts of Cami.js. These examples are numbered & ordered by complexity.
+## Counter Component
-## Key Concepts / API
-
-### `ReactiveElement Class`, `Observable` Objects, and `HTML Tagged Templates`
-
-`ReactiveElement` is a class that extends `HTMLElement` to create reactive web components. These components can automatically update their view (the `template`) when their state changes.
-
-Automatic updates are done by observables. An observable is an object that can be observed for state changes, and when it changes, it triggers an effect (a function that runs in response to changes in observables).
-
-Cami's observables have the following characteristics:
-
-* They can be accessed and mutated like any other data structure. The difference is that observables, upon mutation, notify observers.
-* The observer in the `ReactiveElement` case is the `html tagged template` or the `template()` method to be specific.
-
-When you mutate the value of an observable, it will automatically trigger a re-render of the component's `html tagged template`. This is what makes the component reactive.
-
-Let's illustrate these three concepts with an example. Here's a simple counter component:
+Notice that you don't have to define observables or effects. You can just directly mutate the state, and the component will automatically update its view. Loading, error, and data states are also handled automatically.
```html
+
This fetches the products from an API, and uses a client-side store to manage the cart.Counter
Products
+ Cart
+
Count: ${this.count}
- `; - } -``` +```html + +Try entering an email that is already taken, such as geovanniheaney@block.info (mock email)
+ + + + + ``` -In this case, `countSquared` will always hold the square of the current count value, and will automatically update whenever `count` changes. - -**Effects:** - -Effects in Cami.js are functions that run in response to changes in observable properties. They are a great way to handle side effects in your application, such as integrating with non-reactive components, emitting custom events, or logging/debugging. +## Motivation -Here is an example of an effect that logs the current count and its square whenever either of them changes: +I wanted a minimalist javascript library that has no build steps, great debuggability, and didn't take over my front-end. -```javascript -this.effect(() => console.log(`Count: ${this.count} & Count Squared: ${this.countSquared}`)); -``` +My workflow is simple: I want to start any application with normal HTML/CSS, and if there were fragments or islands that needed to be interactive (such as dashboards & calculators), I needed a powerful enough library that I could easily drop in without rewriting my whole front-end. Unfortunately, the latter is the case for the majority of javascript libraries out there. -This effect will run whenever `count` or `countSquared` changes, logging the new values to the console. +That said, I like the idea of declarative templates, uni-directional data flow, time-travel debugging, and fine-grained reactivity. But I wanted no build steps (or at least make 'no build' the default). So I created Cami. -**Observable Attributes:** +## Who is this for? -Observable attributes in Cami.js are attributes that are observed for changes. When an attribute changes, the component's state is updated and the component is re-rendered. +- **Lean Teams or Solo Devs**: If you're building a small to medium-sized application, I built Cami with that in mind. You can start with `ReactiveElement`, and once you need to share state between components, you can add our store. It's a great choice for rich data tables, dashboards, calculators, and other interactive islands. If you're working with large applications with large teams, you may want to consider other frameworks. +- **Developers of Multi-Page Applications**: For folks who have an existing server-rendered application, you can use Cami to add interactivity to your application. -Here is an example of an observable attribute `todos` which is parsed from a JSON string: +## Get Started & View Examples -```javascript -todos = [] +To see some examples, just do the following: -constructor() { - super(); - this.setup({ - observables: ['todos'], - attributes: { - todos: (v) => JSON.parse(v).data - } - }); -} +```bash +git clone git@github.com:kennyfrc/cami.js.git +cd cami.js +bun install --global serve +bunx serve ``` -In this case, `todos` will be updated whenever the `todos` attribute of the element changes. - -**Queries:** +Open http://localhost:3000 in your browser, then navigate to the examples folder. In the examples folder, you will find a series of examples that illustrate the key concepts of Cami.js. These examples are numbered & ordered by complexity. -Queries in Cami.js are a way to fetch data asynchronously and update the component's state when the data is available. The state is automatically updated with the loading status, the data, and any error that might occur. +## Learn Cami by Example -Here is an example of a query that fetches posts from a JSON placeholder API: +In this tutorial, we'll walk through creating a simple Task Manager component using Cami.js. By the end, you'll understand how to create interactive components with Cami's reactive system. -```javascript -posts = {} +### Step 1: Creating a Basic Component -constructor() { - super(); - this.setup({ - observables: ['posts'], - }); -} +Let's start by creating a basic structure for our Task Manager component. We'll define the HTML structure and include the necessary scripts to use Cami.js. -connectedCallback() { - super.connectedCallback(); - this.posts = this.query({ - queryKey: ["posts"], - queryFn: () => fetch("https://jsonplaceholder.typicode.com/posts").then(posts => posts.json()) - }); -} -``` +```html +Cart value: ${this.cartValue}
+ + + + +Try entering an email that is already taken, such as geovanniheaney@block.info (mock email)
+ @@ -484,32 +510,91 @@ They are also listed below: const { html, ReactiveElement } = cami; class FormElement extends ReactiveElement { + emailError = '' + passwordError = '' email = ''; password = ''; - emailError = ''; - passwordError = ''; + emailIsValid = null; + isEmailAvailable = null; + + inputValidation$ = this.stream(); + passwordValidation$ = this.stream(); + + onConnect() { + this.inputValidation$ + .map(e => this.validateEmail(e.target.value)) + .debounce(300) + .subscribe(({ isEmailValid, emailError, email }) => { + this.emailError = emailError; + this.isEmailValid = isEmailValid; + this.email = email; + this.isEmailAvailable = this.queryEmail(this.email) + }); + + this.passwordValidation$ + .map(e => this.validatePassword(e.target.value)) + .debounce(300) + .subscribe(({ isValid, password }) => { + this.passwordError = isValid ? '' : 'Password must be at least 8 characters long.'; + this.password = password; + }); + } + + validateEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + let emailError = ''; + let isEmailValid = null; + if (email === '') { + emailError = ''; + isEmailValid = null; + } else if (!emailRegex.test(email)) { + emailError = 'Please enter a valid email address.'; + isEmailValid = false; + } else { + emailError = ''; + isEmailValid = true; + } + return { isEmailValid, emailError, email }; + } - constructor() { - super(); - this.setup({ - observables: ['email', 'password', 'emailError', 'passwordError'] + validatePassword(password) { + let isValid = false; + if (password === '') { + isValid = null; + } else if (password?.length >= 8) { + isValid = true; + } + + return { isValid, password } + } + + queryEmail(email) { + return this.query({ + queryKey: ['Email', email], + queryFn: () => { + return fetch(`https://mockend.com/api/kennyfrc/cami-mock-api/users?email_eq=${email}`).then(res => res.json()) + }, + staleTime: 1000 * 60 * 5 }) } - validateEmail() { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(this.email)) { - this.emailError = 'Please enter a valid email address.'; + getEmailInputState() { + if (this.email === '') { + return ''; + } else if (this.isEmailValid && this.isEmailAvailable?.status === 'success' && this.isEmailAvailable?.data?.length === 0) { + return false; } else { - this.emailError = ''; + return true; } } - validatePassword() { - if (this.password.length < 8) { - this.passwordError = 'Password must be at least 8 characters long.'; + getPasswordInputState() { + if (this.password === '') { + return ''; + } else if (this.passwordError === '') { + return false; } else { - this.passwordError = ''; + return true; } } @@ -518,15 +603,20 @@ They are also listed below: `; } @@ -538,65 +628,95 @@ They are also listed below: ```html -