Skip to main content

Import the library

Copy-pasting rvx_juice.ts is the minimalist path. But you can also install Jon as a regular package:

npm install @priolo/jon
import { createStore, useStore } from "@priolo/jon"

The core API is identical to the juice file. So why install? Because the full library ships a few optional extras that the juice file deliberately leaves out to stay tiny.

What you get on top

addWatch — observe a store from outside

Watch actions/mutators of a store and react to them — the classic use case is linking two stores without coupling them:

import { addWatch } from "@priolo/jon"

// when authStore runs "logout", clear the cart.
// Neither store knows the other exists.
addWatch({
store: authStore,
actionName: "logout",
callback: () => cartStore.clear(),
})

See it in action: watch stores.

The full createStore also wires in lifecycle hooks like onStateChange (used for persistence) — those events are what the watcher system is built on, and they're not in the juice file.

useValidator — form validation

A small declarative validation system, independent from the store core:

import { useValidator, validateAll, resetAll } from "@priolo/jon"

const nameRules = {
required: (v: string) => !v ? "name is required" : null,
min: (v: string) => v.length < 3 ? "at least 3 characters" : null,
}

const nameValidator = useValidator(name, nameRules)
// → { helperText, error, inputRef } — spread-ready for a MUI TextField

const errors = validateAll() // validates every field, focuses the first invalid one

See it in action: validator form.

mixStores — compose setups like classes

Merge multiple setup objects into one, ready for createStore. Split a big store across files, or write a generic base setup and extend it with the real properties — inheritance for stores:

import { mixStores } from "@priolo/jon"

const store = createStore(mixStores(baseSetup, userSetup, cartSetup))

state, getters, actions, mutators and the lifecycle hooks are merged together; on conflicts the last setup wins, like a subclass override.

See it in action: mix stores.

storeToTools — your store as an LLM tool layer (experimental)

A store action is already name(payload) => result — which maps almost 1:1 onto an LLM "function declaration". storeToAiTools.ts turns store actions into tools the model can call, mutating the same reactive state the UI reads:

const { functionDeclarations, dispatchAll } = storeToTools(todoStore, [
{ name: "addTask", description: "Adds a task", parameters: {...} },
])
// declare functionDeclarations to Gemini/Claude,
// then dispatchAll(model function calls) → the UI updates by itself

It's still experimental (not exported from the package yet — copy the file, it's also self-contained). See it in action: src/examples/aiTools.

Rule of thumb

You needDo this
state / getters / actions / mutators, render predicatescopy rvx_juice.ts
watchers, lifecycle hooks, validator, mixStoresnpm install @priolo/jon