Skip to content

Commit 2816289

Browse files
Jonathan D.A. Jewellclaude
andcommitted
fix: Restore original README content
The previous RSR standardization commit accidentally replaced the full README content with a minimal template. This restores the original project documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5c11759 commit 2816289

1 file changed

Lines changed: 272 additions & 42 deletions

File tree

README.adoc

Lines changed: 272 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,306 @@
1-
= Rescript-TEA
2-
Jonathan D.A. Jewell <jonathan.jewell@gmail.com>
1+
// SPDX-License-Identifier: MIT AND Palimpsest-0.8
2+
// SPDX-FileCopyrightText: 2024 Jonathan D.A. Jewell
3+
= rescript-tea
34
:toc: macro
5+
:toc-title: Contents
6+
:toclevels: 3
47
:icons: font
58
:source-highlighter: rouge
69
:experimental:
7-
:url-github: https://github.com/hyperpolymath/Rescript-TEA
8-
:url-gitlab: https://gitlab.com/hyperpolymath/Rescript-TEA
9-
:url-bitbucket: https://bitbucket.org/hyperpolymath/Rescript-TEA
10-
:url-codeberg: https://codeberg.org/hyperpolymath/Rescript-TEA
1110

12-
ReScript implementation of The Elm Architecture
13-
14-
image:https://img.shields.io/badge/RSR-Certified-gold[RSR Certified]
15-
image:https://img.shields.io/badge/License-AGPL%20v3-blue[License]
11+
The Elm Architecture (TEA) for ReScript, providing a principled way to build web applications with guaranteed state consistency, exhaustive event handling, and time-travel debugging.
1612

1713
toc::[]
1814

1915
== Overview
2016

21-
Rescript-TEA is part of the link:https://rhodium.sh[Rhodium Standard] (RSR) ecosystem.
17+
rescript-tea implements The Elm Architecture pattern for ReScript with React integration. It provides:
2218

23-
Domain: *software-development*
19+
* *Single source of truth* - One model, one update pathway
20+
* *Pure updates* - Easy to test, easy to reason about
21+
* *Type-safe* - Compiler catches missing message handlers
22+
* *React integration* - Uses React for rendering, compatible with existing components
23+
* *Commands & Subscriptions* - Declarative side effects
2424

2525
== Installation
2626

2727
[source,bash]
2828
----
29-
# Clone from GitHub (primary)
30-
git clone {url-github}
29+
npm install rescript-tea
30+
----
31+
32+
Add to your `rescript.json`:
33+
34+
[source,json]
35+
----
36+
{
37+
"bs-dependencies": ["rescript-tea", "@rescript/react"]
38+
}
39+
----
40+
41+
== Quick Start
42+
43+
[source,rescript]
44+
----
45+
open Tea
46+
47+
// 1. Define your model
48+
type model = {count: int}
49+
50+
// 2. Define your messages
51+
type msg =
52+
| Increment
53+
| Decrement
54+
55+
// 3. Initialize your app
56+
let init = () => ({count: 0}, Cmd.none)
57+
58+
// 4. Handle updates
59+
let update = (msg, model) => {
60+
switch msg {
61+
| Increment => ({count: model.count + 1}, Cmd.none)
62+
| Decrement => ({count: model.count - 1}, Cmd.none)
63+
}
64+
}
65+
66+
// 5. Render your view (with dispatch for event handling)
67+
let view = (model, dispatch) => {
68+
<div>
69+
<button onClick={_ => dispatch(Decrement)}> {React.string("-")} </button>
70+
<span> {model.count->Belt.Int.toString->React.string} </span>
71+
<button onClick={_ => dispatch(Increment)}> {React.string("+")} </button>
72+
</div>
73+
}
74+
75+
// 6. Declare subscriptions (none for this simple example)
76+
let subscriptions = _model => Sub.none
77+
78+
// 7. Create the app component
79+
module App = MakeWithDispatch({
80+
type model = model
81+
type msg = msg
82+
type flags = unit
83+
let init = _ => init()
84+
let update = update
85+
let view = view
86+
let subscriptions = subscriptions
87+
})
88+
89+
// 8. Mount it
90+
switch ReactDOM.querySelector("#root") {
91+
| Some(root) => {
92+
let rootElement = ReactDOM.Client.createRoot(root)
93+
rootElement->ReactDOM.Client.Root.render(<App flags=() />)
94+
}
95+
| None => ()
96+
}
97+
----
98+
99+
== Core Concepts
100+
101+
=== Model
102+
103+
Your application state is a single value (typically a record):
104+
105+
[source,rescript]
106+
----
107+
type model = {
108+
user: option<user>,
109+
posts: array<post>,
110+
loading: bool,
111+
}
112+
----
113+
114+
=== Messages
115+
116+
All possible events are variants of a single type:
117+
118+
[source,rescript]
119+
----
120+
type msg =
121+
| FetchPosts
122+
| GotPosts(result<array<post>, error>)
123+
| SelectPost(int)
124+
| Logout
125+
----
126+
127+
=== Update
128+
129+
A pure function that handles messages:
130+
131+
[source,rescript]
132+
----
133+
let update = (msg, model) => {
134+
switch msg {
135+
| FetchPosts => (model, fetchPostsCmd)
136+
| GotPosts(Ok(posts)) => ({...model, posts, loading: false}, Cmd.none)
137+
| GotPosts(Error(_)) => ({...model, loading: false}, Cmd.none)
138+
| SelectPost(id) => ({...model, selectedId: Some(id)}, Cmd.none)
139+
| Logout => ({...model, user: None}, Cmd.none)
140+
}
141+
}
142+
----
143+
144+
=== Commands
145+
146+
Descriptions of side effects to perform:
147+
148+
[source,rescript]
149+
----
150+
// Do nothing
151+
Cmd.none
152+
153+
// Batch multiple commands
154+
Cmd.batch([cmd1, cmd2, cmd3])
155+
156+
// Perform an async operation
157+
Cmd.perform(() => fetchUser("alice"), user => GotUser(user))
158+
159+
// Handle potential failures
160+
Cmd.attempt(() => fetchUser("alice"), result => GotUser(result))
161+
----
162+
163+
=== Subscriptions
164+
165+
Declarations of external event sources:
166+
167+
[source,rescript]
168+
----
169+
let subscriptions = model => {
170+
if model.timerRunning {
171+
Sub.Time.every(1000, time => Tick(time))
172+
} else {
173+
Sub.none
174+
}
175+
}
176+
----
177+
178+
Built-in subscriptions:
179+
180+
* `Sub.Time.every(ms, toMsg)` - Timer
181+
* `Sub.Keyboard.downs(toMsg)` - Key down events
182+
* `Sub.Keyboard.ups(toMsg)` - Key up events
183+
* `Sub.Mouse.clicks(toMsg)` - Mouse clicks
184+
* `Sub.Mouse.moves(toMsg)` - Mouse movement
185+
* `Sub.Window.resizes(toMsg)` - Window resize
186+
187+
== Modules
188+
189+
=== Tea.Cmd
190+
191+
Commands for side effects.
192+
193+
=== Tea.Sub
194+
195+
Subscriptions for external events.
196+
197+
=== Tea.Json
198+
199+
Type-safe JSON decoding:
200+
201+
[source,rescript]
202+
----
203+
open Tea.Json
204+
205+
let userDecoder = map3(
206+
(id, name, email) => {id, name, email},
207+
field("id", int),
208+
field("name", string),
209+
field("email", string),
210+
)
211+
212+
// Use it
213+
switch decodeString(userDecoder, jsonString) {
214+
| Ok(user) => // use user
215+
| Error(err) => Console.log(errorToString(err))
216+
}
217+
----
218+
219+
=== Tea.Html
220+
221+
Optional HTML helpers (you can also use JSX directly):
222+
223+
[source,rescript]
224+
----
225+
open Tea.Html
226+
227+
let view = model => {
228+
div([className("container")], [
229+
h1([], [text("Hello")]),
230+
button([onClick(Increment)], [text("+")]),
231+
])
232+
}
233+
----
234+
235+
=== Tea.Test
236+
237+
Testing utilities:
238+
239+
[source,rescript]
240+
----
241+
// Simulate a sequence of messages
242+
let finalModel = Tea.Test.simulate(
243+
~init,
244+
~update,
245+
~msgs=[Increment, Increment, Decrement],
246+
)
31247
32-
# Or from mirrors
33-
git clone {url-gitlab}
34-
git clone {url-codeberg}
248+
// Collect commands for inspection
249+
let cmds = Tea.Test.collectCmds(
250+
~init,
251+
~update,
252+
~msgs=[FetchUser("alice")],
253+
)
35254
----
36255

