@rljson/db 0.0.21 → 0.0.23
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/dist/connector/connector.d.ts +28 -5
- package/dist/db.js +55 -18
- package/dist/db.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { Socket } from '@rljson/io';
|
|
2
|
-
import { AckPayload, ClientId, ConflictCallback,
|
|
2
|
+
import { AckPayload, ClientId, ConflictCallback, Route, SyncConfig, SyncEventNames } from '@rljson/rljson';
|
|
3
3
|
import { Db } from '../db.ts';
|
|
4
4
|
export type { ConnectorPayload } from '@rljson/rljson';
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Invoked for each deduplicated incoming ref. `predecessorRefs` carries the
|
|
7
|
+
* causal predecessor *content refs* (shared identity across clients) when
|
|
8
|
+
* `causalOrdering` is enabled, so the receiver can record correct ancestry in
|
|
9
|
+
* its own InsertHistory. Empty/undefined for roots or when causal ordering is off.
|
|
10
|
+
*/
|
|
11
|
+
export type ConnectorCallback = (ref: string, predecessorRefs?: string[]) => Promise<any>;
|
|
6
12
|
export declare class Connector {
|
|
7
13
|
private readonly _db;
|
|
8
14
|
private readonly _route;
|
|
@@ -11,6 +17,7 @@ export declare class Connector {
|
|
|
11
17
|
private _callbacks;
|
|
12
18
|
private _conflictCallbacks;
|
|
13
19
|
private _missedRef;
|
|
20
|
+
private _missedPredecessorRefs;
|
|
14
21
|
private _lastSentRef;
|
|
15
22
|
private _isListening;
|
|
16
23
|
private _sentRefsCurrent;
|
|
@@ -39,10 +46,12 @@ export declare class Connector {
|
|
|
39
46
|
*/
|
|
40
47
|
sendWithAck(ref: string): Promise<AckPayload>;
|
|
41
48
|
/**
|
|
42
|
-
* Sets the causal predecessors
|
|
43
|
-
*
|
|
49
|
+
* Sets the causal predecessors (content refs) attached to the next send.
|
|
50
|
+
* Normally auto-populated from the InsertHistoryRow; exposed for tests/manual
|
|
51
|
+
* control.
|
|
52
|
+
* @param predecessors - The predecessor content refs
|
|
44
53
|
*/
|
|
45
|
-
setPredecessors(predecessors:
|
|
54
|
+
setPredecessors(predecessors: string[]): void;
|
|
46
55
|
/**
|
|
47
56
|
* Registers a callback for incoming refs on this route.
|
|
48
57
|
*
|
|
@@ -64,6 +73,20 @@ export declare class Connector {
|
|
|
64
73
|
* @param callback - Invoked with the detected Conflict
|
|
65
74
|
*/
|
|
66
75
|
onConflict(callback: ConflictCallback): void;
|
|
76
|
+
/**
|
|
77
|
+
* Removes a ref from the received-dedup set so a later re-advertisement of
|
|
78
|
+
* the same ref (e.g. the server's bootstrap heartbeat, which only re-sends
|
|
79
|
+
* the *latest* ref) is delivered to listeners again instead of being
|
|
80
|
+
* silently deduplicated.
|
|
81
|
+
*
|
|
82
|
+
* A ref is added to the dedup set the instant it is *received* — before the
|
|
83
|
+
* listener callback (which materializes it) has a chance to succeed. If that
|
|
84
|
+
* apply step fails terminally, the ref is stuck in the dedup set forever and
|
|
85
|
+
* the heartbeat can never heal it. Consumers whose apply failed call this so
|
|
86
|
+
* recovery becomes eventually-consistent rather than permanent loss.
|
|
87
|
+
* @param ref - The ref to allow re-delivery for.
|
|
88
|
+
*/
|
|
89
|
+
invalidateReceived(ref: string): void;
|
|
67
90
|
/**
|
|
68
91
|
* Returns the current sequence number.
|
|
69
92
|
* Only meaningful when `causalOrdering` is enabled.
|
package/dist/db.js
CHANGED
|
@@ -24,6 +24,7 @@ class Connector {
|
|
|
24
24
|
_callbacks = [];
|
|
25
25
|
_conflictCallbacks = [];
|
|
26
26
|
_missedRef = null;
|
|
27
|
+
_missedPredecessorRefs = void 0;
|
|
27
28
|
_lastSentRef = null;
|
|
28
29
|
_isListening = false;
|
|
29
30
|
// Two-generation dedup sets — bounded memory
|
|
@@ -37,6 +38,9 @@ class Connector {
|
|
|
37
38
|
_clientId;
|
|
38
39
|
_events;
|
|
39
40
|
_seq = 0;
|
|
41
|
+
// Predecessor *content refs* (not timeIds) attached to the next send. Refs are
|
|
42
|
+
// the only identity shared across clients, so the receiver can map them to its
|
|
43
|
+
// own local ancestry. Auto-populated from the InsertHistoryRow on db inserts.
|
|
40
44
|
_lastPredecessors = [];
|
|
41
45
|
_peerSeqs = /* @__PURE__ */ new Map();
|
|
42
46
|
// ...........................................................................
|
|
@@ -92,8 +96,10 @@ class Connector {
|
|
|
92
96
|
}
|
|
93
97
|
// ...........................................................................
|
|
94
98
|
/**
|
|
95
|
-
* Sets the causal predecessors
|
|
96
|
-
*
|
|
99
|
+
* Sets the causal predecessors (content refs) attached to the next send.
|
|
100
|
+
* Normally auto-populated from the InsertHistoryRow; exposed for tests/manual
|
|
101
|
+
* control.
|
|
102
|
+
* @param predecessors - The predecessor content refs
|
|
97
103
|
*/
|
|
98
104
|
setPredecessors(predecessors) {
|
|
99
105
|
this._lastPredecessors = predecessors;
|
|
@@ -111,8 +117,12 @@ class Connector {
|
|
|
111
117
|
this._callbacks.push(callback);
|
|
112
118
|
if (this._missedRef !== null) {
|
|
113
119
|
const ref = this._missedRef;
|
|
120
|
+
const predecessorRefs = this._missedPredecessorRefs;
|
|
114
121
|
this._missedRef = null;
|
|
115
|
-
|
|
122
|
+
this._missedPredecessorRefs = void 0;
|
|
123
|
+
Promise.resolve(
|
|
124
|
+
predecessorRefs ? callback(ref, predecessorRefs) : callback(ref)
|
|
125
|
+
).catch(console.error);
|
|
116
126
|
}
|
|
117
127
|
}
|
|
118
128
|
// ...........................................................................
|
|
@@ -131,6 +141,24 @@ class Connector {
|
|
|
131
141
|
this._conflictCallbacks.push(callback);
|
|
132
142
|
}
|
|
133
143
|
// ...........................................................................
|
|
144
|
+
/**
|
|
145
|
+
* Removes a ref from the received-dedup set so a later re-advertisement of
|
|
146
|
+
* the same ref (e.g. the server's bootstrap heartbeat, which only re-sends
|
|
147
|
+
* the *latest* ref) is delivered to listeners again instead of being
|
|
148
|
+
* silently deduplicated.
|
|
149
|
+
*
|
|
150
|
+
* A ref is added to the dedup set the instant it is *received* — before the
|
|
151
|
+
* listener callback (which materializes it) has a chance to succeed. If that
|
|
152
|
+
* apply step fails terminally, the ref is stuck in the dedup set forever and
|
|
153
|
+
* the heartbeat can never heal it. Consumers whose apply failed call this so
|
|
154
|
+
* recovery becomes eventually-consistent rather than permanent loss.
|
|
155
|
+
* @param ref - The ref to allow re-delivery for.
|
|
156
|
+
*/
|
|
157
|
+
invalidateReceived(ref) {
|
|
158
|
+
this._receivedRefsCurrent.delete(ref);
|
|
159
|
+
this._receivedRefsPrevious.delete(ref);
|
|
160
|
+
}
|
|
161
|
+
// ...........................................................................
|
|
134
162
|
/**
|
|
135
163
|
* Returns the current sequence number.
|
|
136
164
|
* Only meaningful when `causalOrdering` is enabled.
|
|
@@ -209,12 +237,17 @@ class Connector {
|
|
|
209
237
|
this._receivedRefsCurrent = /* @__PURE__ */ new Set();
|
|
210
238
|
}
|
|
211
239
|
}
|
|
212
|
-
_notifyCallbacks(ref) {
|
|
240
|
+
_notifyCallbacks(ref, predecessorRefs) {
|
|
213
241
|
if (this._callbacks.length === 0) {
|
|
214
242
|
this._missedRef = ref;
|
|
243
|
+
this._missedPredecessorRefs = predecessorRefs;
|
|
215
244
|
return;
|
|
216
245
|
}
|
|
217
|
-
Promise.all(
|
|
246
|
+
Promise.all(
|
|
247
|
+
this._callbacks.map(
|
|
248
|
+
(cb) => predecessorRefs ? cb(ref, predecessorRefs) : cb(ref)
|
|
249
|
+
)
|
|
250
|
+
).catch((err) => {
|
|
218
251
|
console.error(`Error notifying connector callbacks for ref ${ref}:`, err);
|
|
219
252
|
});
|
|
220
253
|
}
|
|
@@ -235,7 +268,7 @@ class Connector {
|
|
|
235
268
|
this._peerSeqs.set(payload.c, payload.seq);
|
|
236
269
|
}
|
|
237
270
|
this._addReceivedRef(ref);
|
|
238
|
-
this._notifyCallbacks(ref);
|
|
271
|
+
this._notifyCallbacks(ref, payload.p);
|
|
239
272
|
if (this._syncConfig?.requireAck) {
|
|
240
273
|
this._socket.emit(this._events.ackClient, { r: ref });
|
|
241
274
|
}
|
|
@@ -273,19 +306,23 @@ class Connector {
|
|
|
273
306
|
});
|
|
274
307
|
}
|
|
275
308
|
_registerDbObserver() {
|
|
276
|
-
this._db.registerObserver(this._route, (ins) => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
309
|
+
this._db.registerObserver(this._route, async (ins) => {
|
|
310
|
+
const tableKey = this.route.root.tableKey;
|
|
311
|
+
const ref = ins[tableKey + "Ref"];
|
|
312
|
+
if (this._hasSentRef(ref)) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (this._syncConfig?.causalOrdering && ins.previous?.length) {
|
|
316
|
+
const refs = [];
|
|
317
|
+
for (const timeId2 of ins.previous) {
|
|
318
|
+
const predRef = await this._db.getRefOfTimeId(tableKey, timeId2);
|
|
319
|
+
if (predRef) {
|
|
320
|
+
refs.push(predRef);
|
|
321
|
+
}
|
|
285
322
|
}
|
|
286
|
-
this.
|
|
287
|
-
|
|
288
|
-
|
|
323
|
+
this._lastPredecessors = refs;
|
|
324
|
+
}
|
|
325
|
+
this.send(ref);
|
|
289
326
|
});
|
|
290
327
|
}
|
|
291
328
|
get socket() {
|