Skip to content

Commit 7997544

Browse files
committed
Refine Live Queries docs
1 parent 0391b6e commit 7997544

File tree

1 file changed

+34
-35
lines changed

1 file changed

+34
-35
lines changed

site/capabilities/live-queries.md

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ You get back a *live view* of your query — in realtime.
1313
Live queries are a first-class concept in LinkedQL.
1414
They happen over the same `client.query()` API.
1515

16-
The `.query()` method does what you expect. But for a `SELECT` query, it also works in **live mode**:
16+
The `client.query()` method simply additionally supports an `options.live` parameter for `SELECT` queries:
1717

1818
```js
1919
const result = await client.query(`SELECT * FROM posts`, { live: true });
@@ -153,7 +153,7 @@ Essentially, LinkedQL extends reactivity to the full semantic surface of `SELECT
153153

154154
## Live Views in Detail
155155

156-
`result.rows` is a self-updating array of objects — each element reflecting a row in real time.
156+
`result.rows` is a self-updating array of objects — each result row in the array reflecting the database in real time.
157157

158158
For a query like the below:
159159

@@ -194,7 +194,7 @@ UPDATE posts SET title = 'Hello Again' WHERE title = 'Hello World';
194194
[A, B, C^, D, E] // C updated
195195
```
196196

197-
When a row is deleted in the database, the corresponding row in the view disappears:
197+
When a row is deleted in the database, the corresponding row in the view leaves the view:
198198

199199
```sql
200200
DELETE FROM posts WHERE title = 'Hello Again';
@@ -218,19 +218,18 @@ const result = await client.query(
218218
);
219219
```
220220

221-
Database-level mutations — `INSERT`, `UPDATE`, or `DELETE` — on either side of a join can affect the **join relationship** itself.<br>
222-
A right-hand or left-hand side that once matched may suddenly match no more, or the reverse may be the case.
221+
Database-level mutations — `INSERT`, `UPDATE`, or `DELETE` — have a deeper semantic effect in the view. Given that rows are composed of data from multiple tables, each row in the view has to obey the join semantics as mutations happen on any of the underlying tables. A right-hand or left-hand side of a join that once matched may suddenly match no more, and the reverse may be the case.
223222

224223
This means the result of an event like `INSERT` or `DELETE` may not always mean “add”, or “remove”, a row in the view.
225-
It might instead mean: *a row has transited from "no matching right-hand side" to "fully materialized"*, or the reverse.<br>
226-
This is **Join Transition**.
224+
It might instead mean: *a row has transited from "no matching right-hand side" to "a fully materialized join"*, or the reverse.<br>
225+
This is treated in LinkedQL as **Join Transition**.
227226

228-
Join transitions would normally be observed as a "delete" + "add" effect — existing composition dissolves and a new one emerges.
227+
If interpreted naively, join transitions would be observed as a "delete" + "add" effect — that is, the existing composition dissolves and a new one emerges.
229228
But LinkedQL is designed to detect these phenomena and properly communicate them as **in-place updates**, preserving continuity and identity.
230229

231230
Observers see an in-place update, not a *teardown + recreate* sequence:
232231

233-
#### Example 1 — `INSERT` causes a join to materialize
232+
#### Scenario 1 — An `INSERT` causes a join to materialize
234233

235234
```sql
236235
INSERT INTO users (id, name) VALUES (42, 'Ada');
@@ -244,7 +243,7 @@ INSERT INTO users (id, name) VALUES (42, 'Ada');
244243
└──────────────────────────────────────────────────────────────┘
245244
```
246245

247-
#### Example 2 — `UPDATE` changes the join relationship
246+
#### Scenario 2 — An `UPDATE` changes the join relationship
248247

249248
```sql
250249
UPDATE posts SET author_id = 42 WHERE title = 'Untitled';
@@ -258,7 +257,7 @@ UPDATE posts SET author_id = 42 WHERE title = 'Untitled';
258257
└───────────────────────────────────────────────────────────────┘
259258
```
260259

261-
#### Example 3 — `DELETE` dissolves the join
260+
#### Scenario 3 — A `DELETE` dissolves the join
262261

263262
```sql
264263
DELETE FROM users WHERE id = 42;
@@ -272,14 +271,11 @@ DELETE FROM users WHERE id = 42;
272271
└──────────────────────────────────────────────────────────────┘
273272
```
274273

275-
*Overal:* identity persists.
276-
277-
Essentially, LinkedQL interprets database mutations through the lens of query semantics —
278-
thus, join compositions remain continuous relationships over time.
274+
*Overal:* identity persists. The view stays true the join relationships without letting the underlying mutaions leak into the view.
279275

280276
### Frames and Ordinality
281277

282-
Queries that have ordering, limits, or offsets applied are materialized with the semantics of each modifier automatically maintained in the view.
278+
Queries that have ordering, limits, or offsets applied materialize in the view with the semantics of each modifier fully maintained.
283279

284280
```js
285281
const top5 = (await client.query(
@@ -299,13 +295,13 @@ Initially:
299295
[A, B, C, D, E] // initial result
300296
```
301297

302-
Then on "_`INSERT` a new row `N`_":
298+
Then on "_`INSERT`ing a new row `N`_":
303299

304300
```text
305301
[N, A, B, C, D] // N enters the view; E falls off because it’s now #6
306302
```
307303

308-
Then on "_`UPDATE` a post’s `created_at`_":
304+
Then on "_`UPDATE`ing a post’s `created_at` field and promoting it one step higher in the list_":
309305

310306
```text
311307
[N, A, C, B, D] // C and B swap places without initiating a full re-ordering
@@ -315,9 +311,7 @@ Essentially, ordering and slicing remain stable relationships — they evolve as
315311

316312
### Precision and Granularity
317313

318-
Live updates apply the smallest possible change needed to keep the view correct. This reflects a key design goal in LinkedQL: precision and granularity.
319-
320-
This guarantees two things:
314+
Live updates apply the smallest possible change needed to keep the view correct. This is a key design goal in LinkedQL.
321315

322316
**(a) Field-level updates**
323317

@@ -349,7 +343,7 @@ After: [A, C, B, D, E]
349343

350344
**Why that matters:**
351345

352-
Precision and granularity help keep costs low across consumers bound to the view. When rendering on the UI, for example:
346+
Precision and granularity keeps the system – all the way to the consumers bound to the view – highly efficient. When rendering on the UI, for example:
353347

354348
* The UI maintains state, avoids unnecessary rerenders, and never flickers.
355349
* Components keyed by row identity keep their state.
@@ -361,23 +355,25 @@ Live views are not just auto-updating — they are also **observable**.
361355

362356
LinkedQL exposes them through the [Observer API](https://github.com/webqit/observer). Observer is a general-purpose JavaScript API for observing object and array-level mutations.
363357

364-
You pass a callback to observe root-level changes — which, for `result.rows`, would mean row additions and deletions:
358+
This makes `result.rows` observable like any object.
365359

366360
```js
367361
Observer.observe(result.rows, (mutations) => {
368362
console.log(`${mutations[0].type}: ${mutations[0].key} = ${mutations[0].value}`);
369363
});
370364
```
371365

372-
To go deeper and observe field-level changes, you use the `Observer.subtree()` directive:
366+
You pass a callback, as shown above, to observe root-level changes — which, for `result.rows`, would mean row additions and deletions:
367+
368+
You observe field-level changes by adding the `Observer.subtree()` directive:
373369

374370
```js
375371
Observer.observe(result.rows, Observer.subtree(), (mutations) => {
376372
console.log(`${mutations[0].type}: ${mutations[0].key} = ${mutations[0].value}`);
377373
});
378374
```
379375

380-
Observer guarantees that events are delivered with the atomicity of the underlying database transactions. In other words, all mutations that happen inside a single database transaction arrive together in one callback turn.
376+
LinkedQL leverages Observer's batching feature to preserve the atomicity of the database transactions behind the emitted events. It guarantees that all mutations that happen inside a single database transaction arrive together in one callback turn.
381377

382378
For example:
383379

@@ -403,15 +399,15 @@ Observer.observe(result.rows, Observer.subtree(), (mutations) => {
403399
});
404400
```
405401

406-
Essentially, you never see half a transaction.
402+
Essentially, transactions aren't torn across multiple emissions.
407403

408404
### Live Bindings
409405

410406
LinkedQL’s live views are ordinary JavaScript objects and arrays. They simply happen to mutate over time as the database changes.
411407

412-
And here’s how that plays out across runtimes: because they use the [Observer API](https://github.com/webqit/observer) protocol, you get automatic binding and mutation-based reactivity across contexts or runtimes where mutations are a first-class concept.
408+
Those mutations themselves are the basis for reactivity in the design. Because they happen via the [Observer API](https://github.com/webqit/observer) protocol, you get automatic binding and mutation-based reactivity across contexts or runtimes where mutations are a first-class concept.
413409

414-
For example, with the[ Webflo framework](https://github.com/webqit/webflo)’s *[live response](https://webflo.netlify.app/docs/concepts/realtime#live-responses)* capability, `result.rows` — like any object — can be returned from a route, with reactivity preserved over the wire.
410+
For example, with the[ Webflo framework](https://github.com/webqit/webflo)’s *[live response](https://webflo.netlify.app/docs/concepts/realtime#live-responses)* capability, `result.rows` — like any object — can be returned from a route as live response, with reactivity preserved over the wire.
415411

416412
```js
417413
export default async function(event) {
@@ -421,7 +417,7 @@ export default async function(event) {
421417
}
422418
```
423419

424-
That object materializes on the client-side as the same live object, accessible via `document.bindings.data`:
420+
That object materializes in the client as the same live object, which in Webflo is accessible via `document.bindings.data`:
425421

426422
```html
427423
<script src="https://unpkg.com/@webqit/observer/main.js"></script>
@@ -433,14 +429,17 @@ Observer.observe(data, console.log);
433429
</script>
434430
```
435431

436-
If the goal is to render, that, too, comes automatic: [OOHTML](https://github.com/webqit/oohtml) gives you automatic data-binding over arbitrary objects and arrays — without a compile step:
432+
These live objects automatically bind to UI in any mutation-based data-binding framework like [OOHTML](https://github.com/webqit/oohtml). OOHTML is an addition to the DOM that brings mutation-based reactivity to the UI — without a compile step:
437433

438434
```html
439435
<script src="https://unpkg.com/@webqit/oohtml/dist/main.lite.js"></script>
440436
<div><?{ data.list.length }?></div>
441437
```
442438

443-
The UI updates as posts are added or removed — with no glue code. (List rendering has been omitted here for brevity. Try updating the posts table from a terminal to see the UI update the total count.)
439+
The UI in this example updates as posts are added or removed — with no glue code. (List rendering has been omitted here for brevity.)
440+
441+
> [!TIP]
442+
> Try updating the posts table from a terminal to see the UI update the total count.
444443
445444
Essentially, with the Observer protocol as the shared vocabulary of change, continuity stays intact from database to DOM. Each layer in the chain — LinkedQL → Webflo → OOHTML — simply makes or reacts to mutations.
446445

@@ -453,12 +452,12 @@ That event stream is made of three event types:
453452

454453
| Event | Meaning |
455454
| :------- | :----------------------------------------------------------------- |
456-
| `result` | A full snapshot of the query result. |
455+
| `result` | A full snapshot of the query result – for when diffrential updates aren't feasible for the qiven query – typically queries with aggregates. |
457456
| `diff` | Incremental inserts, updates, and deletes. |
458457
| `swap` | Positional swaps that satisfy an `ORDER BY` clause |
459458

460459
You can subscribe to these events directly and maintain your own state store.
461-
This is useful if you’re building a custom cache, animation layer, or replication layer.
460+
This is useful if you’re building a custom cache, or replication layer.
462461

463462
```js
464463
// Get a handle to the live query
@@ -536,11 +535,11 @@ That logic is **conceptually** what the built-in [`RealtimeResult`](../docs/quer
536535
* preserves ordering and LIMIT/OFFSET semantics;
537536
* exposes the final live state as `result.rows`.
538537

539-
Compared to the default live view concept, custom event handling sits closer to the wire — meant for systems that need explicit control, like caches or replication layers.
538+
Compared to the default live view concept, custom event handling sits closer to the wire.
540539

541540
## Query Inheritance
542541

543-
Live queries are efficient because LinkedQL does not treat each subscription as an isolated process.
542+
Live queries are highly efficient because LinkedQL does not treat each subscription as an isolated process.
544543
Instead, LinkedQL groups overlapping queries into a shared structure called a **query inheritance tree**.
545544

546545
Example:

0 commit comments

Comments
 (0)