|
1 | 1 | // SPDX-License-Identifier: MPL-2.0 |
2 | | -// Example: Using SafeDOM for formally verified DOM mounting |
3 | | -// Translated from ReasonML/ReScript to AffineScript |
4 | | - |
5 | | -use SafeDOM |
6 | | - |
7 | | -// Define callback types for SafeDOM functions |
8 | | -type MountCallbacks = { |
9 | | - on_success: fn(Element) -> () / IO, |
10 | | - on_error: fn(String) -> () / IO |
| 2 | +// SafeDOMExample.affine — formally-verified DOM mounting (aspirational). |
| 3 | +// |
| 4 | +// This example shows the *shape* of SafeDOM consumer code in current |
| 5 | +// AffineScript syntax. The `SafeDOM` stdlib surface it references |
| 6 | +// (`mount_safe`, `mount_when_ready`, `mount_batch`, |
| 7 | +// `proven_selector_validate`, `proven_html_validate`, `mount`) is the |
| 8 | +// target of `affinescript#56` (DOM+Pixi binding survey) and does not |
| 9 | +// yet exist in the published stdlib. The file is therefore |
| 10 | +// parse-checked but not type-checked end-to-end until #56 lands the |
| 11 | +// bindings; `affinescript check` reports `Resolve.UndefinedModule |
| 12 | +// SafeDOM` which is expected. |
| 13 | +// |
| 14 | +// Previous versions of this file (estate-wide, 5 dialect variants) |
| 15 | +// pre-dated ADR-014 (qualified paths), ADR-016 (effect rows), and the |
| 16 | +// `#{`-record-literal sigil (ADR-215). They were retired in favour of |
| 17 | +// this canonical via the gitbot-fleet#208 sweep (2026-05-26). |
| 18 | + |
| 19 | +module SafeDOMExample; |
| 20 | + |
| 21 | +use prelude::{Option, Some, None, Result, Ok, Err}; |
| 22 | + |
| 23 | +// `Element` and friends are nominal extern types for now — the real |
| 24 | +// shape lands with affinescript#56. |
| 25 | +extern type Element; |
| 26 | +extern type Selector; |
| 27 | +extern type ValidHTML; |
| 28 | + |
| 29 | +// Single-mount status, lifted from the host into a typed tag union. |
| 30 | +enum MountStatus { |
| 31 | + Mounted(Element), |
| 32 | + MountPointNotFound(String), |
| 33 | + InvalidSelector(String), |
| 34 | + InvalidHTML(String) |
11 | 35 | } |
12 | 36 |
|
13 | | -type MountResult = Ok(Array[Element]) | Error(String) |
| 37 | +// Batch-mount result. |
| 38 | +enum MountResult { |
| 39 | + Mounted([Element]), |
| 40 | + Failed(String) |
| 41 | +} |
14 | 42 |
|
15 | | -type MountStatus = Mounted(Element) | MountPointNotFound(String) | InvalidSelector(String) | InvalidHTML(String) |
| 43 | +// Spec for one element in a batch mount. |
| 44 | +struct MountSpec { |
| 45 | + selector: String, |
| 46 | + html: String |
| 47 | +} |
16 | 48 |
|
17 | | -// Example 1: Basic mounting with error handling |
18 | | -fn mount_app() -> () / IO { |
| 49 | +// SafeDOM's host-side surface, all IO-effecting. Callbacks are passed |
| 50 | +// as separate parameters (rather than a `MountCallbacks` record) |
| 51 | +// because fn-typed struct fields are not currently parser-supported. |
| 52 | +extern fn mount_safe( |
| 53 | + selector: ref String, |
| 54 | + html: ref String, |
| 55 | + on_success: fn(Element) -> (), |
| 56 | + on_error: fn(String) -> (), |
| 57 | +) -{IO}-> (); |
| 58 | + |
| 59 | +extern fn mount_when_ready( |
| 60 | + selector: ref String, |
| 61 | + html: ref String, |
| 62 | + on_success: fn(Element) -> (), |
| 63 | + on_error: fn(String) -> (), |
| 64 | +) -{IO}-> (); |
| 65 | + |
| 66 | +extern fn mount_batch(specs: ref [MountSpec]) -{IO}-> MountResult; |
| 67 | + |
| 68 | +extern fn proven_selector_validate(s: ref String) -{IO}-> Result<Selector, String>; |
| 69 | +extern fn proven_html_validate(s: ref String) -{IO}-> Result<ValidHTML, String>; |
| 70 | +extern fn mount(sel: ref Selector, html: ref ValidHTML) -{IO}-> MountStatus; |
| 71 | + |
| 72 | +extern fn array_for_each(xs: ref [Element], f: fn(Element) -> ()) -{IO}-> (); |
| 73 | +extern fn array_len(xs: ref [Element]) -> Int; |
| 74 | + |
| 75 | +// Example 1 — basic mount with success/error branches. |
| 76 | +pub fn mount_app() -{IO}-> () { |
19 | 77 | mount_safe( |
20 | 78 | "#app", |
21 | 79 | "<div><h1>Hello, World!</h1><p>Mounted safely with proofs.</p></div>", |
22 | | - { |
23 | | - on_success: fn(el) -> () / IO { IO.println("✓ App mounted successfully!"); IO.println("Element: ", el) }, |
24 | | - on_error: fn(err) -> () / IO { IO.eprintln("✗ Mount failed: ", err) } |
25 | | - } |
26 | | - ) |
| 80 | + fn(el) -> () { Console::log("App mounted successfully"); }, |
| 81 | + fn(err) -> () { Console::error("Mount failed: " ++ err); }, |
| 82 | + ); |
27 | 83 | } |
28 | 84 |
|
29 | | -// Example 2: Wait for DOM ready before mounting |
30 | | -fn mount_when_dom_ready() -> () / IO { |
| 85 | +// Example 2 — defer until DOM ready. |
| 86 | +pub fn mount_when_dom_ready() -{IO}-> () { |
31 | 87 | mount_when_ready( |
32 | 88 | "#app", |
33 | 89 | "<div class='container'><h1>App Title</h1></div>", |
34 | | - { |
35 | | - on_success: fn(_) -> () / IO { IO.println("✓ Mounted after DOM ready") }, |
36 | | - on_error: fn(err) -> () / IO { IO.eprintln("✗ Failed: ", err) } |
37 | | - } |
38 | | - ) |
| 90 | + fn(_el) -> () { Console::log("Mounted after DOM ready"); }, |
| 91 | + fn(err) -> () { Console::error("Failed: " ++ err); }, |
| 92 | + ); |
39 | 93 | } |
40 | 94 |
|
41 | | -// Example 3: Batch mounting (atomic - all or nothing) |
42 | | -fn mount_multiple() -> () / IO { |
| 95 | +// Example 3 — atomic batch mount. |
| 96 | +pub fn mount_multiple() -{IO}-> () { |
43 | 97 | let specs = [ |
44 | | - {selector: "#header", html: "<header><h1>Site Title</h1></header>"}, |
45 | | - {selector: "#nav", html: "<nav><a href='/'>Home</a></nav>"}, |
46 | | - {selector: "#main", html: "<main><p>Content here</p></main>"}, |
47 | | - {selector: "#footer", html: "<footer>© 2026</footer>"} |
48 | | - ] |
| 98 | + MountSpec #{ selector: "#header", html: "<header><h1>Site Title</h1></header>" }, |
| 99 | + MountSpec #{ selector: "#nav", html: "<nav><a href='/'>Home</a></nav>" }, |
| 100 | + MountSpec #{ selector: "#main", html: "<main><p>Content here</p></main>" }, |
| 101 | + MountSpec #{ selector: "#footer", html: "<footer>2026</footer>" }, |
| 102 | + ]; |
49 | 103 |
|
50 | 104 | match mount_batch(specs) { |
51 | | - Ok(elements) => { |
52 | | - IO.println("✓ Successfully mounted ", array_len(elements), " elements") |
53 | | - array_for_each(elements, fn(el) -> () / IO { IO.println(" -", el) }) |
54 | | - } |
55 | | - Error(err) => { |
56 | | - IO.eprintln("✗ Batch mount failed: ", err) |
57 | | - IO.eprintln(" (None were mounted - atomic operation)") |
| 105 | + Mounted(elements) => { |
| 106 | + Console::log("Batch mount succeeded"); |
| 107 | + array_for_each(elements, fn(_el) -> () { Console::log(" element"); }); |
| 108 | + }, |
| 109 | + Failed(err) => { |
| 110 | + Console::error("Batch mount failed (atomic — none mounted): " ++ err); |
58 | 111 | } |
59 | 112 | } |
60 | 113 | } |
61 | 114 |
|
62 | | -// Example 4: Explicit validation before mounting |
63 | | -fn mount_with_validation() -> () / IO { |
64 | | - // Validate selector first |
| 115 | +// Example 4 — explicit two-stage validation before mounting. |
| 116 | +pub fn mount_with_validation() -{IO}-> () { |
65 | 117 | match proven_selector_validate("#my-app") { |
66 | | - Error(e) => IO.eprintln("Invalid selector: ", e), |
67 | | - Ok(valid_selector) => { |
68 | | - // Validate HTML |
69 | | - match proven_html_validate("<div>Content</div>") { |
70 | | - Error(e) => IO.eprintln("Invalid HTML: ", e), |
71 | | - Ok(valid_html) => { |
72 | | - // Now mount with proven safety |
73 | | - match mount(valid_selector, valid_html) { |
74 | | - Mounted(el) => IO.println("✓ Mounted with validated inputs: ", el), |
75 | | - MountPointNotFound(s) => IO.eprintln("✗ Element not found: ", s), |
76 | | - InvalidSelector(_) => IO.eprintln("Impossible - already validated"), |
77 | | - InvalidHTML(_) => IO.eprintln("Impossible - already validated") |
78 | | - } |
79 | | - } |
80 | | - } |
81 | | - } |
82 | | - } |
83 | | -} |
84 | | - |
85 | | -// Example 5: Integration with TEA-like pattern |
86 | | -struct MyApp { |
87 | | - message: String |
88 | | -} |
89 | | - |
90 | | -type MyMsg = NoOp |
91 | | - |
92 | | -fn my_app_init() -> MyApp { |
93 | | - MyApp { message: "Hello from TEA" } |
94 | | -} |
95 | | - |
96 | | -fn my_app_update(model: ref MyApp, _msg: MyMsg) -> MyApp { |
97 | | - model |
98 | | -} |
99 | | - |
100 | | -fn my_app_view(model: ref MyApp) -> String { |
101 | | - "<div><h1>" ++ model.message ++ "</h1></div>" |
102 | | -} |
103 | | - |
104 | | -fn mount_tea_app() -> () / IO { |
105 | | - let model = my_app_init() |
106 | | - let html = my_app_view(ref model) |
107 | | - |
108 | | - mount_when_ready( |
109 | | - "#tea-app", |
110 | | - html, |
111 | | - { |
112 | | - on_success: fn(el) -> () / IO { |
113 | | - IO.println("✓ TEA app mounted") |
114 | | - // Set up event handlers, subscriptions here |
| 118 | + Err(e) => Console::error("Invalid selector: " ++ e), |
| 119 | + Ok(valid_selector) => match proven_html_validate("<div>Content</div>") { |
| 120 | + Err(e) => Console::error("Invalid HTML: " ++ e), |
| 121 | + Ok(valid_html) => match mount(valid_selector, valid_html) { |
| 122 | + Mounted(_el) => Console::log("Mounted with validated inputs"), |
| 123 | + MountPointNotFound(s) => Console::error("Element not found: " ++ s), |
| 124 | + InvalidSelector(_) => Console::error("impossible — already validated"), |
| 125 | + InvalidHTML(_) => Console::error("impossible — already validated"), |
115 | 126 | }, |
116 | | - on_error: fn(err) -> () / IO { IO.eprintln("✗ TEA mount failed: ", err) } |
117 | | - } |
118 | | - ) |
119 | | -} |
120 | | - |
121 | | -// Helper functions for array operations |
122 | | -fn array_len(arr: ref Array[Element]) -> Nat { |
123 | | - 0 // Placeholder - actual implementation depends on SafeDOM module |
124 | | -} |
125 | | - |
126 | | -fn array_for_each(arr: ref Array[Element], f: fn(Element) -> () / IO) -> () / IO { |
127 | | - // Placeholder implementation |
128 | | - () |
129 | | -} |
130 | | - |
131 | | -// Entry point |
132 | | -fn main() -> () / IO { |
133 | | - IO.println("SafeDOM Examples") |
134 | | - IO.println("================\n") |
135 | | - |
136 | | - // Choose which example to run |
137 | | - mount_when_dom_ready() // Run on DOM ready |
| 127 | + }, |
| 128 | + } |
138 | 129 | } |
0 commit comments