@mblaney/holster 2.0.0 → 2.0.2
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/build/holster.js +3 -3
- package/build/holster.js.map +3 -3
- package/dist/holster.js +3 -3
- package/dist/holster.js.map +3 -3
- package/package.json +1 -1
- package/src/holster.js +2 -7
- package/src/radisk.js +0 -16
- package/src/store.js +30 -17
- package/src/wire.js +28 -22
- package/ts/src/holster.ts +2 -6
- package/ts/src/radisk.ts +0 -14
- package/ts/src/store.ts +30 -18
- package/ts/src/wire.ts +30 -22
package/package.json
CHANGED
package/src/holster.js
CHANGED
|
@@ -420,9 +420,7 @@ const Holster = opt => {
|
|
|
420
420
|
// ctx already removed by another chained callback is ok?
|
|
421
421
|
if (!ctx) return
|
|
422
422
|
|
|
423
|
-
if (!ctx.cb) {
|
|
424
|
-
if (!cb) return
|
|
425
|
-
|
|
423
|
+
if (!ctx.cb && cb) {
|
|
426
424
|
// This (and ack) allows nested objects to set their own callbacks.
|
|
427
425
|
ctx.cb = cb
|
|
428
426
|
cb = null
|
|
@@ -762,10 +760,7 @@ const Holster = opt => {
|
|
|
762
760
|
// First remove the initial listener
|
|
763
761
|
wire.off(initialLex, map.get(cb))
|
|
764
762
|
// Then add listener on the related node
|
|
765
|
-
|
|
766
|
-
if (lex) relLex = utils.obj.put(initialLex, "#", id)
|
|
767
|
-
else relLex = {"#": id, ".": null}
|
|
768
|
-
wire.on(relLex, map.get(cb), false, _opt)
|
|
763
|
+
wire.on({"#": id, ".": null}, map.get(cb), false, _opt)
|
|
769
764
|
if (_get) map.get(cb)()
|
|
770
765
|
} else if (_get) {
|
|
771
766
|
// Not a rel, but _get was requested, so trigger callback.
|
package/src/radisk.js
CHANGED
|
@@ -55,16 +55,6 @@ const Radisk = opt => {
|
|
|
55
55
|
return
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// Performance logging
|
|
59
|
-
const perfLog = (operation, startTime, key, size) => {
|
|
60
|
-
const duration = Date.now() - startTime
|
|
61
|
-
if (duration > 2000) {
|
|
62
|
-
console.log(
|
|
63
|
-
`[RADISK-SLOW] ${operation}: ${duration}ms for ${key}${size ? ` (${size} bytes)` : ""}`,
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
58
|
// Memory monitoring and cache cleanup
|
|
69
59
|
const checkMemoryUsage = () => {
|
|
70
60
|
if (typeof process === "undefined" || !process.memoryUsage) return
|
|
@@ -129,7 +119,6 @@ const Radisk = opt => {
|
|
|
129
119
|
}
|
|
130
120
|
|
|
131
121
|
pendingReads.set(key, [cb])
|
|
132
|
-
const readStart = Date.now()
|
|
133
122
|
// If radisk.read genuinely hangs (fd leak, stuck I/O), recover
|
|
134
123
|
// callbacks as a cache miss so callers can fall through to the wire path.
|
|
135
124
|
// 30s is long enough to distinguish a hang from a slow-but-healthy read.
|
|
@@ -142,7 +131,6 @@ const Radisk = opt => {
|
|
|
142
131
|
}, 30000)
|
|
143
132
|
return radisk.read(key, (err, result) => {
|
|
144
133
|
clearTimeout(timeout)
|
|
145
|
-
perfLog("read", readStart, key)
|
|
146
134
|
const callbacks = pendingReads.get(key) || []
|
|
147
135
|
pendingReads.delete(key)
|
|
148
136
|
callbacks.forEach(callback => callback(err, result))
|
|
@@ -175,7 +163,6 @@ const Radisk = opt => {
|
|
|
175
163
|
return (radisk.thrash.more = true)
|
|
176
164
|
}
|
|
177
165
|
|
|
178
|
-
const thrashStart = Date.now()
|
|
179
166
|
clearTimeout(radisk.batch.timeout)
|
|
180
167
|
radisk.thrash.more = false
|
|
181
168
|
radisk.thrash.ing = true
|
|
@@ -191,7 +178,6 @@ const Radisk = opt => {
|
|
|
191
178
|
// file needs to be split.
|
|
192
179
|
if (++i > 1) return
|
|
193
180
|
|
|
194
|
-
perfLog("thrash", thrashStart, `batch-${batch.ed}`)
|
|
195
181
|
if (err) opt.log(err)
|
|
196
182
|
batch.acks.forEach(cb => cb(err))
|
|
197
183
|
radisk.thrash.at = null
|
|
@@ -350,7 +336,6 @@ const Radisk = opt => {
|
|
|
350
336
|
Radix.map(rad, write.each, true)
|
|
351
337
|
// There is always accumulated write.text to store once write.each has
|
|
352
338
|
// finished.
|
|
353
|
-
const writeStart = Date.now()
|
|
354
339
|
opt.store.put(file, write.text, err => {
|
|
355
340
|
// If a split occurred the cached radix tree still contains entries that
|
|
356
341
|
// were moved to the sub-file. Invalidate so the next save.mix re-reads
|
|
@@ -359,7 +344,6 @@ const Radisk = opt => {
|
|
|
359
344
|
cache.delete(file)
|
|
360
345
|
fileListCache = null
|
|
361
346
|
}
|
|
362
|
-
perfLog("file-write", writeStart, file, write.text.length)
|
|
363
347
|
cb(err)
|
|
364
348
|
})
|
|
365
349
|
}
|
package/src/store.js
CHANGED
|
@@ -197,7 +197,7 @@ const Store = opt => {
|
|
|
197
197
|
const radisk = Radisk(opt)
|
|
198
198
|
|
|
199
199
|
return {
|
|
200
|
-
get: (lex, cb
|
|
200
|
+
get: (lex, cb) => {
|
|
201
201
|
if (!lex || !utils.obj.is(lex)) {
|
|
202
202
|
cb("lex required")
|
|
203
203
|
return
|
|
@@ -207,41 +207,54 @@ const Store = opt => {
|
|
|
207
207
|
return
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
if (!_opt) _opt = {}
|
|
211
|
-
|
|
212
210
|
var soul = lex["#"]
|
|
213
|
-
var key =
|
|
211
|
+
var key = typeof lex["."] === "string" ? lex["."] : ""
|
|
214
212
|
var node
|
|
215
213
|
var signatures = {}
|
|
216
|
-
const each = (value,
|
|
217
|
-
|
|
218
|
-
if (key !== utils.userPublicKey && !utils.match(lex["."], key)) return
|
|
214
|
+
const each = (value, k) => {
|
|
215
|
+
if (k !== utils.userPublicKey && !utils.match(lex["."], k)) return
|
|
219
216
|
|
|
220
217
|
if (!node) node = {_: {"#": soul, ">": {}}}
|
|
221
|
-
node[
|
|
222
|
-
node._[">"][
|
|
218
|
+
node[k] = value[0]
|
|
219
|
+
node._[">"][k] = value[1]
|
|
223
220
|
// If signature is present, store it in _["s"]
|
|
224
221
|
if (value.length === 3 && value[2]) {
|
|
225
222
|
signatures[value[1]] = value[2]
|
|
226
223
|
}
|
|
227
224
|
}
|
|
228
225
|
|
|
226
|
+
// When doing a targeted property lookup, also fetch userPublicKey in
|
|
227
|
+
// parallel so ham.js can verify signatures regardless of secure mode.
|
|
228
|
+
// Full-soul scans (key="") already surface it via Radix.map.
|
|
229
|
+
const needsPubKey = key !== "" && key !== utils.userPublicKey
|
|
230
|
+
let pending = needsPubKey ? 2 : 1
|
|
231
|
+
let cbErr = null
|
|
232
|
+
const finish = err => {
|
|
233
|
+
if (err && !cbErr) cbErr = err
|
|
234
|
+
if (--pending > 0) return
|
|
235
|
+
const graph = {[soul]: node}
|
|
236
|
+
if (node && Object.keys(signatures).length > 0) {
|
|
237
|
+
node._["s"] = signatures
|
|
238
|
+
}
|
|
239
|
+
cb(cbErr, graph)
|
|
240
|
+
}
|
|
241
|
+
|
|
229
242
|
radisk(soul + enq + key, (err, value) => {
|
|
230
|
-
let graph
|
|
231
243
|
if (utils.obj.is(value)) {
|
|
232
244
|
Radix.map(value, each)
|
|
233
245
|
if (!node) each(value, key)
|
|
234
|
-
graph = {[soul]: node}
|
|
235
246
|
} else if (value) {
|
|
236
247
|
each(value, key)
|
|
237
|
-
graph = {[soul]: node}
|
|
238
248
|
}
|
|
239
|
-
|
|
240
|
-
if (graph && graph[soul] && Object.keys(signatures).length > 0) {
|
|
241
|
-
graph[soul]._["s"] = signatures
|
|
242
|
-
}
|
|
243
|
-
cb(err, graph)
|
|
249
|
+
finish(err)
|
|
244
250
|
})
|
|
251
|
+
|
|
252
|
+
if (needsPubKey) {
|
|
253
|
+
radisk(soul + enq + utils.userPublicKey, (err, value) => {
|
|
254
|
+
if (value && !utils.obj.is(value)) each(value, utils.userPublicKey)
|
|
255
|
+
finish(err)
|
|
256
|
+
})
|
|
257
|
+
}
|
|
245
258
|
},
|
|
246
259
|
put: (graph, cb) => {
|
|
247
260
|
if (!graph) {
|
package/src/wire.js
CHANGED
|
@@ -206,7 +206,7 @@ const Wire = opt => {
|
|
|
206
206
|
|
|
207
207
|
// Initialize rate limiting and connection management
|
|
208
208
|
// Check if test environment (mock-socket usage)
|
|
209
|
-
const isTestEnv =
|
|
209
|
+
const isTestEnv = isNode && globalThis.WebSocket !== wsModule?.WebSocket
|
|
210
210
|
const rateLimiter = createRateLimiter(isTestEnv)
|
|
211
211
|
const connectionManager = createConnectionManager(opt.maxConnections || 1000)
|
|
212
212
|
|
|
@@ -270,22 +270,16 @@ const Wire = opt => {
|
|
|
270
270
|
}),
|
|
271
271
|
)
|
|
272
272
|
} else {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
err: err,
|
|
284
|
-
}),
|
|
285
|
-
)
|
|
286
|
-
},
|
|
287
|
-
{secure: true},
|
|
288
|
-
)
|
|
273
|
+
store.get(msg.get, (err, ack) => {
|
|
274
|
+
send(
|
|
275
|
+
JSON.stringify({
|
|
276
|
+
"#": dup.track(utils.text.random(9)),
|
|
277
|
+
"@": msg["#"],
|
|
278
|
+
put: ack,
|
|
279
|
+
err: err,
|
|
280
|
+
}),
|
|
281
|
+
)
|
|
282
|
+
})
|
|
289
283
|
}
|
|
290
284
|
}
|
|
291
285
|
|
|
@@ -467,9 +461,7 @@ const Wire = opt => {
|
|
|
467
461
|
pendingTimeouts.delete(track)
|
|
468
462
|
return
|
|
469
463
|
}
|
|
470
|
-
}
|
|
471
|
-
_opt,
|
|
472
|
-
)
|
|
464
|
+
})
|
|
473
465
|
}
|
|
474
466
|
|
|
475
467
|
const api = send => {
|
|
@@ -726,6 +718,7 @@ const Wire = opt => {
|
|
|
726
718
|
let clientThrottled = false
|
|
727
719
|
let throttleUntil = 0
|
|
728
720
|
let messageQueue = []
|
|
721
|
+
let offlinePuts = []
|
|
729
722
|
let queueProcessor = null
|
|
730
723
|
const maxQueueLength = opt.maxQueueLength || 10000
|
|
731
724
|
|
|
@@ -783,6 +776,11 @@ const Wire = opt => {
|
|
|
783
776
|
|
|
784
777
|
if (sent || isStaleCheckMessage) {
|
|
785
778
|
messageQueue.shift()
|
|
779
|
+
} else if (!sent && msg.put) {
|
|
780
|
+
// Hold offline PUTs separately so they don't block GET messages behind
|
|
781
|
+
// them. They'll be flushed back to the front of the queue on reconnect.
|
|
782
|
+
messageQueue.shift()
|
|
783
|
+
offlinePuts.push(data)
|
|
786
784
|
}
|
|
787
785
|
|
|
788
786
|
if (trackId && pendingTimeouts.has(trackId)) {
|
|
@@ -864,14 +862,22 @@ const Wire = opt => {
|
|
|
864
862
|
ws = null
|
|
865
863
|
|
|
866
864
|
if (retryHandler.shouldRetry()) {
|
|
867
|
-
const delay = retryHandler.getDelay()
|
|
865
|
+
const delay = isTestEnv ? 50 : retryHandler.getDelay()
|
|
868
866
|
retryHandler.increment()
|
|
869
|
-
setTimeout(start, delay)
|
|
867
|
+
const t = setTimeout(start, delay)
|
|
868
|
+
if (t.unref) t.unref()
|
|
870
869
|
}
|
|
871
870
|
}
|
|
872
871
|
|
|
873
872
|
ws.onopen = () => {
|
|
874
873
|
retryHandler.reset()
|
|
874
|
+
if (offlinePuts.length > 0) {
|
|
875
|
+
messageQueue = offlinePuts.concat(messageQueue)
|
|
876
|
+
offlinePuts = []
|
|
877
|
+
if (!queueProcessor) {
|
|
878
|
+
queueProcessor = setTimeout(processQueue, 50)
|
|
879
|
+
}
|
|
880
|
+
}
|
|
875
881
|
}
|
|
876
882
|
|
|
877
883
|
ws.onerror = e => {
|
package/ts/src/holster.ts
CHANGED
|
@@ -521,8 +521,7 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
521
521
|
const ctx = allctx.get(ctxid!)
|
|
522
522
|
if (!ctx) return
|
|
523
523
|
|
|
524
|
-
if (!ctx.cb) {
|
|
525
|
-
if (!callback) return
|
|
524
|
+
if (!ctx.cb && callback) {
|
|
526
525
|
ctx.cb = callback as never
|
|
527
526
|
callback = undefined
|
|
528
527
|
}
|
|
@@ -845,10 +844,7 @@ const Holster = (opt?: HolsterOptions | string | string[]): HolsterAPI => {
|
|
|
845
844
|
const id = utils.rel.is(current as GraphValue)
|
|
846
845
|
if (id) {
|
|
847
846
|
wire.off(initialLex as never, map.get(callback)!)
|
|
848
|
-
|
|
849
|
-
? utils.obj.put(initialLex as never, "#", id)
|
|
850
|
-
: { "#": id, ".": null }) as Lex
|
|
851
|
-
wire.on(relLex, map.get(callback)!, false, opts)
|
|
847
|
+
wire.on({ "#": id, ".": null } as never, map.get(callback)!, false, opts)
|
|
852
848
|
if (_get) map.get(callback)!()
|
|
853
849
|
} else if (_get) {
|
|
854
850
|
// Not a rel, but _get was requested, so trigger callback.
|
package/ts/src/radisk.ts
CHANGED
|
@@ -82,14 +82,6 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
82
82
|
// After validation, store is guaranteed to be defined
|
|
83
83
|
const store = options.store
|
|
84
84
|
|
|
85
|
-
const perfLog = (operation: string, startTime: number, key: string, size?: number): void => {
|
|
86
|
-
const duration = Date.now() - startTime
|
|
87
|
-
if (duration > 2000) {
|
|
88
|
-
console.log(
|
|
89
|
-
`[RADISK-SLOW] ${operation}: ${duration}ms for ${key}${size ? ` (${size} bytes)` : ""}`
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
85
|
|
|
94
86
|
const checkMemoryUsage = (): void => {
|
|
95
87
|
if (typeof process === "undefined" || !process.memoryUsage) return
|
|
@@ -143,7 +135,6 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
143
135
|
}
|
|
144
136
|
|
|
145
137
|
pendingReads.set(key, [callback])
|
|
146
|
-
const readStart = Date.now()
|
|
147
138
|
const timeout = setTimeout(() => {
|
|
148
139
|
const callbacks = pendingReads.get(key)
|
|
149
140
|
if (!callbacks) return
|
|
@@ -153,7 +144,6 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
153
144
|
}, 30000)
|
|
154
145
|
return radisk.read(key, (err, result) => {
|
|
155
146
|
clearTimeout(timeout)
|
|
156
|
-
perfLog("read", readStart, key)
|
|
157
147
|
const callbacks = pendingReads.get(key) || []
|
|
158
148
|
pendingReads.delete(key)
|
|
159
149
|
callbacks.forEach(callback => callback(err, result))
|
|
@@ -184,7 +174,6 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
184
174
|
return
|
|
185
175
|
}
|
|
186
176
|
|
|
187
|
-
const thrashStart = Date.now()
|
|
188
177
|
clearTimeout(radisk.batch.timeout)
|
|
189
178
|
radisk.thrash.more = false
|
|
190
179
|
radisk.thrash.ing = true
|
|
@@ -196,7 +185,6 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
196
185
|
radisk.save(batch, err => {
|
|
197
186
|
if (++i > 1) return
|
|
198
187
|
|
|
199
|
-
perfLog("thrash", thrashStart, `batch-${batch.ed}`)
|
|
200
188
|
if (err) options.log(err)
|
|
201
189
|
batch.acks.forEach(cb => cb(err))
|
|
202
190
|
radisk.thrash.at = undefined
|
|
@@ -337,7 +325,6 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
337
325
|
},
|
|
338
326
|
}
|
|
339
327
|
Radix.map(rad, write.each, true)
|
|
340
|
-
const writeStart = Date.now()
|
|
341
328
|
store.put(file, write.text, err => {
|
|
342
329
|
// If a split occurred the cached radix tree still contains entries that
|
|
343
330
|
// were moved to the sub-file. Invalidate so the next save.mix re-reads
|
|
@@ -346,7 +333,6 @@ const Radisk = (opt: RadiskOptions) => {
|
|
|
346
333
|
cache.delete(file)
|
|
347
334
|
fileListCache = null
|
|
348
335
|
}
|
|
349
|
-
perfLog("file-write", writeStart, file, write.text.length)
|
|
350
336
|
cb(err)
|
|
351
337
|
})
|
|
352
338
|
}
|
package/ts/src/store.ts
CHANGED
|
@@ -212,7 +212,6 @@ export interface StoreInterface {
|
|
|
212
212
|
get: (
|
|
213
213
|
lex: Lex,
|
|
214
214
|
cb: (err?: string | null, graph?: Graph) => void,
|
|
215
|
-
_opt?: { secure?: boolean }
|
|
216
215
|
) => void
|
|
217
216
|
put: (graph: Graph, cb: (err?: string | null) => void) => void
|
|
218
217
|
}
|
|
@@ -227,7 +226,7 @@ const Store = (opt?: StoreOptions): StoreInterface => {
|
|
|
227
226
|
const radisk = Radisk(options as never)
|
|
228
227
|
|
|
229
228
|
return {
|
|
230
|
-
get: (lex, cb
|
|
229
|
+
get: (lex, cb) => {
|
|
231
230
|
if (!lex || !utils.obj.is(lex)) {
|
|
232
231
|
cb("lex required")
|
|
233
232
|
return
|
|
@@ -237,42 +236,55 @@ const Store = (opt?: StoreOptions): StoreInterface => {
|
|
|
237
236
|
return
|
|
238
237
|
}
|
|
239
238
|
|
|
240
|
-
const opts = _opt || {}
|
|
241
239
|
const soul = lex["#"]
|
|
242
|
-
const key =
|
|
243
|
-
!opts.secure && typeof lex["."] === "string" ? lex["."] : ""
|
|
240
|
+
const key = typeof lex["."] === "string" ? lex["."] : ""
|
|
244
241
|
let node: GraphNode | undefined
|
|
245
242
|
const signatures: Record<string, string> = {}
|
|
246
243
|
|
|
247
|
-
const each = (value: EncodedValue,
|
|
248
|
-
|
|
249
|
-
if (key !== utils.userPublicKey && !utils.match(lex["."], key)) return
|
|
244
|
+
const each = (value: EncodedValue, k: string): void => {
|
|
245
|
+
if (k !== utils.userPublicKey && !utils.match(lex["."], k)) return
|
|
250
246
|
|
|
251
247
|
if (!node) node = { _: { "#": soul, ">": {} } }
|
|
252
|
-
node[
|
|
253
|
-
node._[">"][
|
|
248
|
+
node[k] = value[0]
|
|
249
|
+
node._[">"][k] = value[1]
|
|
254
250
|
// If signature is present, store it in _["s"]
|
|
255
251
|
if (value.length === 3 && value[2]) {
|
|
256
252
|
signatures[value[1].toString()] = value[2]
|
|
257
253
|
}
|
|
258
254
|
}
|
|
259
255
|
|
|
256
|
+
// When doing a targeted property lookup, also fetch userPublicKey in
|
|
257
|
+
// parallel so ham.js can verify signatures regardless of secure mode.
|
|
258
|
+
// Full-soul scans (key="") already surface it via Radix.map.
|
|
259
|
+
const needsPubKey = key !== "" && key !== utils.userPublicKey
|
|
260
|
+
let pending = needsPubKey ? 2 : 1
|
|
261
|
+
let cbErr: string | null = null
|
|
262
|
+
const finish = (err: string | null | undefined): void => {
|
|
263
|
+
if (err && !cbErr) cbErr = err
|
|
264
|
+
if (--pending > 0) return
|
|
265
|
+
const graph: Graph = { [soul]: node! }
|
|
266
|
+
if (node && Object.keys(signatures).length > 0) {
|
|
267
|
+
node._["s"] = signatures
|
|
268
|
+
}
|
|
269
|
+
cb(cbErr, graph)
|
|
270
|
+
}
|
|
271
|
+
|
|
260
272
|
radisk(soul + enq + key, (err, value) => {
|
|
261
|
-
let graph: Graph | undefined
|
|
262
273
|
if (utils.obj.is(value)) {
|
|
263
274
|
Radix.map(value as never, each)
|
|
264
275
|
if (!node) each(value as never, key)
|
|
265
|
-
graph = { [soul]: node! }
|
|
266
276
|
} else if (value) {
|
|
267
277
|
each(value as EncodedValue, key)
|
|
268
|
-
graph = { [soul]: node! }
|
|
269
|
-
}
|
|
270
|
-
// Only add _["s"] if we found any signatures
|
|
271
|
-
if (graph && graph[soul] && Object.keys(signatures).length > 0) {
|
|
272
|
-
graph[soul]._["s"] = signatures
|
|
273
278
|
}
|
|
274
|
-
|
|
279
|
+
finish(err)
|
|
275
280
|
})
|
|
281
|
+
|
|
282
|
+
if (needsPubKey) {
|
|
283
|
+
radisk(soul + enq + utils.userPublicKey, (err, value) => {
|
|
284
|
+
if (value && !utils.obj.is(value)) each(value as EncodedValue, utils.userPublicKey)
|
|
285
|
+
finish(err)
|
|
286
|
+
})
|
|
287
|
+
}
|
|
276
288
|
},
|
|
277
289
|
put: (graph, cb) => {
|
|
278
290
|
if (!graph) {
|
package/ts/src/wire.ts
CHANGED
|
@@ -246,7 +246,7 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
246
246
|
})
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
const isTestEnv =
|
|
249
|
+
const isTestEnv = isNode && (globalThis.WebSocket as unknown) !== wsModule?.WebSocket
|
|
250
250
|
const rateLimiter = createRateLimiter(isTestEnv)
|
|
251
251
|
const connectionManager = createConnectionManager(options.maxConnections || 1000)
|
|
252
252
|
|
|
@@ -303,20 +303,16 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
303
303
|
})
|
|
304
304
|
)
|
|
305
305
|
} else {
|
|
306
|
-
store.get(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
)
|
|
317
|
-
},
|
|
318
|
-
{ secure: true }
|
|
319
|
-
)
|
|
306
|
+
store.get(msg.get, (err, ack) => {
|
|
307
|
+
send(
|
|
308
|
+
JSON.stringify({
|
|
309
|
+
"#": dup.track(utils.text.random(9)),
|
|
310
|
+
"@": msg["#"],
|
|
311
|
+
put: ack,
|
|
312
|
+
err: err,
|
|
313
|
+
})
|
|
314
|
+
)
|
|
315
|
+
})
|
|
320
316
|
}
|
|
321
317
|
}
|
|
322
318
|
|
|
@@ -492,9 +488,7 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
492
488
|
pendingTimeouts.delete(track)
|
|
493
489
|
return
|
|
494
490
|
}
|
|
495
|
-
}
|
|
496
|
-
opts
|
|
497
|
-
)
|
|
491
|
+
})
|
|
498
492
|
}
|
|
499
493
|
|
|
500
494
|
const api = (send: (msg: string) => { err?: string } | void): WireAPI => {
|
|
@@ -603,7 +597,7 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
603
597
|
if (typeof lex["."] === "string") {
|
|
604
598
|
ack[id] = { [lex["."]]: null } as never
|
|
605
599
|
}
|
|
606
|
-
cb({
|
|
600
|
+
cb({ put: ack })
|
|
607
601
|
delete queue[trackId]
|
|
608
602
|
}
|
|
609
603
|
}, isTestEnv ? 100 : 10000)
|
|
@@ -734,6 +728,7 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
734
728
|
let clientThrottled = false
|
|
735
729
|
let throttleUntil = 0
|
|
736
730
|
let messageQueue: string[] = []
|
|
731
|
+
let offlinePuts: string[] = []
|
|
737
732
|
let queueProcessor: NodeJS.Timeout | null = null
|
|
738
733
|
const maxQueueLength = options.maxQueueLength || 10000
|
|
739
734
|
|
|
@@ -748,7 +743,7 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
748
743
|
if (typeof lex["."] === "string") {
|
|
749
744
|
ack[id] = { [lex["."]]: null } as never
|
|
750
745
|
}
|
|
751
|
-
cb({
|
|
746
|
+
cb({ put: ack })
|
|
752
747
|
delete queue[trackId]
|
|
753
748
|
}
|
|
754
749
|
}, delay)
|
|
@@ -784,6 +779,11 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
784
779
|
|
|
785
780
|
if (sent || isStaleCheckMessage) {
|
|
786
781
|
messageQueue.shift()
|
|
782
|
+
} else if (!sent && msg.put) {
|
|
783
|
+
// Hold offline PUTs separately so they don't block GET messages behind
|
|
784
|
+
// them. They'll be flushed back to the front of the queue on reconnect.
|
|
785
|
+
messageQueue.shift()
|
|
786
|
+
offlinePuts.push(data)
|
|
787
787
|
}
|
|
788
788
|
|
|
789
789
|
if (trackId && pendingTimeouts.has(trackId)) {
|
|
@@ -863,14 +863,22 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
863
863
|
ws = null
|
|
864
864
|
|
|
865
865
|
if (retryHandler.shouldRetry()) {
|
|
866
|
-
const delay = retryHandler.getDelay()
|
|
866
|
+
const delay = isTestEnv ? 50 : retryHandler.getDelay()
|
|
867
867
|
retryHandler.increment()
|
|
868
|
-
setTimeout(start, delay)
|
|
868
|
+
const t = setTimeout(start, delay)
|
|
869
|
+
if ((t as unknown as { unref?: () => void }).unref) (t as unknown as { unref: () => void }).unref()
|
|
869
870
|
}
|
|
870
871
|
}
|
|
871
872
|
|
|
872
873
|
ws.onopen = () => {
|
|
873
874
|
retryHandler.reset()
|
|
875
|
+
if (offlinePuts.length > 0) {
|
|
876
|
+
messageQueue = offlinePuts.concat(messageQueue)
|
|
877
|
+
offlinePuts = []
|
|
878
|
+
if (!queueProcessor) {
|
|
879
|
+
queueProcessor = setTimeout(processQueue, 50)
|
|
880
|
+
}
|
|
881
|
+
}
|
|
874
882
|
}
|
|
875
883
|
|
|
876
884
|
ws.onerror = (e: Event) => {
|