@mblaney/holster 1.0.17 → 1.0.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblaney/holster",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Holster is a service that allows synchronising data between devices using Node, Deno, Bun or the browser.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/holster.js CHANGED
@@ -568,25 +568,19 @@ const Holster = opt => {
568
568
  if (id) {
569
569
  // It's a rel, read the related node. It might not be in the graph
570
570
  // yet when the listener fires, so we retry with delays.
571
- const attemptRead = (retries = 0) => {
572
- wire.get(
573
- {"#": id},
574
- relMsg => {
575
- if (relMsg.put && relMsg.put[id]) {
576
- delete relMsg.put[id]._
577
- delete relMsg.put[id][utils.userPublicKey]
578
- delete relMsg.put[id][utils.userSignature]
579
- cb(relMsg.put[id])
580
- } else if (retries < 5) {
581
- // Data not ready yet, retry after a delay
582
- setTimeout(() => attemptRead(retries + 1), 50)
583
- } else {
584
- // Give up after retries
585
- cb(null)
586
- }
587
- },
588
- _opt,
589
- )
571
+ const attemptRead = async (retries = 0) => {
572
+ const data = await new Promise(res => {
573
+ const _ctxid = utils.text.random()
574
+ allctx.set(_ctxid, {chain: [{item: null, soul: id}]})
575
+ api(_ctxid).next(null, res, _opt)
576
+ })
577
+ if (data !== null || retries >= 5) {
578
+ cb(data)
579
+ } else {
580
+ // Data not ready, retry after delay
581
+ await new Promise(resolve => setTimeout(resolve, 50))
582
+ attemptRead(retries + 1)
583
+ }
590
584
  }
591
585
  attemptRead()
592
586
  } else {
package/src/wire.js CHANGED
@@ -15,7 +15,7 @@ if (typeof globalThis.WebSocket === "undefined") {
15
15
  // Rate limiting with throttling
16
16
  const createRateLimiter = isTestEnv => {
17
17
  const clients = new Map()
18
- const maxRequests = 300 // 5 requests per second
18
+ const maxRequests = 1000 // requests per minute
19
19
  const windowMs = 60000 // 1 minute window
20
20
  const disconnectThreshold = 10 // Disconnect after 10 violations
21
21
  let cleanupInterval = null
@@ -277,6 +277,18 @@ const Wire = opt => {
277
277
  )
278
278
  })
279
279
  }
280
+
281
+ // Always broadcast original data to other peers even if no local update
282
+ const sendResult = send(
283
+ JSON.stringify({
284
+ "#": dup.track(utils.text.random(9)),
285
+ put: msg.put,
286
+ }),
287
+ )
288
+ if (sendResult && sendResult.err) {
289
+ console.log(`Error broadcasting put: ${sendResult.err}`)
290
+ }
291
+
280
292
  if (Object.keys(update.defer).length) {
281
293
  setTimeout(() => put({put: update.defer}, send), update.wait)
282
294
  }
@@ -353,30 +365,30 @@ const Wire = opt => {
353
365
  // used whereas wire spec needs to handle clock skew for updates
354
366
  // across the network.
355
367
  const update = await Ham.mix(data, graph, opt.secure, listen)
356
- const none = Object.keys(update.now).length === 0
357
- if (none) {
358
- // Nothing to do.
359
- cb(null)
360
- return
361
- }
362
-
363
- if (!(await check(update.now, send, cb))) return
364
368
 
365
- // Seed pendingReferences with any new references from API calls
366
- for (const [soul, node] of Object.entries(update.now)) {
367
- if (node && typeof node === "object") {
368
- for (const [key, value] of Object.entries(node)) {
369
- const soulId = utils.rel.is(value)
370
- if (soulId) {
371
- // Add referenced soul to pending list
372
- pendingReferences.add(soulId)
369
+ if (Object.keys(update.now).length !== 0) {
370
+ if (!(await check(update.now, send, cb))) return
371
+
372
+ // Seed pendingReferences with any new references from API calls
373
+ for (const [soul, node] of Object.entries(update.now)) {
374
+ if (node && typeof node === "object") {
375
+ for (const [key, value] of Object.entries(node)) {
376
+ const soulId = utils.rel.is(value)
377
+ if (soulId) {
378
+ // Add referenced soul to pending list
379
+ pendingReferences.add(soulId)
380
+ }
373
381
  }
374
382
  }
375
383
  }
384
+
385
+ store.put(update.now, cb)
386
+ } else {
387
+ // No local update needed, but still callback
388
+ if (cb) cb(null)
376
389
  }
377
390
 
378
- store.put(update.now, cb)
379
- // Also put data on the wire spec.
391
+ // Always put data on the wire spec even if no local update needed
380
392
  const sendResult = send(
381
393
  JSON.stringify({
382
394
  "#": dup.track(utils.text.random(9)),
@@ -608,44 +620,51 @@ const Wire = opt => {
608
620
  }
609
621
 
610
622
  // Process next message in queue
611
- const data = messageQueue.shift()
612
- sendToPeers(data)
613
-
614
- // Start timeout for this message now that it's been sent
615
- const msg = JSON.parse(data)
616
- const trackId = msg["#"]
617
- if (trackId && pendingTimeouts.has(trackId)) {
618
- const timeoutConfig = pendingTimeouts.get(trackId)
619
- pendingTimeouts.delete(trackId)
620
-
621
- // Respond to callback with null if no response.
622
- timeoutConfig.timeoutId = setTimeout(() => {
623
- const cb = queue[trackId]
624
- if (cb) {
625
- const id = timeoutConfig.lex["#"]
626
- const ack = {[id]: null}
627
- if (typeof timeoutConfig.lex["."] === "string") {
628
- ack[id] = {[timeoutConfig.lex["."]]: null}
623
+ const data = messageQueue[0]
624
+ const sent = sendToPeers(data)
625
+
626
+ if (sent) {
627
+ // Only remove from queue if successfully sent
628
+ messageQueue.shift()
629
+
630
+ // Start timeout for this message now that it's been sent
631
+ const msg = JSON.parse(data)
632
+ const trackId = msg["#"]
633
+ if (trackId && pendingTimeouts.has(trackId)) {
634
+ const timeoutConfig = pendingTimeouts.get(trackId)
635
+ pendingTimeouts.delete(trackId)
636
+
637
+ // Respond to callback with null if no response.
638
+ timeoutConfig.timeoutId = setTimeout(() => {
639
+ const cb = queue[trackId]
640
+ if (cb) {
641
+ const id = timeoutConfig.lex["#"]
642
+ const ack = {[id]: null}
643
+ if (typeof timeoutConfig.lex["."] === "string") {
644
+ ack[id] = {[timeoutConfig.lex["."]]: null}
645
+ }
646
+ cb({put: ack})
647
+ delete queue[trackId]
629
648
  }
630
- cb({put: ack})
631
- delete queue[trackId]
632
- }
633
- }, timeoutConfig.wait)
649
+ }, timeoutConfig.wait)
650
+ }
634
651
  }
635
652
 
636
653
  // Schedule processing of next message if queue not empty
637
654
  if (messageQueue.length > 0) {
638
- // Add delay between messages to respect rate limits (300 req/min = 200ms)
639
- queueProcessor = setTimeout(processQueue, 250)
655
+ // Add delay between messages to respect rate limit
656
+ queueProcessor = setTimeout(processQueue, 100)
640
657
  } else {
641
658
  queueProcessor = null
642
659
  }
643
660
  }
644
661
 
645
662
  const sendToPeers = data => {
663
+ let sentToAtLeastOne = false
646
664
  peers.forEach(peer => {
647
665
  if (peer && peer.readyState === WebSocket.OPEN) {
648
666
  peer.send(data)
667
+ sentToAtLeastOne = true
649
668
  } else {
650
669
  const retryHandler = peer._retryHandler || createRetryHandler()
651
670
  peer._retryHandler = retryHandler
@@ -663,6 +682,7 @@ const Wire = opt => {
663
682
  }
664
683
  }
665
684
  })
685
+ return sentToAtLeastOne
666
686
  }
667
687
 
668
688
  const send = data => {
@@ -677,7 +697,7 @@ const Wire = opt => {
677
697
  messageQueue.push(data)
678
698
  // Start queue processor if not already running
679
699
  if (!queueProcessor) {
680
- queueProcessor = setTimeout(processQueue, 250)
700
+ queueProcessor = setTimeout(processQueue, 100)
681
701
  }
682
702
  }
683
703
  if (!(opt.peers instanceof Array)) {