humn 1.7.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/guides/api-reference.md +91 -0
- package/docs/guides/getting-started.md +83 -0
- package/docs/guides/humn-files.md +87 -0
- package/docs/guides/interaction-helpers.md +117 -0
- package/docs/guides/lifecycle-hooks.md +51 -0
- package/docs/guides/scoped-css.md +22 -0
- package/docs/guides/state-management.md +172 -0
- package/docs/guides/typescript.md +89 -0
- package/docs/index.md +39 -0
- package/docs/internals/compiler.md +88 -0
- package/docs/internals/cortex-deep-dive.md +575 -0
- package/docs/internals/css.md +96 -0
- package/docs/internals/reactivity.md +72 -0
- package/docs/internals/runtime.md +38 -0
- package/docs/internals/virtual-dom.md +380 -0
- package/llms-full.txt +2079 -70
- package/package.json +4 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## `mount(target, Component)`
|
|
4
|
+
|
|
5
|
+
Mounts a component to a target DOM element.
|
|
6
|
+
|
|
7
|
+
- `target`: The DOM element to mount the component to.
|
|
8
|
+
- `Component`: The root component to render.
|
|
9
|
+
|
|
10
|
+
## `Cortex<Memory, Synapses>({ memory, synapses })`
|
|
11
|
+
|
|
12
|
+
Creates a strongly-typed `Cortex` instance for state management.
|
|
13
|
+
|
|
14
|
+
- **Generics**:
|
|
15
|
+
- `Memory`: The shape of your state object.
|
|
16
|
+
- `Synapses`: The shape of your actions object.
|
|
17
|
+
- **Arguments**:
|
|
18
|
+
- `memory`: The initial state. Values can be raw or wrapped in `persist()`.
|
|
19
|
+
- `synapses`: A builder function `(set, get) => Synapses`.
|
|
20
|
+
|
|
21
|
+
### The `set` function
|
|
22
|
+
|
|
23
|
+
When typing is applied, `set` supports two modes:
|
|
24
|
+
|
|
25
|
+
1. **Partial Update**: `set({ count: 1 })` — validated against `Memory`.
|
|
26
|
+
2. **Functional Update**: `set(state => { state.count++ })` — `state` is inferred as `Memory`.
|
|
27
|
+
|
|
28
|
+
### `persist(initial, config)`
|
|
29
|
+
|
|
30
|
+
Marks a section of the state for persistence in localStorage.
|
|
31
|
+
|
|
32
|
+
- `initial`: The initial value of the state.
|
|
33
|
+
- `config`: (Optional) The configuration for persistence.
|
|
34
|
+
- `key`: A custom key to use in `localStorage`.
|
|
35
|
+
|
|
36
|
+
## `onMount(callback)`
|
|
37
|
+
|
|
38
|
+
Registers a callback to be called after a component is mounted.
|
|
39
|
+
|
|
40
|
+
- `callback`: The function to be called.
|
|
41
|
+
|
|
42
|
+
## `onCleanup(callback)`
|
|
43
|
+
|
|
44
|
+
Registers a callback to be called when a component is unmounted.
|
|
45
|
+
|
|
46
|
+
- `callback`: The function to be called.
|
|
47
|
+
|
|
48
|
+
## Interaction Helper Props
|
|
49
|
+
|
|
50
|
+
Interaction helpers are optional props for common DOM interaction boilerplate. Existing event behavior is unchanged when these props are not used. See the [Interaction Helpers guide](./interaction-helpers.md) for examples and edge-case details.
|
|
51
|
+
|
|
52
|
+
### Keyboard Helpers
|
|
53
|
+
|
|
54
|
+
- `onenter(event)`: runs when `Enter` is pressed.
|
|
55
|
+
- `onescape(event)`: runs when `Escape` is pressed.
|
|
56
|
+
- `onkeys`: object map of key combinations to handlers, such as `{ 'Mod+Enter': save, Escape: close }`.
|
|
57
|
+
|
|
58
|
+
Keyboard helpers are composition-aware and ignore key handling while IME composition is active. `Mod` maps to `Meta` on macOS and `Ctrl` elsewhere.
|
|
59
|
+
|
|
60
|
+
### Debounced Input
|
|
61
|
+
|
|
62
|
+
- `debounce={number}`: debounce delay in milliseconds. Defaults to `250` when omitted or invalid.
|
|
63
|
+
- `oninputdebounced(event)`: runs once input settles for the configured delay.
|
|
64
|
+
|
|
65
|
+
Each input event clears the previous pending timer. The callback receives an event-like object with `target` and `currentTarget` set to the element, so `event.target.value` reflects the latest value when the timer fires.
|
|
66
|
+
|
|
67
|
+
### Commit Semantics
|
|
68
|
+
|
|
69
|
+
- `oncommit(event)`: runs when a field is committed by `Enter` or blur.
|
|
70
|
+
|
|
71
|
+
If pressing `Enter` causes blur, `oncommit` fires once for that user intent instead of firing for both events. The Enter path is IME-safe.
|
|
72
|
+
|
|
73
|
+
### Async Click Helpers
|
|
74
|
+
|
|
75
|
+
- `onclickasync(event)`: async click handler.
|
|
76
|
+
- `disabledwhilepending`: when true, disables the element and blocks repeated clicks while `onclickasync` is pending.
|
|
77
|
+
|
|
78
|
+
The element is re-enabled after the async handler settles, including rejection. Without `disabledwhilepending`, repeated async clicks are allowed.
|
|
79
|
+
|
|
80
|
+
### Event Modifiers
|
|
81
|
+
|
|
82
|
+
Use pipe syntax on event prop keys for explicit modifiers:
|
|
83
|
+
|
|
84
|
+
- `'onclick|prevent'`
|
|
85
|
+
- `'onclick|stop'`
|
|
86
|
+
- `'onsubmit|prevent|stop'`
|
|
87
|
+
- `'onclick|once'`
|
|
88
|
+
- `'onclick|capture'`
|
|
89
|
+
- `'onscroll|passive'`
|
|
90
|
+
|
|
91
|
+
`prevent` and `stop` run before the handler. `once`, `capture`, and `passive` are passed as listener options. Modifiers are opt-in and local to the specific handler prop.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide will walk you through the process of creating a simple "Hello, World!" application with Humn.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Humn Core
|
|
8
|
+
|
|
9
|
+
You can install Humn using npm or yarn:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install humn
|
|
13
|
+
# or
|
|
14
|
+
yarn add humn
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### The Vite Plugin
|
|
18
|
+
|
|
19
|
+
To use `.humn` files, you need to use the `vite-plugin-humn` in your Vite configuration.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install --save-dev vite-plugin-humn
|
|
23
|
+
# or
|
|
24
|
+
yarn add vite-plugin-humn -D
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// vite.config.js
|
|
29
|
+
import { defineConfig } from 'vite'
|
|
30
|
+
import humn from 'vite-plugin-humn'
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
plugins: [humn()],
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Prettier
|
|
38
|
+
|
|
39
|
+
We also have, and recommend, a Prettier Plugin which can be installed like any other
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install --save-dev prettier-plugin-humn
|
|
43
|
+
# or
|
|
44
|
+
yarn add prettier-plugin-humn -D
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
// prettier.config.js
|
|
49
|
+
import humn from 'prettier-plugin-humn'
|
|
50
|
+
|
|
51
|
+
const config = {
|
|
52
|
+
plugins: [humn],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default config
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Hello, World
|
|
59
|
+
|
|
60
|
+
Here's a simple "Hello, World!" example to get you started:
|
|
61
|
+
|
|
62
|
+
```humn
|
|
63
|
+
<script>
|
|
64
|
+
// App.humn
|
|
65
|
+
const message = 'Hello, World!'
|
|
66
|
+
</script>
|
|
67
|
+
|
|
68
|
+
<h1>{message}</h1>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
In this example, we're creating a simple component that renders an `h1` element with the text "Hello, World!".
|
|
72
|
+
|
|
73
|
+
To mount this application, you would typically have an entry point like `main.js`:
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
// main.js
|
|
77
|
+
import { mount } from 'humn'
|
|
78
|
+
import App from './App.humn'
|
|
79
|
+
|
|
80
|
+
mount(document.getElementById('root'), App)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
In this example, we're creating a simple component called `App` that renders an `h1` element with the text "Hello, World!". We then mount the `App` component to the `div` with the id `root`.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# .humn Files
|
|
2
|
+
|
|
3
|
+
Humn introduces a dedicated file format `.humn` to make writing components even more intuitive. It feels a lot like Svelte, but with Humn's unique flavour.
|
|
4
|
+
|
|
5
|
+
A `.humn` file consists of three optional sections: `<script>`, `<template>` (implicit), and `<style>`.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
```humn
|
|
10
|
+
<script>
|
|
11
|
+
// Your JavaScript logic here
|
|
12
|
+
import { cortex } from './cortex.js'
|
|
13
|
+
|
|
14
|
+
// Props are available via the `props` variable
|
|
15
|
+
const { name } = props
|
|
16
|
+
|
|
17
|
+
// Access cortex state
|
|
18
|
+
const { count } = cortex.memory
|
|
19
|
+
const { increment } = cortex.synapses
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<!-- The template is the main content of the file -->
|
|
23
|
+
<div>
|
|
24
|
+
<h1>Hello, {name}!</h1>
|
|
25
|
+
<p>Count: {count}</p>
|
|
26
|
+
<button onclick={increment}>Increment</button>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
/* Scoped CSS */
|
|
31
|
+
div {
|
|
32
|
+
padding: 20px;
|
|
33
|
+
border: 1px solid #ccc;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
h1 {
|
|
37
|
+
color: blue;
|
|
38
|
+
}
|
|
39
|
+
</style>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## The Script Section
|
|
43
|
+
|
|
44
|
+
The `<script>` block is where you define your component's logic.
|
|
45
|
+
|
|
46
|
+
- **Imports**: Import other components, cortexes, or utilities.
|
|
47
|
+
- **Props**: `props` is a magic variable available in the script scope containing the properties passed to the component.
|
|
48
|
+
- **Reactivity**: Destructuring from `Cortex` memory creates reactive subscriptions.
|
|
49
|
+
|
|
50
|
+
## The Template
|
|
51
|
+
|
|
52
|
+
Anything outside of `<script>` and `<style>` tags is considered the template.
|
|
53
|
+
|
|
54
|
+
- **JSX-like Syntax**: Use `{expression}` to embed JavaScript values.
|
|
55
|
+
- **Directives**: Use standard HTML attributes. Event listeners match the HTML format, such as `onclick`, and runtime interaction helpers such as `onenter`, `oncommit`, and `oninputdebounced` are available on base elements.
|
|
56
|
+
- **Control Flow**: Use JavaScript logic within curly braces for conditionals and loops.
|
|
57
|
+
|
|
58
|
+
```humn
|
|
59
|
+
{isLoading && <div class="loader">Loading...</div>}
|
|
60
|
+
|
|
61
|
+
<ul>
|
|
62
|
+
{items.map((item) => (
|
|
63
|
+
<li>{item.name}</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Interaction helpers can remove common event boilerplate from templates:
|
|
69
|
+
|
|
70
|
+
```humn
|
|
71
|
+
<script>
|
|
72
|
+
const save = (event) => {
|
|
73
|
+
const value = event.target.value
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<input debounce={300} oninputdebounced={save} oncommit={save} />
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
See the [Interaction Helpers guide](./interaction-helpers.md) for keyboard helpers, debounce behavior, commit semantics, async click handling, and event modifiers.
|
|
81
|
+
|
|
82
|
+
## The Style Section
|
|
83
|
+
|
|
84
|
+
The `<style>` block contains CSS that is **scoped** to the component.
|
|
85
|
+
|
|
86
|
+
- **Isolation**: Styles defined here will not leak out to other components.
|
|
87
|
+
- **No Config**: It works out of the box with the Humn compiler.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Interaction Helpers
|
|
2
|
+
|
|
3
|
+
Humn includes small runtime helper props for common UI interactions. They are regular props on base DOM elements, so existing event handling behavior is unchanged unless you opt into one of these helpers.
|
|
4
|
+
|
|
5
|
+
## Keyboard Helpers
|
|
6
|
+
|
|
7
|
+
Use keyboard helpers when you want common key handling without writing the same `keydown` branching code in every component.
|
|
8
|
+
|
|
9
|
+
```humn
|
|
10
|
+
<script>
|
|
11
|
+
const save = () => {
|
|
12
|
+
// Save the current field value.
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const close = () => {
|
|
16
|
+
// Close a dialog or clear focus.
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<input
|
|
21
|
+
onenter={save}
|
|
22
|
+
onescape={close}
|
|
23
|
+
onkeys={{ 'Mod+Enter': save, 'Shift+Escape': close }}
|
|
24
|
+
/>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- `onenter`: runs when `Enter` is pressed.
|
|
28
|
+
- `onescape`: runs when `Escape` is pressed.
|
|
29
|
+
- `onkeys`: maps key combinations to handlers.
|
|
30
|
+
|
|
31
|
+
Key combinations use `event.key` names plus optional `Ctrl`, `Meta`, `Alt`, and `Shift` modifiers. `Mod` maps to `Meta` on macOS and `Ctrl` elsewhere.
|
|
32
|
+
|
|
33
|
+
Keyboard helpers are IME-safe. They ignore keyboard events while composition is active, including native `event.isComposing` events and the period between `compositionstart` and `compositionend`.
|
|
34
|
+
|
|
35
|
+
## Debounced Input
|
|
36
|
+
|
|
37
|
+
Use `oninputdebounced` when input should wait until typing settles before running work such as filtering, validation, or search.
|
|
38
|
+
|
|
39
|
+
```humn
|
|
40
|
+
<script>
|
|
41
|
+
const search = (event) => {
|
|
42
|
+
const query = event.target.value
|
|
43
|
+
// Run the search with the latest value.
|
|
44
|
+
}
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<input debounce={300} oninputdebounced={search} />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- `debounce`: delay in milliseconds.
|
|
51
|
+
- `oninputdebounced`: runs after the delay has passed without another input event.
|
|
52
|
+
|
|
53
|
+
If `debounce` is omitted or cannot be converted to a finite number, Humn uses `250` milliseconds. Each input event clears the previous pending timer, and the callback receives an event-like object whose `target` and `currentTarget` point at the input element so the latest value is available when the timer fires.
|
|
54
|
+
|
|
55
|
+
## Commit Semantics
|
|
56
|
+
|
|
57
|
+
Use `oncommit` when a field should commit on either `Enter` or blur.
|
|
58
|
+
|
|
59
|
+
```humn
|
|
60
|
+
<script>
|
|
61
|
+
const commitName = (event) => {
|
|
62
|
+
const name = event.target.value
|
|
63
|
+
// Persist the committed value.
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<input oncommit={commitName} />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`oncommit` runs when the element receives `Enter` or loses focus. If pressing `Enter` also causes blur, Humn treats that as one user intent and fires `oncommit` once instead of double-firing. The Enter path uses the same IME safety as the keyboard helpers.
|
|
71
|
+
|
|
72
|
+
## Async Click Helpers
|
|
73
|
+
|
|
74
|
+
Use `onclickasync` for async submit-style actions, and add `disabledwhilepending` when repeated clicks should be blocked while the promise is pending.
|
|
75
|
+
|
|
76
|
+
```humn
|
|
77
|
+
<script>
|
|
78
|
+
const save = async () => {
|
|
79
|
+
await api.save()
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<button onclickasync={save} disabledwhilepending={true}>
|
|
84
|
+
Save
|
|
85
|
+
</button>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `onclickasync`: async click handler.
|
|
89
|
+
- `disabledwhilepending`: when true, disables the element and blocks repeated clicks until the async handler settles.
|
|
90
|
+
|
|
91
|
+
The disabled state is restored in a `finally` step, so the element is re-enabled whether the promise resolves or rejects. Without `disabledwhilepending`, `onclickasync` behaves like an async click listener and repeated clicks are allowed.
|
|
92
|
+
|
|
93
|
+
## Event Modifiers
|
|
94
|
+
|
|
95
|
+
Event modifiers are explicit, local opt-ins on handler prop names. They use pipe syntax on the prop key.
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
import { h } from 'humn'
|
|
99
|
+
|
|
100
|
+
export function Link() {
|
|
101
|
+
return h(
|
|
102
|
+
'a',
|
|
103
|
+
{ href: '#', 'onclick|prevent|stop': (event) => navigate(event) },
|
|
104
|
+
'Open',
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
- `prevent`: calls `event.preventDefault()` before the handler.
|
|
110
|
+
- `stop`: calls `event.stopPropagation()` before the handler.
|
|
111
|
+
- `once`: installs the listener with `{ once: true }`.
|
|
112
|
+
- `capture`: installs the listener with `{ capture: true }`.
|
|
113
|
+
- `passive`: installs the listener with `{ passive: true }`.
|
|
114
|
+
|
|
115
|
+
Modifiers apply only to the specific handler prop where they are declared. Unknown modifier names are ignored.
|
|
116
|
+
|
|
117
|
+
Pipe-named modifiers are runtime prop keys. When using `h()`, quote the key as shown above. Current `.humn` templates support the helper props in this guide, but do not parse `|` inside attribute names; use standard event handlers in templates or generate a quoted prop key through `h()` for modifier behavior.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Lifecycle Hooks
|
|
2
|
+
|
|
3
|
+
Humn provides two lifecycle hooks: `onMount` and `onCleanup`.
|
|
4
|
+
|
|
5
|
+
## Side Effects & Data Fetching (Important)
|
|
6
|
+
|
|
7
|
+
**Do not start fetches, timers, subscriptions or DOM listeners directly in a `.humn` script block.** The `<script>` block is part of the component's render logic, which means it may run multiple times (on every re-render).
|
|
8
|
+
|
|
9
|
+
Always use `onMount` for component-owned side effects to ensure they run only once when the component appears in the DOM.
|
|
10
|
+
|
|
11
|
+
```humn
|
|
12
|
+
<script>
|
|
13
|
+
import { onMount } from 'humn'
|
|
14
|
+
import { api } from '../api.js'
|
|
15
|
+
|
|
16
|
+
// ❌ BAD: This will trigger an infinite loop of fetches and re-renders!
|
|
17
|
+
// api.fetchData()
|
|
18
|
+
|
|
19
|
+
// ✅ GOOD: Runs only once when the component mounts
|
|
20
|
+
onMount(() => {
|
|
21
|
+
api.fetchData()
|
|
22
|
+
})
|
|
23
|
+
</script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
_(Note: For robust data fetching, we recommend using Cortex's built-in `resource` primitives instead of manual API calls. See the [Data Fetching guide](./state-management.md#data-fetching) for the best practices.)_
|
|
27
|
+
|
|
28
|
+
## Overview
|
|
29
|
+
|
|
30
|
+
The `onMount` hook is called after a component is mounted to the DOM. You can use it to perform any setup that you need to do.
|
|
31
|
+
|
|
32
|
+
The `onCleanup` hook is called when a component is unmounted from the DOM. You can use it to perform any cleanup that you need to do, such as removing event listeners.
|
|
33
|
+
|
|
34
|
+
Here's an example of how to use the lifecycle hooks:
|
|
35
|
+
|
|
36
|
+
```humn
|
|
37
|
+
<script>
|
|
38
|
+
// MyComponent.humn
|
|
39
|
+
import { onMount, onCleanup } from 'humn'
|
|
40
|
+
|
|
41
|
+
onMount(() => {
|
|
42
|
+
console.log('Component mounted!')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
onCleanup(() => {
|
|
46
|
+
console.log('Component unmounted!')
|
|
47
|
+
})
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<p>My component</p>
|
|
51
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Scoped CSS
|
|
2
|
+
|
|
3
|
+
In `.humn` files, you can simply use the `<style>` tag. Humn automatically scopes these styles to your component.
|
|
4
|
+
|
|
5
|
+
```humn
|
|
6
|
+
<script>
|
|
7
|
+
// MyComponent.humn
|
|
8
|
+
// ... logic
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<p>This component has scoped styles, no need for classes!</p>
|
|
12
|
+
<div>Both elements are styled by top level, unwrapped, CSS</div>
|
|
13
|
+
|
|
14
|
+
<style>
|
|
15
|
+
background: red; /* This applies to both the <p> and the <div> */
|
|
16
|
+
|
|
17
|
+
p {
|
|
18
|
+
color: blue;
|
|
19
|
+
font-size: 20px;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
```
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# State Management
|
|
2
|
+
|
|
3
|
+
Humn's state management system is called `Cortex`. It is a simple yet powerful way to manage the state of your application.
|
|
4
|
+
|
|
5
|
+
To create a `Cortex` instance, you need to provide an initial `memory` (state) and a `synapses` function. The `synapses` function receives `set` and `get` functions that you can use to interact with the state.
|
|
6
|
+
|
|
7
|
+
Here's an example of a simple counter:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// cortex.js
|
|
11
|
+
import { Cortex } from 'humn'
|
|
12
|
+
|
|
13
|
+
export const counterCortex = new Cortex({
|
|
14
|
+
memory: {
|
|
15
|
+
count: 0,
|
|
16
|
+
},
|
|
17
|
+
synapses: (set) => ({
|
|
18
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
19
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
20
|
+
}),
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```humn
|
|
25
|
+
<script>
|
|
26
|
+
// App.humn
|
|
27
|
+
import { counterCortex } from './cortex.js'
|
|
28
|
+
|
|
29
|
+
const { count } = counterCortex.memory
|
|
30
|
+
const { increment, decrement } = counterCortex.synapses
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div>
|
|
34
|
+
<h1>{count}</h1>
|
|
35
|
+
<button onclick={increment}>Increment</button>
|
|
36
|
+
<button onclick={decrement}>Decrement</button>
|
|
37
|
+
</div>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
To mount the app:
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// main.js
|
|
44
|
+
import { mount } from 'humn'
|
|
45
|
+
import App from './App.humn'
|
|
46
|
+
|
|
47
|
+
mount(document.getElementById('app'), App)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
In this example, we're creating a `Cortex` instance called `counterCortex` with an initial count of 0. We're also defining two synapses, `increment` and `decrement`, that update the count.
|
|
51
|
+
|
|
52
|
+
The `App` component gets the `count` from the `counterCortex.memory` and the `increment` and `decrement` functions from the `counterCortex.synapses`. It then renders the count and two buttons to increment and decrement the count.
|
|
53
|
+
|
|
54
|
+
## State Persistence
|
|
55
|
+
|
|
56
|
+
You can persist the state of your application to `localStorage` using the `persist` utility. This is useful for keeping the state of your application across page reloads.
|
|
57
|
+
|
|
58
|
+
To use it, wrap the value you want to persist with the `persist` function.
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
import { Cortex, persist } from 'humn'
|
|
62
|
+
|
|
63
|
+
const todoCortex = new Cortex({
|
|
64
|
+
memory: {
|
|
65
|
+
items: persist([
|
|
66
|
+
{ id: 1, text: 'Create Humn Library', done: true },
|
|
67
|
+
{ id: 2, text: 'Write first app', done: false },
|
|
68
|
+
]),
|
|
69
|
+
inputValue: '',
|
|
70
|
+
errorMessage: '',
|
|
71
|
+
isLoading: false,
|
|
72
|
+
},
|
|
73
|
+
// ...
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
By default, the key used in `localStorage` will be the same as the key in the `memory` object. In the example above, the key will be `items`.
|
|
78
|
+
|
|
79
|
+
You can also provide a custom key by passing a configuration object to the `persist` function:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
const todoCortex = new Cortex({
|
|
83
|
+
memory: {
|
|
84
|
+
items: persist(
|
|
85
|
+
[
|
|
86
|
+
{ id: 1, text: 'Create Humn Library', done: true },
|
|
87
|
+
{ id: 2, text: 'Write first app', done: false },
|
|
88
|
+
],
|
|
89
|
+
{ key: 'my-custom-key' },
|
|
90
|
+
),
|
|
91
|
+
// ...
|
|
92
|
+
},
|
|
93
|
+
// ...
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Type Safety (TS & JSDoc)
|
|
98
|
+
|
|
99
|
+
Cortex supports strong typing out of the box. By defining your Memory and Synapse types, you ensure that your `set` updates are valid and your components receive the correct data types.
|
|
100
|
+
|
|
101
|
+
Cortex automatically handles the complexity of unwrapping `persist()` values in your types, so your API remains clean.
|
|
102
|
+
|
|
103
|
+
> For a detailed guide on how to type your cortex using TypeScript or JSDoc, see the **[TypeScript Support](./typescript.md)** guide.
|
|
104
|
+
|
|
105
|
+
## Data Fetching
|
|
106
|
+
|
|
107
|
+
Do not start fetches directly in a `.humn` script block. The script block runs during render and may run many times.
|
|
108
|
+
|
|
109
|
+
Use `onMount` to trigger component-owned loading, and use a Cortex resource synapse to own the async workflow.
|
|
110
|
+
|
|
111
|
+
Components should read resource state and render the right UI. They should not manage request lifecycle manually.
|
|
112
|
+
|
|
113
|
+
### The `resource` primitive
|
|
114
|
+
|
|
115
|
+
Humn comes with built-in primitives to handle server state, similar to React Query or Solid's resources: `resource` and `resourceSynapse`.
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
// cortexes/crmCortex.js
|
|
119
|
+
import { Cortex, resource, resourceSynapse } from 'humn'
|
|
120
|
+
import { api } from '../api/api.js'
|
|
121
|
+
|
|
122
|
+
export const crmCortex = new Cortex({
|
|
123
|
+
memory: {
|
|
124
|
+
// Automatically creates { data: [], status: 'idle', loading: false, error: null }
|
|
125
|
+
accounts: resource([]),
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
synapses: (set, get) => ({
|
|
129
|
+
// Automatically manages loading/error states, race conditions, and deduplication
|
|
130
|
+
loadAccounts: resourceSynapse(set, get, 'accounts', async (params) => {
|
|
131
|
+
return api.accounts.list(params)
|
|
132
|
+
}),
|
|
133
|
+
}),
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Usage in a component:
|
|
138
|
+
|
|
139
|
+
```humn
|
|
140
|
+
<script>
|
|
141
|
+
import { onMount } from 'humn'
|
|
142
|
+
import { crmCortex } from '../cortexes/crmCortex.js'
|
|
143
|
+
|
|
144
|
+
const { accounts } = crmCortex.memory
|
|
145
|
+
const { loadAccounts } = crmCortex.synapses
|
|
146
|
+
|
|
147
|
+
// Trigger the fetch safely on mount
|
|
148
|
+
onMount(() => loadAccounts())
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<div>
|
|
152
|
+
{#if accounts.loading}
|
|
153
|
+
<p>Loading accounts...</p>
|
|
154
|
+
{/if}
|
|
155
|
+
|
|
156
|
+
{#if accounts.error}
|
|
157
|
+
<p>Could not load accounts: {accounts.error.message}</p>
|
|
158
|
+
{/if}
|
|
159
|
+
|
|
160
|
+
{#if accounts.status === 'success'}
|
|
161
|
+
<ul>
|
|
162
|
+
{accounts.data.map(acc => <li>{acc.name}</li>)}
|
|
163
|
+
</ul>
|
|
164
|
+
{/if}
|
|
165
|
+
</div>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`resourceSynapse` supports fetching options like `force`:
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
crmCortex.synapses.loadAccounts(null, { force: true })
|
|
172
|
+
```
|