@mblaney/holster 1.0.28 → 1.0.30
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/package.json +1 -1
- package/src/get.js +14 -0
- package/src/ham.js +11 -21
- package/src/holster.js +11 -5
- package/src/user.js +1 -1
- package/src/utils.js +3 -0
- package/src/wire.js +39 -17
- package/test/holster.on.test.js +47 -23
- package/test/holster.user.on.test.js +40 -17
- package/test/system/concurrent-listeners.test.js +5 -4
- package/test/system/listener-before-data.test.js +2 -1
- package/test/system/user-concurrent-listeners.test.js +4 -3
- package/test/system/user-listener-before-data.test.js +2 -1
package/package.json
CHANGED
package/src/get.js
CHANGED
|
@@ -32,6 +32,20 @@ const Get = (lex, graph) => {
|
|
|
32
32
|
signatures[key] = graph[soul]._["s"][key]
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
// Always include userPublicKey for verification, regardless of filter
|
|
37
|
+
if (userPublicKey && typeof graph[soul][userPublicKey] !== "undefined") {
|
|
38
|
+
node[userPublicKey] = graph[soul][userPublicKey]
|
|
39
|
+
node._[">"][userPublicKey] = graph[soul]._[">"][userPublicKey]
|
|
40
|
+
const pkState = graph[soul]._[">"][userPublicKey]
|
|
41
|
+
if (graph[soul]._["s"]) {
|
|
42
|
+
if (graph[soul]._["s"][pkState]) {
|
|
43
|
+
signatures[pkState] = graph[soul]._["s"][pkState]
|
|
44
|
+
} else if (graph[soul]._["s"][userPublicKey]) {
|
|
45
|
+
signatures[userPublicKey] = graph[soul]._["s"][userPublicKey]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
35
49
|
} else {
|
|
36
50
|
for (const key of Object.keys(graph[soul])) {
|
|
37
51
|
// Always include userPublicKey for verification, regardless of filter
|
package/src/ham.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as utils from "./utils.js"
|
|
2
2
|
import SEA from "./sea.js"
|
|
3
3
|
|
|
4
|
-
const LISTENER_DELAY = 100
|
|
5
4
|
// Maximum number of souls to keep in memory
|
|
6
5
|
const MAX_GRAPH_SIZE = 10000
|
|
7
6
|
|
|
@@ -51,6 +50,7 @@ Ham.mix = async (change, graph, secure, listen) => {
|
|
|
51
50
|
const machine = Date.now()
|
|
52
51
|
const now = {}
|
|
53
52
|
const defer = {}
|
|
53
|
+
const listeners = [] // Collect listeners to fire after store.put
|
|
54
54
|
const validProperties = new Map() // Track valid properties per soul
|
|
55
55
|
const validTimestamps = new Map() // Track valid timestamps per soul
|
|
56
56
|
let wait = 0
|
|
@@ -130,9 +130,6 @@ Ham.mix = async (change, graph, secure, listen) => {
|
|
|
130
130
|
for (const key of Object.keys(node)) {
|
|
131
131
|
if (key === "_") continue
|
|
132
132
|
|
|
133
|
-
// Skip metadata fields (including userSignature for old data)
|
|
134
|
-
if (key === utils.userSignature || key === utils.userPublicKey) continue
|
|
135
|
-
|
|
136
133
|
// Properties without signatures in this update are left unchanged
|
|
137
134
|
if (validProperties.has(soul) && !validProperties.get(soul).has(key)) {
|
|
138
135
|
continue
|
|
@@ -190,30 +187,23 @@ Ham.mix = async (change, graph, secure, listen) => {
|
|
|
190
187
|
graph[soul]._["s"][key] = now[soul]._["s"][key] = node._["s"][key]
|
|
191
188
|
}
|
|
192
189
|
}
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
// updates on a node.
|
|
190
|
+
// Collect event listeners to fire after store.put completes.
|
|
191
|
+
// This ensures listeners always fire with data that's been stored.
|
|
196
192
|
if (listen[soul]) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.filter(l => utils.match(l["."], key))
|
|
201
|
-
.forEach(l => l.cb())
|
|
202
|
-
}
|
|
203
|
-
}, LISTENER_DELAY)
|
|
193
|
+
listen[soul]
|
|
194
|
+
.filter(l => utils.match(l["."], key))
|
|
195
|
+
.forEach(l => listeners.push(l.cb))
|
|
204
196
|
}
|
|
205
197
|
updated = true
|
|
206
198
|
}
|
|
207
199
|
}
|
|
208
200
|
}
|
|
209
201
|
|
|
210
|
-
//
|
|
202
|
+
// Collect event listeners for update on soul.
|
|
211
203
|
if (updated && listen[soul]) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
}, LISTENER_DELAY)
|
|
204
|
+
listen[soul]
|
|
205
|
+
.filter(l => utils.match(l["."]))
|
|
206
|
+
.forEach(l => listeners.push(l.cb))
|
|
217
207
|
}
|
|
218
208
|
}
|
|
219
209
|
|
|
@@ -231,7 +221,7 @@ Ham.mix = async (change, graph, secure, listen) => {
|
|
|
231
221
|
remove.forEach(({soul}) => delete graph[soul])
|
|
232
222
|
}
|
|
233
223
|
|
|
234
|
-
return {now: now, defer: defer, wait: wait}
|
|
224
|
+
return {now: now, defer: defer, wait: wait, listeners: listeners}
|
|
235
225
|
}
|
|
236
226
|
|
|
237
227
|
export default Ham
|
package/src/holster.js
CHANGED
|
@@ -522,8 +522,8 @@ const Holster = opt => {
|
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
// The nested objects created above will also have rels on the
|
|
525
|
-
// parent object, so fetch the node so that the rest of the
|
|
526
|
-
// can be added.
|
|
525
|
+
// parent object, so fetch the node so that the rest of the
|
|
526
|
+
// updates can be added.
|
|
527
527
|
wire.get({"#": id}, async msg => {
|
|
528
528
|
if (msg.err) {
|
|
529
529
|
_ack(`error getting ${id}: ${msg.err}`)
|
|
@@ -673,17 +673,23 @@ const Holster = opt => {
|
|
|
673
673
|
const {item, soul} = resolve({off: true}, cb)
|
|
674
674
|
if (!soul) return
|
|
675
675
|
|
|
676
|
-
//
|
|
676
|
+
// Remove listener immediately from the original soul to avoid race condition.
|
|
677
|
+
// The listener may be on a related soul if item is a rel, but we remove
|
|
678
|
+
// from the original soul first since that's where it's initially registered.
|
|
679
|
+
wire.off({"#": soul}, map.get(cb))
|
|
680
|
+
|
|
681
|
+
// Check if item is a rel and remove from the related node as well.
|
|
677
682
|
wire.get({"#": soul, ".": item}, msg => {
|
|
678
683
|
if (msg.err) {
|
|
679
|
-
|
|
684
|
+
// Even if get fails, we've already removed from original soul
|
|
685
|
+
map.delete(cb)
|
|
686
|
+
allctx.delete(ctxid)
|
|
680
687
|
return
|
|
681
688
|
}
|
|
682
689
|
|
|
683
690
|
const current = msg.put && msg.put[soul] && msg.put[soul][item]
|
|
684
691
|
const id = utils.rel.is(current)
|
|
685
692
|
if (id) wire.off({"#": id}, map.get(cb))
|
|
686
|
-
else wire.off({"#": soul}, map.get(cb))
|
|
687
693
|
map.delete(cb)
|
|
688
694
|
allctx.delete(ctxid)
|
|
689
695
|
})
|
package/src/user.js
CHANGED
package/src/utils.js
CHANGED
|
@@ -118,6 +118,9 @@ export const match = (lex, key) => {
|
|
|
118
118
|
|
|
119
119
|
if (typeof lex === "string") return lex === key
|
|
120
120
|
|
|
121
|
+
// Array support: check if key is in the array
|
|
122
|
+
if (Array.isArray(lex)) return lex.includes(key)
|
|
123
|
+
|
|
121
124
|
if (!obj.is(lex) || !key) return false
|
|
122
125
|
|
|
123
126
|
const prefix = lex["*"]
|
package/src/wire.js
CHANGED
|
@@ -203,9 +203,11 @@ const Wire = opt => {
|
|
|
203
203
|
// stored. To avoid this, the current data on a soul needs to be checked to
|
|
204
204
|
// make sure the stored public key matches the one provided with the update.
|
|
205
205
|
const check = async (data, send, cb) => {
|
|
206
|
+
const key = utils.userPublicKey
|
|
207
|
+
|
|
206
208
|
for (const soul of Object.keys(data)) {
|
|
207
209
|
const msg = await new Promise(res => {
|
|
208
|
-
getWithCallback({"#": soul}, res, send)
|
|
210
|
+
getWithCallback({"#": soul, ".": key}, res, send)
|
|
209
211
|
})
|
|
210
212
|
if (msg.err) {
|
|
211
213
|
if (cb) cb(msg.err)
|
|
@@ -213,12 +215,18 @@ const Wire = opt => {
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
const node = data[soul]
|
|
216
|
-
const key = utils.userPublicKey
|
|
217
218
|
// If there is no current node then the data is ok to write without
|
|
218
219
|
// matching public keys, as the provided soul also needs a rel on the
|
|
219
|
-
// parent node which then also requires checking. Otherwise
|
|
220
|
-
// need to match for existing data.
|
|
221
|
-
|
|
220
|
+
// parent node which then also requires checking. Otherwise public keys
|
|
221
|
+
// need to match for existing data. If the incoming update doesn't
|
|
222
|
+
// include a userPublicKey property at all, allow it - the update is
|
|
223
|
+
// not trying to change the public key.
|
|
224
|
+
if (
|
|
225
|
+
!msg.put ||
|
|
226
|
+
!msg.put[soul] ||
|
|
227
|
+
node[key] === undefined ||
|
|
228
|
+
msg.put[soul][key] === node[key]
|
|
229
|
+
) {
|
|
222
230
|
continue
|
|
223
231
|
}
|
|
224
232
|
|
|
@@ -248,16 +256,22 @@ const Wire = opt => {
|
|
|
248
256
|
}),
|
|
249
257
|
)
|
|
250
258
|
} else {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
// Always pass secure to ensure public key is fetched. User data may be
|
|
260
|
+
// requested that requires the public key for verification.
|
|
261
|
+
store.get(
|
|
262
|
+
msg.get,
|
|
263
|
+
(err, ack) => {
|
|
264
|
+
send(
|
|
265
|
+
JSON.stringify({
|
|
266
|
+
"#": dup.track(utils.text.random(9)),
|
|
267
|
+
"@": msg["#"],
|
|
268
|
+
put: ack,
|
|
269
|
+
err: err,
|
|
270
|
+
}),
|
|
271
|
+
)
|
|
272
|
+
},
|
|
273
|
+
{secure: true},
|
|
274
|
+
)
|
|
261
275
|
}
|
|
262
276
|
}
|
|
263
277
|
|
|
@@ -285,6 +299,8 @@ const Wire = opt => {
|
|
|
285
299
|
err: err,
|
|
286
300
|
}),
|
|
287
301
|
)
|
|
302
|
+
// Fire listeners after data is stored
|
|
303
|
+
update.listeners.forEach(cb => cb())
|
|
288
304
|
})
|
|
289
305
|
|
|
290
306
|
if (Object.keys(update.defer).length !== 0) {
|
|
@@ -378,7 +394,9 @@ const Wire = opt => {
|
|
|
378
394
|
return
|
|
379
395
|
}
|
|
380
396
|
|
|
381
|
-
if (!(await check(update.now, send, cb)))
|
|
397
|
+
if (!(await check(update.now, send, cb))) {
|
|
398
|
+
return
|
|
399
|
+
}
|
|
382
400
|
|
|
383
401
|
// Seed pendingReferences with any new references from API calls
|
|
384
402
|
for (const [soul, node] of Object.entries(update.now)) {
|
|
@@ -393,7 +411,11 @@ const Wire = opt => {
|
|
|
393
411
|
}
|
|
394
412
|
}
|
|
395
413
|
|
|
396
|
-
store.put(update.now,
|
|
414
|
+
store.put(update.now, err => {
|
|
415
|
+
if (cb) cb(err)
|
|
416
|
+
// Fire listeners after data is stored
|
|
417
|
+
update.listeners.forEach(l => l())
|
|
418
|
+
})
|
|
397
419
|
|
|
398
420
|
// Always put data on the wire spec even if no local update needed
|
|
399
421
|
const sendResult = send(
|
package/test/holster.on.test.js
CHANGED
|
@@ -16,13 +16,17 @@ describe("holster.on", () => {
|
|
|
16
16
|
})
|
|
17
17
|
|
|
18
18
|
test("on for property on root then update - event", (t, done) => {
|
|
19
|
-
// Listener
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
// Listener is now called for initial put and updates
|
|
20
|
+
let callCount = 0
|
|
21
|
+
holster.get("key").on(data => {
|
|
22
|
+
callCount++
|
|
23
|
+
if (callCount === 1) {
|
|
24
|
+
assert.equal(data, "value")
|
|
25
|
+
} else if (callCount === 2) {
|
|
26
|
+
assert.equal(data, "update")
|
|
27
|
+
done()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
26
30
|
|
|
27
31
|
holster.get("key").put("value", err => {
|
|
28
32
|
assert.equal(err, null)
|
|
@@ -37,19 +41,27 @@ describe("holster.on", () => {
|
|
|
37
41
|
let done1 = false
|
|
38
42
|
let done2 = false
|
|
39
43
|
|
|
44
|
+
let callback1Count = 0
|
|
40
45
|
const callback1 = data => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
callback1Count++
|
|
47
|
+
if (callback1Count === 2) {
|
|
48
|
+
assert.equal(data, "update")
|
|
49
|
+
holster.get("two").off(callback1)
|
|
50
|
+
if (done2) done()
|
|
51
|
+
else done1 = true
|
|
52
|
+
}
|
|
45
53
|
}
|
|
46
54
|
holster.get("two").on(callback1)
|
|
47
55
|
|
|
56
|
+
let callback2Count = 0
|
|
48
57
|
const callback2 = data => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
callback2Count++
|
|
59
|
+
if (callback2Count === 2) {
|
|
60
|
+
assert.equal(data, "update")
|
|
61
|
+
holster.get("two").off(callback2)
|
|
62
|
+
if (done1) done()
|
|
63
|
+
else done2 = true
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
66
|
holster.get("two").on(callback2)
|
|
55
67
|
|
|
@@ -80,11 +92,12 @@ describe("holster.on", () => {
|
|
|
80
92
|
holster.get("key3").put("value3", err => {
|
|
81
93
|
assert.equal(err, null)
|
|
82
94
|
|
|
83
|
-
let
|
|
95
|
+
let callCount = 0
|
|
84
96
|
holster.get("key3").on(data => {
|
|
85
|
-
|
|
97
|
+
callCount++
|
|
98
|
+
if (callCount === 1) {
|
|
86
99
|
assert.equal(data, "update1")
|
|
87
|
-
} else {
|
|
100
|
+
} else if (callCount === 2) {
|
|
88
101
|
assert.equal(data, "update2")
|
|
89
102
|
done()
|
|
90
103
|
}
|
|
@@ -95,7 +108,6 @@ describe("holster.on", () => {
|
|
|
95
108
|
})
|
|
96
109
|
|
|
97
110
|
setTimeout(() => {
|
|
98
|
-
first = false
|
|
99
111
|
holster.get("key3").put("update2", err => {
|
|
100
112
|
assert.equal(err, null)
|
|
101
113
|
})
|
|
@@ -112,10 +124,14 @@ describe("holster.on", () => {
|
|
|
112
124
|
|
|
113
125
|
// Need to wait until after all initial puts are done to set on listeners.
|
|
114
126
|
setTimeout(() => {
|
|
127
|
+
let completed = 0
|
|
115
128
|
for (let i = 0; i < 5; i++) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
const idx = i // Capture value
|
|
130
|
+
holster.get("for" + idx).on(data => {
|
|
131
|
+
// Listener fires for updates (initial puts already completed before listener setup)
|
|
132
|
+
assert.equal(data, "update" + idx)
|
|
133
|
+
completed++
|
|
134
|
+
if (completed === 5) done()
|
|
119
135
|
})
|
|
120
136
|
}
|
|
121
137
|
|
|
@@ -154,6 +170,7 @@ describe("holster.on", () => {
|
|
|
154
170
|
.get("hello")
|
|
155
171
|
.next("world!")
|
|
156
172
|
.on(data => {
|
|
173
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
157
174
|
assert.equal(data, "update")
|
|
158
175
|
holster.get("hello").next("world!").off()
|
|
159
176
|
done()
|
|
@@ -183,6 +200,7 @@ describe("holster.on", () => {
|
|
|
183
200
|
number: 42,
|
|
184
201
|
}
|
|
185
202
|
holster.get("plain").on(data => {
|
|
203
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
186
204
|
assert.deepEqual(data, update)
|
|
187
205
|
done()
|
|
188
206
|
})
|
|
@@ -211,14 +229,20 @@ describe("holster.on", () => {
|
|
|
211
229
|
has: "child update",
|
|
212
230
|
},
|
|
213
231
|
}
|
|
232
|
+
let parentFired = false
|
|
214
233
|
holster.get("nested").on(data => {
|
|
215
|
-
|
|
234
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
235
|
+
if (!parentFired) {
|
|
236
|
+
parentFired = true
|
|
237
|
+
assert.deepEqual(data, {key: nested.key, ...update})
|
|
238
|
+
}
|
|
216
239
|
})
|
|
217
240
|
|
|
218
241
|
holster
|
|
219
242
|
.get("nested")
|
|
220
243
|
.next("child")
|
|
221
244
|
.on(data => {
|
|
245
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
222
246
|
assert.deepEqual(data, update.child)
|
|
223
247
|
done()
|
|
224
248
|
})
|
|
@@ -31,11 +31,17 @@ describe("holster.user.on", () => {
|
|
|
31
31
|
})
|
|
32
32
|
|
|
33
33
|
test("on for property then update - event", (t, done) => {
|
|
34
|
-
// Listener
|
|
34
|
+
// Listener is now called for initial put and updates
|
|
35
|
+
let callCount = 0
|
|
35
36
|
const callback = data => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
callCount++
|
|
38
|
+
if (callCount === 1) {
|
|
39
|
+
assert.equal(data, "value")
|
|
40
|
+
} else if (callCount === 2) {
|
|
41
|
+
assert.equal(data, "update")
|
|
42
|
+
user.get("key").off(callback)
|
|
43
|
+
done()
|
|
44
|
+
}
|
|
39
45
|
}
|
|
40
46
|
user.get("key").on(callback)
|
|
41
47
|
|
|
@@ -52,19 +58,27 @@ describe("holster.user.on", () => {
|
|
|
52
58
|
let done1 = false
|
|
53
59
|
let done2 = false
|
|
54
60
|
|
|
61
|
+
let callback1Count = 0
|
|
55
62
|
const callback1 = data => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
callback1Count++
|
|
64
|
+
if (callback1Count === 2) {
|
|
65
|
+
assert.equal(data, "update")
|
|
66
|
+
user.get("two").off(callback1)
|
|
67
|
+
if (done2) done()
|
|
68
|
+
else done1 = true
|
|
69
|
+
}
|
|
60
70
|
}
|
|
61
71
|
user.get("two").on(callback1)
|
|
62
72
|
|
|
73
|
+
let callback2Count = 0
|
|
63
74
|
const callback2 = data => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
callback2Count++
|
|
76
|
+
if (callback2Count === 2) {
|
|
77
|
+
assert.equal(data, "update")
|
|
78
|
+
user.get("two").off(callback2)
|
|
79
|
+
if (done1) done()
|
|
80
|
+
else done2 = true
|
|
81
|
+
}
|
|
68
82
|
}
|
|
69
83
|
user.get("two").on(callback2)
|
|
70
84
|
|
|
@@ -92,11 +106,12 @@ describe("holster.user.on", () => {
|
|
|
92
106
|
user.get("key3").put("value3", err => {
|
|
93
107
|
assert.equal(err, null)
|
|
94
108
|
|
|
95
|
-
let
|
|
109
|
+
let callCount = 0
|
|
96
110
|
const callback = data => {
|
|
97
|
-
|
|
111
|
+
callCount++
|
|
112
|
+
if (callCount === 1) {
|
|
98
113
|
assert.equal(data, "update1")
|
|
99
|
-
} else {
|
|
114
|
+
} else if (callCount === 2) {
|
|
100
115
|
assert.equal(data, "update2")
|
|
101
116
|
user.get("key3").off(callback)
|
|
102
117
|
done()
|
|
@@ -109,7 +124,6 @@ describe("holster.user.on", () => {
|
|
|
109
124
|
})
|
|
110
125
|
|
|
111
126
|
setTimeout(() => {
|
|
112
|
-
first = false
|
|
113
127
|
user.get("key3").put("update2", err => {
|
|
114
128
|
assert.equal(err, null)
|
|
115
129
|
})
|
|
@@ -132,6 +146,7 @@ describe("holster.user.on", () => {
|
|
|
132
146
|
for (let i = 0; i < 5; i++) {
|
|
133
147
|
const idx = i // Capture value
|
|
134
148
|
const callback = data => {
|
|
149
|
+
// Listener fires for updates (initial puts already completed before listener setup)
|
|
135
150
|
assert.equal(data, "update" + idx)
|
|
136
151
|
user.get("for" + idx).off(callback)
|
|
137
152
|
count++
|
|
@@ -176,6 +191,7 @@ describe("holster.user.on", () => {
|
|
|
176
191
|
assert.equal(err, null)
|
|
177
192
|
|
|
178
193
|
const callback = data => {
|
|
194
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
179
195
|
assert.equal(data, "update")
|
|
180
196
|
user.get("hello").next("world!").off(callback)
|
|
181
197
|
done()
|
|
@@ -208,6 +224,7 @@ describe("holster.user.on", () => {
|
|
|
208
224
|
}
|
|
209
225
|
|
|
210
226
|
const callback = data => {
|
|
227
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
211
228
|
assert.deepEqual(data, update)
|
|
212
229
|
user.get("plain").off(callback)
|
|
213
230
|
done()
|
|
@@ -240,12 +257,18 @@ describe("holster.user.on", () => {
|
|
|
240
257
|
},
|
|
241
258
|
}
|
|
242
259
|
|
|
260
|
+
let parentFired = false
|
|
243
261
|
const parentCallback = data => {
|
|
244
|
-
|
|
262
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
263
|
+
if (!parentFired) {
|
|
264
|
+
parentFired = true
|
|
265
|
+
assert.deepEqual(data, update)
|
|
266
|
+
}
|
|
245
267
|
}
|
|
246
268
|
user.get("nested").on(parentCallback)
|
|
247
269
|
|
|
248
270
|
const childCallback = data => {
|
|
271
|
+
// Listener fires for updates (initial put already completed before listener setup)
|
|
249
272
|
assert.deepEqual(data, update.child)
|
|
250
273
|
user.get("nested").off(parentCallback)
|
|
251
274
|
user.get("nested").next("child").off(childCallback)
|
|
@@ -86,22 +86,23 @@ describe("system - concurrent listeners", () => {
|
|
|
86
86
|
holster.get("race").put({value: "first"}, err => {
|
|
87
87
|
assert.equal(err, null)
|
|
88
88
|
|
|
89
|
-
// Set up listener with
|
|
89
|
+
// Set up listener with _get=true to read existing data
|
|
90
|
+
// The listener will be called with the existing data via the get request
|
|
90
91
|
holster.get("race").on(data => {
|
|
91
92
|
results.push(data?.value)
|
|
92
93
|
}, true)
|
|
93
94
|
|
|
94
|
-
// Immediately put new data
|
|
95
|
+
// Immediately put new data - listener will fire for this put as well
|
|
95
96
|
setTimeout(() => {
|
|
96
97
|
holster.get("race").put({value: "second"}, err => {
|
|
97
98
|
assert.equal(err, null)
|
|
98
99
|
|
|
99
100
|
setTimeout(() => {
|
|
100
|
-
// Should have received both values
|
|
101
|
+
// Should have received both values - one from get, one from put
|
|
101
102
|
assert.equal(
|
|
102
103
|
results.length >= 1,
|
|
103
104
|
true,
|
|
104
|
-
"Should receive at least initial value",
|
|
105
|
+
"Should receive at least initial value from get",
|
|
105
106
|
)
|
|
106
107
|
assert.equal(results[0], "first", "First value should be 'first'")
|
|
107
108
|
done()
|
|
@@ -23,7 +23,8 @@ describe("system - listener before data", () => {
|
|
|
23
23
|
|
|
24
24
|
// Give listener time to fire
|
|
25
25
|
setTimeout(() => {
|
|
26
|
-
//
|
|
26
|
+
// With the new behavior, listener fires after data is stored
|
|
27
|
+
// Should have received the test value from the put
|
|
27
28
|
const nonNull = results.filter(r => r !== null)
|
|
28
29
|
assert.equal(
|
|
29
30
|
nonNull.length >= 1,
|
|
@@ -103,21 +103,22 @@ describe("system - user concurrent listeners", () => {
|
|
|
103
103
|
assert.equal(err, null)
|
|
104
104
|
|
|
105
105
|
// Set up listener with _get=true to read existing data
|
|
106
|
+
// The listener will be called with the existing data via the get request
|
|
106
107
|
user.get("race").on(data => {
|
|
107
108
|
results.push(data?.value)
|
|
108
109
|
}, true)
|
|
109
110
|
|
|
110
|
-
// Immediately put new data
|
|
111
|
+
// Immediately put new data - listener will fire for this put as well
|
|
111
112
|
setTimeout(() => {
|
|
112
113
|
user.get("race").put({value: "second"}, err => {
|
|
113
114
|
assert.equal(err, null)
|
|
114
115
|
|
|
115
116
|
setTimeout(() => {
|
|
116
|
-
// Should have received both values
|
|
117
|
+
// Should have received both values - one from get, one from put
|
|
117
118
|
assert.equal(
|
|
118
119
|
results.length >= 1,
|
|
119
120
|
true,
|
|
120
|
-
"Should receive at least initial value",
|
|
121
|
+
"Should receive at least initial value from get",
|
|
121
122
|
)
|
|
122
123
|
assert.equal(results[0], "first", "First value should be 'first'")
|
|
123
124
|
done()
|
|
@@ -37,7 +37,8 @@ describe("system - user listener before data", () => {
|
|
|
37
37
|
|
|
38
38
|
// Give listener time to fire
|
|
39
39
|
setTimeout(() => {
|
|
40
|
-
//
|
|
40
|
+
// With the new behavior, listener fires after data is stored
|
|
41
|
+
// Should have received the test value from the put
|
|
41
42
|
const nonNull = results.filter(r => r !== null)
|
|
42
43
|
assert.equal(
|
|
43
44
|
nonNull.length >= 1,
|