solid_objects 0.7.2 → 0.7.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/app/assets/javascripts/solid_objects/component_batch_refresh.js +43 -7
- data/docs/realtime.md +5 -2
- data/lib/solid_objects/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: de78ca94f2c12e3c08d50c7b2512d6dbda67a47571540390c7494c6a828f1d35
|
|
4
|
+
data.tar.gz: b194c137ff3110cabb6cb2f7319819fdd64f314a9ae2113868438a89acce36b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 78834ecb346469854b6d21d938fe3a6ed2689cd60cb037b22b3ce00025466c172ceb3dacdfcf94f88b0e0739e3dcac1c3e84896c6685bc4fe53d4db565ab8513
|
|
7
|
+
data.tar.gz: 31fbae44dce542e1fc07fe218bf504eb8627583ecc64895e8d86a5805340aeea47531b5f08a6e844809c31b513998c53c28b90993ae9cd40b864e868323eee27
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.3 - 2026-08-09
|
|
4
|
+
|
|
5
|
+
- Coordinate batched component refreshes by revision as well as scope and batch
|
|
6
|
+
name. Invalidations for one revision arrive as separate WebSocket messages, so
|
|
7
|
+
the microtask merge could not see them all, and each request aborted the one
|
|
8
|
+
before it. Only the last component updated. Same-revision requests now run
|
|
9
|
+
alongside each other and every frame is applied; only a strictly newer
|
|
10
|
+
revision supersedes an in-flight request. Frames already applied at a revision
|
|
11
|
+
are not applied twice.
|
|
12
|
+
|
|
3
13
|
## 0.7.2 - 2026-08-09
|
|
4
14
|
|
|
5
15
|
- Render batched component partials as HTML regardless of the request format.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const pendingBatches = new Map()
|
|
2
2
|
const activeBatches = new Map()
|
|
3
|
+
const appliedRevisions = new Map()
|
|
4
|
+
let requestSequence = 0
|
|
3
5
|
|
|
4
6
|
class SolidObjectsBatchRefreshElement extends HTMLElement {
|
|
5
7
|
connectedCallback() {
|
|
@@ -34,18 +36,26 @@ class SolidObjectsBatchRefreshElement extends HTMLElement {
|
|
|
34
36
|
pendingBatches.set(key, merged)
|
|
35
37
|
queueMicrotask(() => {
|
|
36
38
|
pendingBatches.delete(key)
|
|
37
|
-
requestBatch(group, batch, merged.sources)
|
|
39
|
+
requestBatch(group, batch, revision, merged.sources)
|
|
38
40
|
})
|
|
39
41
|
this.remove()
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Invalidations for one revision arrive in separate WebSocket messages, so the
|
|
46
|
+
// microtask merge cannot see them all. Requests are tracked per revision and a
|
|
47
|
+
// request is only cancelled by a strictly newer one; same-revision requests run
|
|
48
|
+
// alongside each other and every frame is applied.
|
|
49
|
+
async function requestBatch(group, batch, revision, sources) {
|
|
50
|
+
const parsed = parseRevision(revision)
|
|
51
|
+
supersedeOlderRequests(group, parsed)
|
|
52
|
+
|
|
53
|
+
// Same-revision requests run concurrently, so each needs its own entry.
|
|
54
|
+
// Sharing one key per revision would leave all but the last untracked and
|
|
55
|
+
// therefore impossible to supersede.
|
|
56
|
+
const key = `${group}:${revision}:${(requestSequence += 1)}`
|
|
47
57
|
const controller = new AbortController()
|
|
48
|
-
activeBatches.set(group,
|
|
58
|
+
activeBatches.set(key, { controller, group, revision: parsed })
|
|
49
59
|
|
|
50
60
|
try {
|
|
51
61
|
const url = mergedUrl(sources)
|
|
@@ -68,10 +78,29 @@ async function requestBatch(group, batch, sources) {
|
|
|
68
78
|
} catch (error) {
|
|
69
79
|
if (error.name !== "AbortError") dispatchBatchError(batch, "request_failed")
|
|
70
80
|
} finally {
|
|
71
|
-
if (activeBatches.get(
|
|
81
|
+
if (activeBatches.get(key)?.controller === controller) activeBatches.delete(key)
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
function supersedeOlderRequests(group, revision) {
|
|
86
|
+
if (!revision) return
|
|
87
|
+
|
|
88
|
+
activeBatches.forEach((entry, key) => {
|
|
89
|
+
if (entry.group !== group) return
|
|
90
|
+
if (!olderRevision(entry.revision, revision)) return
|
|
91
|
+
|
|
92
|
+
entry.controller.abort()
|
|
93
|
+
activeBatches.delete(key)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function olderRevision(candidate, current) {
|
|
98
|
+
if (!candidate || !current) return false
|
|
99
|
+
|
|
100
|
+
return candidate[0] < current[0] ||
|
|
101
|
+
(candidate[0] === current[0] && candidate[1] < current[1])
|
|
102
|
+
}
|
|
103
|
+
|
|
75
104
|
// Every notification for one batch and revision carries the same endpoint and
|
|
76
105
|
// differs only by which components changed, so the union of their tokens is the
|
|
77
106
|
// complete set to render.
|
|
@@ -93,6 +122,13 @@ function applyFrame(frame) {
|
|
|
93
122
|
const target = document.getElementById(frame?.target)
|
|
94
123
|
if (!target || !frame.html) return
|
|
95
124
|
if (!newerRevision(frame.revision, target.dataset.solidObjectsRevision)) return
|
|
125
|
+
// Concurrent same-revision responses can carry the same frame. The target's
|
|
126
|
+
// own revision only advances once Turbo applies the stream, so what has
|
|
127
|
+
// already been applied is tracked here as well.
|
|
128
|
+
const applied = appliedRevisions.get(frame.target)
|
|
129
|
+
if (applied && !newerRevision(frame.revision, applied)) return
|
|
130
|
+
|
|
131
|
+
appliedRevisions.set(frame.target, frame.revision)
|
|
96
132
|
|
|
97
133
|
const parsed = new DOMParser().parseFromString(frame.html, "text/html")
|
|
98
134
|
const replacement = parsed.getElementById(frame.target)
|
data/docs/realtime.md
CHANGED
|
@@ -173,8 +173,11 @@ they are while giving the client a documented contract with per-frame revisions.
|
|
|
173
173
|
### What the protocol guarantees
|
|
174
174
|
|
|
175
175
|
Only components whose dependencies changed are requested; the rest are never
|
|
176
|
-
named in the batch.
|
|
177
|
-
|
|
176
|
+
named in the batch. Notifications for the same batch and revision that arrive in
|
|
177
|
+
one task merge into a single request. Notifications that arrive in separate
|
|
178
|
+
WebSocket messages issue their own requests and all of their frames are applied,
|
|
179
|
+
because cancelling a same-revision request would drop the components it carried.
|
|
180
|
+
Only a strictly newer revision supersedes an in-flight request. Each
|
|
178
181
|
frame carries its own revision and cannot overwrite a target that already holds a
|
|
179
182
|
newer one. Authorization is unchanged: every component in the batch passes the
|
|
180
183
|
same `authorize_query` boundary an individual refresh uses, and the batch name is
|