flarp 2.0.1 → 2.5.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/README.md +259 -251
- package/package.json +2 -2
- package/src/components/index.js +3 -1
- package/src/dom/find.js +5 -5
- package/src/index.js +24 -12
- package/src/sync/Store.js +633 -0
- package/src/sync/Sync.js +369 -0
- package/src/sync/index.js +3 -1
- package/src/xml/Node.js +8 -1
- package/src/components/FStore.js +0 -332
package/README.md
CHANGED
|
@@ -1,145 +1,203 @@
|
|
|
1
1
|
# Flarp
|
|
2
2
|
|
|
3
|
-
**DOM-native XML State Management
|
|
3
|
+
**DOM-native XML State Management with Multi-Master Sync**
|
|
4
4
|
|
|
5
|
+
Flarp treats XML as state, the DOM as the runtime, and all browser tabs as equal peers. State survives refresh, syncs across tabs, and conflicts are resolved automatically.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<f-store key="myapp" autosave="500">
|
|
9
|
+
<user>
|
|
10
|
+
<n>Alice</n>
|
|
11
|
+
<role>admin</role>
|
|
12
|
+
</user>
|
|
13
|
+
</f-store>
|
|
14
|
+
|
|
15
|
+
<f-text path="user.name"></f-text>
|
|
16
|
+
<f-field path="user.role"></f-field>
|
|
5
17
|
```
|
|
6
|
-
|
|
18
|
+
|
|
19
|
+
## Core Principles
|
|
20
|
+
|
|
21
|
+
1. **XML is State** — Human-readable, self-describing, works with AI
|
|
22
|
+
2. **DOM is Runtime** — No virtual DOM, no compile step, just the browser
|
|
23
|
+
3. **Multi-Master Sync** — All tabs are equal peers, no single source of truth
|
|
24
|
+
4. **Eventually Consistent** — Conflicts resolved deterministically, all tabs agree
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!DOCTYPE html>
|
|
32
|
+
<html>
|
|
33
|
+
<head>
|
|
34
|
+
<script type="module" src="./src/index.js"></script>
|
|
35
|
+
</head>
|
|
36
|
+
<body>
|
|
37
|
+
<f-store key="demo" autosave="500">
|
|
38
|
+
<counter>
|
|
39
|
+
<value>0</value>
|
|
40
|
+
</counter>
|
|
41
|
+
</f-store>
|
|
42
|
+
|
|
43
|
+
<p>Count: <f-text path="counter.value"></f-text></p>
|
|
44
|
+
<button id="inc">+1</button>
|
|
45
|
+
|
|
46
|
+
<script type="module">
|
|
47
|
+
const store = document.querySelector('f-store');
|
|
48
|
+
|
|
49
|
+
store.state.when('ready', () => {
|
|
50
|
+
document.getElementById('inc').onclick = () => {
|
|
51
|
+
const val = store.at('counter.value');
|
|
52
|
+
val.value = String(Number(val.value) + 1);
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
</body>
|
|
57
|
+
</html>
|
|
7
58
|
```
|
|
8
59
|
|
|
9
60
|
---
|
|
10
61
|
|
|
11
|
-
##
|
|
62
|
+
## Revision System (CouchDB-style)
|
|
12
63
|
|
|
13
|
-
|
|
64
|
+
Every node has two special attributes:
|
|
14
65
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **Web Components for UI** — Standard, portable, composable
|
|
18
|
-
- **DOM as runtime** — MutationObserver does the scheduling
|
|
66
|
+
- **uuid** — Stable identity (never changes)
|
|
67
|
+
- **rev** — Version string: `{number}-{hash}`
|
|
19
68
|
|
|
20
|
-
|
|
69
|
+
```xml
|
|
70
|
+
<user uuid="abc123" rev="3-f7a8b9c0">
|
|
71
|
+
<n>Alice</n>
|
|
72
|
+
</user>
|
|
73
|
+
```
|
|
21
74
|
|
|
22
|
-
|
|
23
|
-
<script type="module">
|
|
24
|
-
import 'https://unpkg.com/flarp/src/index.js';
|
|
25
|
-
</script>
|
|
75
|
+
### Why This Format?
|
|
26
76
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<role>Developer</role>
|
|
31
|
-
</user>
|
|
32
|
-
</f-state>
|
|
77
|
+
The revision number enables quick comparison (higher wins). The hash enables conflict detection (same number, different hash = conflict) and deterministic tie-breaking (alphabetically first hash wins).
|
|
78
|
+
|
|
79
|
+
### Conflict Resolution
|
|
33
80
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
81
|
+
When two tabs write simultaneously:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Tab A writes: rev="3-aaa111"
|
|
85
|
+
Tab B writes: rev="3-bbb222"
|
|
38
86
|
```
|
|
39
87
|
|
|
40
|
-
|
|
88
|
+
1. Both writes succeed (no data loss!)
|
|
89
|
+
2. Conflict detected (same number: 3)
|
|
90
|
+
3. Tab A wins (aaa < bbb alphabetically)
|
|
91
|
+
4. All tabs independently agree on Tab A
|
|
41
92
|
|
|
42
|
-
|
|
93
|
+
```javascript
|
|
94
|
+
// Listen for conflicts
|
|
95
|
+
store.onConflict(({ uuid, localRev, remoteRev, winner }) => {
|
|
96
|
+
console.log(`Conflict on ${uuid}: ${winner} won`);
|
|
97
|
+
});
|
|
98
|
+
```
|
|
43
99
|
|
|
44
|
-
|
|
100
|
+
---
|
|
45
101
|
|
|
46
|
-
|
|
102
|
+
## Cross-Tab Synchronization
|
|
47
103
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
104
|
+
When you set `key="..."`, Flarp automatically:
|
|
105
|
+
|
|
106
|
+
1. Persists to localStorage
|
|
107
|
+
2. Syncs across browser tabs via BroadcastChannel
|
|
108
|
+
3. Requests catch-up sync on connect
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<f-store key="myapp" autosave="500">
|
|
112
|
+
<!-- Shared across all tabs with key="myapp" -->
|
|
113
|
+
</f-store>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### How It Works
|
|
117
|
+
|
|
118
|
+
1. Each tab broadcasts changes to a shared channel
|
|
119
|
+
2. Other tabs receive and apply changes (if revision wins)
|
|
120
|
+
3. Changes include: `{ uuid, rev, data: "<xml>..." }`
|
|
121
|
+
4. On startup, tabs request missed changes from peers
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// Access the sync instance
|
|
125
|
+
store.sync.onChange(change => {
|
|
126
|
+
console.log('Remote change:', change);
|
|
53
127
|
});
|
|
54
128
|
|
|
55
|
-
|
|
56
|
-
|
|
129
|
+
store.sync.onConflict(info => {
|
|
130
|
+
console.log('Conflict:', info);
|
|
131
|
+
});
|
|
57
132
|
```
|
|
58
133
|
|
|
59
|
-
|
|
134
|
+
---
|
|
60
135
|
|
|
61
|
-
|
|
62
|
-
|
|
136
|
+
## External Updates (Server Push)
|
|
137
|
+
|
|
138
|
+
Apply updates from WebSocket, HTTP push, or postMessage:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// From WebSocket
|
|
142
|
+
ws.onmessage = e => {
|
|
143
|
+
const { applied, conflict, winner } = store.applyRemote(e.data);
|
|
144
|
+
console.log(`Applied: ${applied}, Winner: ${winner}`);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// From postMessage
|
|
148
|
+
window.onmessage = e => {
|
|
149
|
+
if (e.data.type === 'node-update') {
|
|
150
|
+
store.applyRemote(e.data.xml);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
63
153
|
```
|
|
64
154
|
|
|
65
|
-
|
|
155
|
+
The XML must include uuid and rev:
|
|
66
156
|
|
|
67
|
-
```
|
|
68
|
-
<
|
|
69
|
-
import 'https://unpkg.com/flarp/src/index.js';
|
|
70
|
-
</script>
|
|
157
|
+
```xml
|
|
158
|
+
<n uuid="abc123" rev="5-xyz789">New Value</n>
|
|
71
159
|
```
|
|
72
160
|
|
|
73
161
|
---
|
|
74
162
|
|
|
75
163
|
## Components
|
|
76
164
|
|
|
77
|
-
### `<f-
|
|
165
|
+
### `<f-store>` — State Container
|
|
78
166
|
|
|
79
167
|
```html
|
|
80
|
-
<f-
|
|
81
|
-
key="myapp"
|
|
82
|
-
autosave="500"
|
|
83
|
-
|
|
168
|
+
<f-store
|
|
169
|
+
key="myapp" <!-- Storage/sync key -->
|
|
170
|
+
autosave="500" <!-- Debounced save (ms) -->
|
|
171
|
+
sync="true" <!-- Enable cross-tab sync -->
|
|
84
172
|
>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</User>
|
|
88
|
-
</f-state>
|
|
173
|
+
<!-- Your XML state here -->
|
|
174
|
+
</f-store>
|
|
89
175
|
```
|
|
90
176
|
|
|
91
|
-
|
|
92
|
-
- `at(path)` — Get reactive node
|
|
93
|
-
- `query(path)` — Get all matching nodes
|
|
94
|
-
- `set(path, value)` — Set value
|
|
95
|
-
- `add(path, xml)` — Add child
|
|
96
|
-
- `remove(path)` — Remove node
|
|
97
|
-
- `serialize()` — Get XML string
|
|
98
|
-
- `on(action, fn)` — Register action
|
|
99
|
-
- `emit(action, payload)` — Dispatch action
|
|
100
|
-
- `state.when(name, fn)` — React to state
|
|
101
|
-
- `state.until(name)` — Promise for state
|
|
102
|
-
|
|
103
|
-
### `<f-text>` — Display
|
|
177
|
+
### `<f-text>` — Display Value
|
|
104
178
|
|
|
105
179
|
```html
|
|
106
|
-
<f-text path="
|
|
107
|
-
<f-text path="Price" format="currency"></f-text>
|
|
180
|
+
<f-text path="user.name"></f-text>
|
|
108
181
|
```
|
|
109
182
|
|
|
110
|
-
**Formats:** `uppercase`, `lowercase`, `number`, `currency`, `percent`
|
|
111
|
-
|
|
112
183
|
### `<f-field>` — Two-Way Binding
|
|
113
184
|
|
|
114
185
|
```html
|
|
115
|
-
<f-field path="
|
|
116
|
-
<f-field path="
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
### `<f-each>` — Lists
|
|
121
|
-
|
|
122
|
-
```html
|
|
123
|
-
<f-each path="Users.User">
|
|
124
|
-
<template>
|
|
125
|
-
<div class="card">
|
|
126
|
-
<f-text path="Name"></f-text>
|
|
127
|
-
<f-field path="Email"></f-field>
|
|
128
|
-
</div>
|
|
129
|
-
</template>
|
|
130
|
-
</f-each>
|
|
186
|
+
<f-field path="user.name"></f-field>
|
|
187
|
+
<f-field path="user.role" type="select">
|
|
188
|
+
<option value="admin">Admin</option>
|
|
189
|
+
<option value="user">User</option>
|
|
190
|
+
</f-field>
|
|
131
191
|
```
|
|
132
192
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
### `<f-when>` — Conditionals
|
|
193
|
+
### `<f-when>` — Conditional
|
|
136
194
|
|
|
137
195
|
```html
|
|
138
|
-
<f-when test="
|
|
139
|
-
<span>
|
|
196
|
+
<f-when test="user.active == 'true'">
|
|
197
|
+
<span>Active!</span>
|
|
140
198
|
</f-when>
|
|
141
199
|
|
|
142
|
-
<f-when test="
|
|
200
|
+
<f-when test="cart.total > 100">
|
|
143
201
|
<span>Free shipping!</span>
|
|
144
202
|
</f-when>
|
|
145
203
|
```
|
|
@@ -147,225 +205,179 @@ Paths inside templates are relative to each item.
|
|
|
147
205
|
### `<f-match>` — Switch
|
|
148
206
|
|
|
149
207
|
```html
|
|
150
|
-
<f-match test="
|
|
208
|
+
<f-match test="user.role">
|
|
151
209
|
<f-case value="admin">Admin Panel</f-case>
|
|
152
210
|
<f-case value="user">Dashboard</f-case>
|
|
153
211
|
<f-else>Please log in</f-else>
|
|
154
212
|
</f-match>
|
|
155
213
|
```
|
|
156
214
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
## Store Discovery
|
|
160
|
-
|
|
161
|
-
Components don't need to be nested inside `<f-state>`. Flarp searches siblings and ancestors:
|
|
215
|
+
### `<f-each>` — Iteration
|
|
162
216
|
|
|
163
217
|
```html
|
|
164
|
-
<
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
<f-text path="User.Name"></f-text>
|
|
172
|
-
</main>
|
|
173
|
-
</div>
|
|
218
|
+
<f-each path="users.user">
|
|
219
|
+
<template>
|
|
220
|
+
<div class="user">
|
|
221
|
+
<f-text path="name"></f-text>
|
|
222
|
+
</div>
|
|
223
|
+
</template>
|
|
224
|
+
</f-each>
|
|
174
225
|
```
|
|
175
226
|
|
|
176
|
-
|
|
227
|
+
---
|
|
177
228
|
|
|
178
|
-
|
|
179
|
-
<f-text store="app" path="User.Name"></f-text>
|
|
180
|
-
```
|
|
229
|
+
## JavaScript API
|
|
181
230
|
|
|
182
|
-
|
|
231
|
+
```javascript
|
|
232
|
+
const store = document.querySelector('f-store');
|
|
183
233
|
|
|
184
|
-
|
|
234
|
+
// Wait for ready
|
|
235
|
+
store.state.when('ready', () => {
|
|
185
236
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
store.on('addUser', ({ name, role }) => {
|
|
189
|
-
store.add('Users', `<User><n>${name}</n><Role>${role}</Role></User>`);
|
|
190
|
-
});
|
|
237
|
+
// Get reactive node
|
|
238
|
+
const name = store.at('user.name');
|
|
191
239
|
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
240
|
+
// Read/write
|
|
241
|
+
console.log(name.value);
|
|
242
|
+
name.value = 'Bob';
|
|
195
243
|
|
|
196
|
-
|
|
244
|
+
// Subscribe to changes
|
|
245
|
+
name.subscribe(v => console.log('Name changed:', v));
|
|
197
246
|
|
|
198
|
-
|
|
247
|
+
// Query multiple
|
|
248
|
+
const users = store.query('users.user');
|
|
199
249
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
- **Identifiable** — `uuid` attribute (stable identity)
|
|
203
|
-
- **Versioned** — `rev` for conflict detection & resolution
|
|
204
|
-
- **Serializable** — `node.serialize()`
|
|
250
|
+
// Add node
|
|
251
|
+
store.add('users', '<user><n>New User</n></user>');
|
|
205
252
|
|
|
206
|
-
|
|
207
|
-
|
|
253
|
+
// Remove node
|
|
254
|
+
store.remove('users.user[0]');
|
|
208
255
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
node.revNumber; // 5
|
|
212
|
-
node.revId; // "f94bc318..." (write identifier)
|
|
256
|
+
// Serialize
|
|
257
|
+
const xml = store.serialize();
|
|
213
258
|
|
|
214
|
-
|
|
259
|
+
// Manual save
|
|
260
|
+
store.save();
|
|
261
|
+
|
|
262
|
+
// Apply external update
|
|
263
|
+
store.applyRemote('<n uuid="..." rev="5-abc">Value</n>');
|
|
264
|
+
});
|
|
215
265
|
|
|
216
|
-
//
|
|
217
|
-
|
|
266
|
+
// Event handlers
|
|
267
|
+
store.onChange(xml => console.log('Changed'));
|
|
268
|
+
store.onConflict(info => console.log('Conflict:', info));
|
|
218
269
|
```
|
|
219
270
|
|
|
220
271
|
---
|
|
221
272
|
|
|
222
|
-
##
|
|
223
|
-
|
|
224
|
-
Flarp uses CouchDB-style revision tracking: `{number}-{uuid}`
|
|
273
|
+
## Architecture
|
|
225
274
|
|
|
226
275
|
```
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
276
|
+
flarp/
|
|
277
|
+
├── src/
|
|
278
|
+
│ ├── core/ # Reactive primitives
|
|
279
|
+
│ │ ├── Signal.js # Synchronous reactive value
|
|
280
|
+
│ │ └── State.js # Named states (ready, synced, etc.)
|
|
281
|
+
│ │
|
|
282
|
+
│ ├── xml/ # XML utilities
|
|
283
|
+
│ │ ├── Node.js # Reactive node wrapper
|
|
284
|
+
│ │ ├── Path.js # Path resolution
|
|
285
|
+
│ │ └── Tree.js # Tree management
|
|
286
|
+
│ │
|
|
287
|
+
│ ├── sync/ # Persistence & sync
|
|
288
|
+
│ │ ├── Sync.js # Cross-tab sync with changes feed
|
|
289
|
+
│ │ ├── Store.js # FStore component
|
|
290
|
+
│ │ ├── Persist.js # Storage adapters
|
|
291
|
+
│ │ └── Channel.js # BroadcastChannel wrapper
|
|
292
|
+
│ │
|
|
293
|
+
│ ├── dom/ # DOM utilities
|
|
294
|
+
│ │ └── find.js # Store discovery
|
|
295
|
+
│ │
|
|
296
|
+
│ ├── components/ # Web Components
|
|
297
|
+
│ │ ├── FText.js
|
|
298
|
+
│ │ ├── FField.js
|
|
299
|
+
│ │ ├── FWhen.js
|
|
300
|
+
│ │ ├── FEach.js
|
|
301
|
+
│ │ └── FBind.js
|
|
302
|
+
│ │
|
|
303
|
+
│ └── index.js # Main exports
|
|
304
|
+
│
|
|
305
|
+
├── index.html # Demo
|
|
306
|
+
├── multiuser.html # Multi-tab sync demo
|
|
307
|
+
└── README.md
|
|
230
308
|
```
|
|
231
309
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
1. **Revision number** — Quick comparison (higher wins)
|
|
235
|
-
2. **Revision UUID** — Conflict detection & tie-breaking
|
|
310
|
+
---
|
|
236
311
|
|
|
237
|
-
|
|
238
|
-
```
|
|
239
|
-
User A saves: rev="3-ee30f92d..."
|
|
240
|
-
User B saves: rev="3-56b1d51a..."
|
|
241
|
-
↑ Same number = CONFLICT detected
|
|
312
|
+
## Multi-User Demo
|
|
242
313
|
|
|
243
|
-
|
|
244
|
-
"56b1d51a" < "ee30f92d" → User B wins
|
|
245
|
-
```
|
|
314
|
+
Open `multiuser.html` in multiple browser tabs to see sync in action:
|
|
246
315
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
316
|
+
1. Each tab gets a unique user ID
|
|
317
|
+
2. Changes sync instantly across all tabs
|
|
318
|
+
3. Conflicts are detected and resolved automatically
|
|
319
|
+
4. Enable "auto-write" to stress test
|
|
251
320
|
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
console.log('Conflict detected!');
|
|
256
|
-
}
|
|
321
|
+
```bash
|
|
322
|
+
# Start a local server
|
|
323
|
+
npm run dev
|
|
257
324
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// { applied: true, conflict: true, winner: 'remote' }
|
|
325
|
+
# Open multiple tabs to:
|
|
326
|
+
http://localhost:8080/multiuser.html
|
|
261
327
|
```
|
|
262
328
|
|
|
263
329
|
---
|
|
264
330
|
|
|
265
|
-
## Persistence
|
|
331
|
+
## Saving & Persistence
|
|
266
332
|
|
|
267
|
-
|
|
333
|
+
### Automatic
|
|
268
334
|
|
|
269
335
|
```html
|
|
270
|
-
<f-
|
|
336
|
+
<f-store key="myapp" autosave="500">
|
|
337
|
+
<!-- Saves 500ms after last change -->
|
|
338
|
+
</f-store>
|
|
271
339
|
```
|
|
272
340
|
|
|
273
|
-
|
|
274
|
-
- **Crash recovery** — Reload resumes state
|
|
275
|
-
- **Cross-tab sync** — BroadcastChannel
|
|
276
|
-
- **Conflict resolution** — Higher `rev` wins, `uuid` tiebreaker
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
## Modular Architecture
|
|
281
|
-
|
|
282
|
-
Flarp is built from composable pieces:
|
|
341
|
+
### Manual
|
|
283
342
|
|
|
284
|
-
```
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
// Just XML utilities
|
|
289
|
-
import { Node, Path } from 'flarp/xml';
|
|
290
|
-
|
|
291
|
-
// Just persistence
|
|
292
|
-
import { Persist } from 'flarp/sync';
|
|
343
|
+
```javascript
|
|
344
|
+
store.save(); // Save now
|
|
345
|
+
store.clear(); // Clear stored state
|
|
293
346
|
```
|
|
294
347
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
## Custom Components
|
|
298
|
-
|
|
299
|
-
```js
|
|
300
|
-
import { findStore } from 'flarp';
|
|
301
|
-
|
|
302
|
-
class UserCard extends HTMLElement {
|
|
303
|
-
connectedCallback() {
|
|
304
|
-
const store = findStore(this);
|
|
305
|
-
|
|
306
|
-
store.state.when('ready', () => {
|
|
307
|
-
const name = store.at('User.Name');
|
|
308
|
-
|
|
309
|
-
name.subscribe(v => {
|
|
310
|
-
this.innerHTML = `<h2>${v}</h2>`;
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
}
|
|
348
|
+
### Emergency Save
|
|
315
349
|
|
|
316
|
-
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
---
|
|
350
|
+
Flarp automatically saves on `beforeunload` (browser close/refresh).
|
|
320
351
|
|
|
321
|
-
|
|
352
|
+
### Storage Location
|
|
322
353
|
|
|
323
|
-
|
|
324
|
-
2. **Signals are sync** — No scheduler, no batching, just updates
|
|
325
|
-
3. **DOM is truth** — MutationObserver, not virtual DOM
|
|
326
|
-
4. **State ≠ UI** — Keep them separate, connect with paths
|
|
327
|
-
5. **Persist by default** — State survives refresh
|
|
354
|
+
Data is stored in localStorage under `flarp-data:{key}`.
|
|
328
355
|
|
|
329
356
|
---
|
|
330
357
|
|
|
331
358
|
## Tag Naming
|
|
332
359
|
|
|
333
|
-
**
|
|
360
|
+
⚠️ **Use lowercase tags!** The DOM lowercases all tag names, so `<User>` becomes `<user>`. For clarity, always write lowercase:
|
|
334
361
|
|
|
335
362
|
```html
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
</
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
<user><name>Alice</name></user>
|
|
346
|
-
</f-state>
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
**Flarp handles this automatically** — path matching is case-insensitive. Both `user.name` and `User.Name` will work. However, we recommend using lowercase tags for clarity:
|
|
350
|
-
|
|
351
|
-
```html
|
|
352
|
-
<f-state>
|
|
353
|
-
<user>
|
|
354
|
-
<name>Alice</name>
|
|
355
|
-
<email>alice@example.com</email>
|
|
356
|
-
</user>
|
|
357
|
-
</f-state>
|
|
363
|
+
<!-- Good -->
|
|
364
|
+
<f-store>
|
|
365
|
+
<user><n>Alice</n></user>
|
|
366
|
+
</f-store>
|
|
367
|
+
|
|
368
|
+
<!-- Works but confusing -->
|
|
369
|
+
<f-store>
|
|
370
|
+
<User><n>Alice</n></User>
|
|
371
|
+
</f-store>
|
|
358
372
|
```
|
|
359
373
|
|
|
360
374
|
---
|
|
361
375
|
|
|
362
376
|
## Browser Support
|
|
363
377
|
|
|
364
|
-
Modern browsers with
|
|
365
|
-
-
|
|
366
|
-
-
|
|
367
|
-
- Safari 11+
|
|
368
|
-
- Edge 79+
|
|
378
|
+
- Modern browsers with BroadcastChannel (Chrome, Firefox, Safari, Edge)
|
|
379
|
+
- Falls back to localStorage events for older browsers
|
|
380
|
+
- Requires ES modules support
|
|
369
381
|
|
|
370
382
|
---
|
|
371
383
|
|
|
@@ -373,14 +385,10 @@ Modern browsers with ES modules:
|
|
|
373
385
|
|
|
374
386
|
flarp (from Jargon File)
|
|
375
387
|
|
|
376
|
-
/flarp/ [Rutgers University] Yet another metasyntactic variable (see foo). Among those who use it, it is associated with a legend that any program not containing the word "flarp" somewhere will not work. The legend is discreetly silent on the reliability of programs which
|
|
388
|
+
/flarp/ [Rutgers University] Yet another metasyntactic variable (see foo). Among those who use it, it is associated with a legend that any program not containing the word "flarp" somewhere will not work. The legend is discreetly silent on the reliability of programs which do contain the magic word.
|
|
377
389
|
|
|
378
390
|
---
|
|
379
391
|
|
|
380
392
|
## License
|
|
381
393
|
|
|
382
394
|
MIT
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
*Built for developers who believe state management can be simple.*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flarp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "DOM-native XML state management
|
|
3
|
+
"version": "2.5.1",
|
|
4
|
+
"description": "DOM-native XML state management with multi-master sync",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"module": "src/index.js",
|
package/src/components/index.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Flarp Web Components
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// FStore is in sync module for better organization
|
|
6
|
+
export { FStore } from '../sync/index.js';
|
|
7
|
+
|
|
6
8
|
export { default as FText } from './FText.js';
|
|
7
9
|
export { default as FField } from './FField.js';
|
|
8
10
|
export { default as FBind } from './FBind.js';
|