37-
== RSR Stack
256+
== Examples
257+
258+
See the `examples/` directory:
259+
260+
* `01_counter/` - Basic counter
261+
262+
== Architecture: React Hooks Integration
263+
264+
rescript-tea uses React hooks internally to implement the TEA runtime:
38265

39-
This project follows RSR conventions:
266+
* *useState* - Stores the model state
267+
* *useRef* - Maintains cleanup functions for subscriptions
268+
* *useEffect* - Executes commands and manages subscription lifecycle
269+
* *useCallback* - Memoizes the dispatch function
40270

41-
* ✅ ReScript for frontend/logic
42-
* ✅ Deno for JS runtime
43-
* ✅ WASM for performance-critical code
44-
* ✅ Rust/OCaml/Haskell for systems/proofs
45-
* ✅ Guile/Scheme for configuration
46-
* ❌ No TypeScript
47-
* ❌ No Go
48-
* ❌ No npm
271+
This provides a seamless integration with React while maintaining TEA's guarantees.
49272

50-
== Mirrors
273+
== Why TEA?
51274

52275
[cols="1,2"]
53276
|===
54-
| Platform | URL
277+
|Bug Type |How TEA Prevents It
55278

56-
| GitHub (primary) | {url-github}
57-
| GitLab | {url-gitlab}
58-
| Bitbucket | {url-bitbucket}
59-
| Codeberg | {url-codeberg}
279+
|Stale UI
280+
|View is pure function of Model
281+
282+
|Forgotten state updates
283+
|View recomputes entirely
284+
285+
|Unhandled events
286+
|Variant types = compiler warnings
287+
288+
|Race conditions
289+
|Single update pathway
290+
291+
|Untestable code
292+
|Pure functions = easy testing
60293
|===
61294

62295
== License
63296

64-
Licensed under AGPL-3.0-or-later OR LicenseRef-Palimpsest-0.5.
65-
66-
See link:LICENSE[LICENSE] for details.
297+
This project is dual-licensed under:
67298

68-
== Contributing
299+
* link:LICENSE.txt[MIT License]
300+
* link:LICENSE-PALIMPSEST.txt[Palimpsest License v0.8]
69301

70-
See link:CONTRIBUTING.adoc[CONTRIBUTING.adoc].
302+
See link:CONTRIBUTING.adoc[CONTRIBUTING] for details on how to contribute.
71303

72-
== Metadata
304+
== RSR Compliance
73305

74-
* Domain: software-development
75-
* Framework: RSR (Rhodium Standard Repository)
76-
* Dublin Core: link:.well-known/dc.xml[.well-known/dc.xml]
306+
This repository follows the https://github.com/Hyperpolymath/rhodium-standard-repositories[Rhodium Standard Repositories] specification.

0 commit comments

Comments
 (0)