braid-text 0.2.86 → 0.2.88
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/.claude/settings.local.json +9 -0
- package/index.js +107 -23
- package/package.json +1 -1
- package/test/tests.js +33 -0
package/index.js
CHANGED
|
@@ -160,24 +160,86 @@ function create_braid_text() {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
// local -> remote
|
|
163
|
+
// local -> remote (with in_flight queue for concurrency control)
|
|
164
|
+
var q = []
|
|
165
|
+
var in_flight = new Map()
|
|
166
|
+
var max_in_flight = 10
|
|
167
|
+
var send_pump_lock = 0
|
|
168
|
+
var temp_acs = new Set()
|
|
169
|
+
ac.signal.addEventListener('abort', () => {
|
|
170
|
+
for (var t of temp_acs) t.abort()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
async function send_out(update) {
|
|
174
|
+
update.signal = ac.signal
|
|
175
|
+
update.dont_retry = true
|
|
176
|
+
var x = await braid_text.put(b, update)
|
|
177
|
+
if (x.ok) {
|
|
178
|
+
local_first_put()
|
|
179
|
+
extend_fork_point(update)
|
|
180
|
+
} else if (x.status === 401 || x.status === 403) {
|
|
181
|
+
options.on_unauthorized?.()
|
|
182
|
+
} else throw new Error('failed to PUT: ' + x.status)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function send_pump() {
|
|
186
|
+
send_pump_lock++
|
|
187
|
+
if (send_pump_lock > 1) return
|
|
188
|
+
try {
|
|
189
|
+
if (closed) return
|
|
190
|
+
if (in_flight.size >= max_in_flight) return
|
|
191
|
+
if (!q.length) {
|
|
192
|
+
// Extend frontier based on in-flight updates
|
|
193
|
+
var frontier = resource.meta.fork_point || []
|
|
194
|
+
for (var u of in_flight.values())
|
|
195
|
+
frontier = extend_frontier(frontier, u.version, u.parents)
|
|
196
|
+
|
|
197
|
+
var temp_ac = new AbortController()
|
|
198
|
+
temp_acs.add(temp_ac)
|
|
199
|
+
var temp_ops = {
|
|
200
|
+
signal: temp_ac.signal,
|
|
201
|
+
parents: frontier,
|
|
202
|
+
merge_type: 'dt',
|
|
203
|
+
subscribe: u => u.version?.length && q.push(u)
|
|
204
|
+
}
|
|
205
|
+
await braid_text.get(a, temp_ops)
|
|
206
|
+
temp_ac.abort()
|
|
207
|
+
temp_acs.delete(temp_ac)
|
|
208
|
+
}
|
|
209
|
+
while (q.length && in_flight.size < max_in_flight) {
|
|
210
|
+
let u = q.shift()
|
|
211
|
+
if (!u.version?.length) continue
|
|
212
|
+
in_flight.set(u.version[0], u);
|
|
213
|
+
(async () => {
|
|
214
|
+
try {
|
|
215
|
+
if (closed) return
|
|
216
|
+
await send_out(u)
|
|
217
|
+
if (closed) return
|
|
218
|
+
in_flight.delete(u.version[0])
|
|
219
|
+
setTimeout(send_pump, 0)
|
|
220
|
+
} catch (e) {
|
|
221
|
+
if (e.name === 'AbortError') {
|
|
222
|
+
// ignore
|
|
223
|
+
} else handle_error(e)
|
|
224
|
+
}
|
|
225
|
+
})()
|
|
226
|
+
}
|
|
227
|
+
} finally {
|
|
228
|
+
var retry = send_pump_lock > 1
|
|
229
|
+
send_pump_lock = 0
|
|
230
|
+
if (retry) setTimeout(send_pump, 0)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
164
234
|
var a_ops = {
|
|
165
235
|
signal: ac.signal,
|
|
236
|
+
merge_type: 'dt',
|
|
166
237
|
subscribe: update => {
|
|
167
|
-
|
|
168
|
-
update.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
extend_fork_point(update)
|
|
173
|
-
} else if (x.status === 401 || x.status === 403) {
|
|
174
|
-
options.on_unauthorized?.()
|
|
175
|
-
} else throw new Error('failed to PUT: ' + x.status)
|
|
176
|
-
}).catch(e => {
|
|
177
|
-
if (e.name === 'AbortError') {
|
|
178
|
-
// ignore
|
|
179
|
-
} else handle_error(e)
|
|
180
|
-
})
|
|
238
|
+
if (closed) return
|
|
239
|
+
if (update.version?.length) {
|
|
240
|
+
q.push(update)
|
|
241
|
+
send_pump()
|
|
242
|
+
}
|
|
181
243
|
}
|
|
182
244
|
}
|
|
183
245
|
if (resource.meta.fork_point)
|
|
@@ -185,12 +247,30 @@ function create_braid_text() {
|
|
|
185
247
|
braid_text.get(a, a_ops)
|
|
186
248
|
|
|
187
249
|
// remote -> local
|
|
250
|
+
var remote_res_done
|
|
251
|
+
var remote_res_promise = new Promise(done => remote_res_done = done)
|
|
252
|
+
var remote_res = null
|
|
253
|
+
|
|
188
254
|
var b_ops = {
|
|
189
255
|
signal: ac.signal,
|
|
190
256
|
dont_retry: true,
|
|
257
|
+
headers: { 'Merge-Type': 'dt', 'accept-encoding': 'updates(dt)' },
|
|
191
258
|
subscribe: async update => {
|
|
192
|
-
|
|
193
|
-
|
|
259
|
+
// Wait for remote_res to be available
|
|
260
|
+
await remote_res_promise
|
|
261
|
+
|
|
262
|
+
// Check if this is a dt-encoded update (initial body without status)
|
|
263
|
+
if (!update.status) {
|
|
264
|
+
var cv = remote_res.headers.get('current-version')
|
|
265
|
+
await braid_text.put(a, {
|
|
266
|
+
body: update.body,
|
|
267
|
+
transfer_encoding: 'dt'
|
|
268
|
+
})
|
|
269
|
+
if (cv) extend_fork_point({ version: JSON.parse(`[${cv}]`), parents: resource.meta.fork_point || [] })
|
|
270
|
+
} else {
|
|
271
|
+
await braid_text.put(a, update)
|
|
272
|
+
if (update.version) extend_fork_point(update)
|
|
273
|
+
}
|
|
194
274
|
},
|
|
195
275
|
on_error: e => {
|
|
196
276
|
options.on_disconnect?.()
|
|
@@ -198,15 +278,16 @@ function create_braid_text() {
|
|
|
198
278
|
}
|
|
199
279
|
}
|
|
200
280
|
// Handle case where remote doesn't exist yet - wait for local to create it
|
|
201
|
-
|
|
202
|
-
|
|
281
|
+
remote_res = await braid_text.get(b, b_ops)
|
|
282
|
+
remote_res_done()
|
|
283
|
+
if (remote_res === null) {
|
|
203
284
|
// Remote doesn't exist yet, wait for local to put something
|
|
204
285
|
await local_first_put_promise
|
|
205
286
|
disconnect()
|
|
206
287
|
connect()
|
|
207
288
|
return
|
|
208
289
|
}
|
|
209
|
-
options.on_res?.(
|
|
290
|
+
options.on_res?.(remote_res)
|
|
210
291
|
// on_error will call handle_error when connection drops
|
|
211
292
|
} catch (e) {
|
|
212
293
|
handle_error(e)
|
|
@@ -548,9 +629,12 @@ function create_braid_text() {
|
|
|
548
629
|
|
|
549
630
|
if (options.subscribe) {
|
|
550
631
|
res.subscribe(async update => {
|
|
551
|
-
|
|
552
|
-
if (update.
|
|
553
|
-
|
|
632
|
+
// Don't convert to text for initial dt-encoded body (no status)
|
|
633
|
+
if (update.status) {
|
|
634
|
+
update.body = update.body_text
|
|
635
|
+
if (update.patches)
|
|
636
|
+
for (var p of update.patches) p.content = p.content_text
|
|
637
|
+
}
|
|
554
638
|
await options.subscribe(update)
|
|
555
639
|
}, e => options.on_error?.(e))
|
|
556
640
|
|
package/package.json
CHANGED
package/test/tests.js
CHANGED
|
@@ -2066,6 +2066,39 @@ runTest(
|
|
|
2066
2066
|
'on_res called'
|
|
2067
2067
|
)
|
|
2068
2068
|
|
|
2069
|
+
runTest(
|
|
2070
|
+
"test braid_text.sync uses accept-encoding updates(dt)",
|
|
2071
|
+
async () => {
|
|
2072
|
+
var remote_key = 'test-remote-' + Math.random().toString(36).slice(2)
|
|
2073
|
+
var local_key = 'test-local-' + Math.random().toString(36).slice(2)
|
|
2074
|
+
|
|
2075
|
+
// Create the remote resource with some content
|
|
2076
|
+
var r = await braid_fetch(`/${remote_key}`, {
|
|
2077
|
+
method: 'PUT',
|
|
2078
|
+
body: 'remote content here'
|
|
2079
|
+
})
|
|
2080
|
+
if (!r.ok) return 'put failed: ' + r.status
|
|
2081
|
+
|
|
2082
|
+
// Start sync with URL first (like the passing "url to key" test)
|
|
2083
|
+
var r = await braid_fetch(`/eval`, {
|
|
2084
|
+
method: 'PUT',
|
|
2085
|
+
body: `void (async () => {
|
|
2086
|
+
braid_text.sync(new URL('http://localhost:8889/${remote_key}'), '/${local_key}')
|
|
2087
|
+
res.end('')
|
|
2088
|
+
})()`
|
|
2089
|
+
})
|
|
2090
|
+
if (!r.ok) return 'eval failed: ' + r.status
|
|
2091
|
+
|
|
2092
|
+
// Wait for sync to complete
|
|
2093
|
+
await new Promise(done => setTimeout(done, 100))
|
|
2094
|
+
|
|
2095
|
+
// Check local has remote content
|
|
2096
|
+
var r = await braid_fetch(`/${local_key}`)
|
|
2097
|
+
return await r.text()
|
|
2098
|
+
},
|
|
2099
|
+
'remote content here'
|
|
2100
|
+
)
|
|
2101
|
+
|
|
2069
2102
|
runTest(
|
|
2070
2103
|
"test braid_text.sync reconnects when inner put fails with non-200 status",
|
|
2071
2104
|
async () => {
|