@rljson/db 0.0.14 → 0.0.15
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 +15 -1
- package/dist/db.d.ts +36 -1
- package/dist/db.js +119 -0
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Socket } from '@rljson/io';
|
|
2
|
-
import { AckPayload, ClientId, InsertHistoryTimeId, Route, SyncConfig, SyncEventNames } from '@rljson/rljson';
|
|
2
|
+
import { AckPayload, ClientId, ConflictCallback, InsertHistoryTimeId, Route, SyncConfig, SyncEventNames } from '@rljson/rljson';
|
|
3
3
|
import { Db } from '../db.ts';
|
|
4
4
|
export type { ConnectorPayload } from '@rljson/rljson';
|
|
5
5
|
export type ConnectorCallback = (ref: string) => Promise<any>;
|
|
@@ -9,6 +9,7 @@ export declare class Connector {
|
|
|
9
9
|
private readonly _socket;
|
|
10
10
|
private _origin;
|
|
11
11
|
private _callbacks;
|
|
12
|
+
private _conflictCallbacks;
|
|
12
13
|
private _isListening;
|
|
13
14
|
private _sentRefsCurrent;
|
|
14
15
|
private _sentRefsPrevious;
|
|
@@ -49,6 +50,18 @@ export declare class Connector {
|
|
|
49
50
|
* @param callback - The callback to invoke with each deduplicated incoming ref
|
|
50
51
|
*/
|
|
51
52
|
listen(callback: ConnectorCallback): void;
|
|
53
|
+
/**
|
|
54
|
+
* Registers a callback that fires when a DAG conflict is detected.
|
|
55
|
+
*
|
|
56
|
+
* A conflict occurs when the InsertHistory for this route's table
|
|
57
|
+
* has multiple "tips" (leaf nodes), indicating concurrent writes
|
|
58
|
+
* from different clients that have not yet been merged.
|
|
59
|
+
*
|
|
60
|
+
* Detection-only: the callback receives a `Conflict` object
|
|
61
|
+
* describing the branches. Resolution is left to upper layers.
|
|
62
|
+
* @param callback - Invoked with the detected Conflict
|
|
63
|
+
*/
|
|
64
|
+
onConflict(callback: ConflictCallback): void;
|
|
52
65
|
/**
|
|
53
66
|
* Returns the current sequence number.
|
|
54
67
|
* Only meaningful when `causalOrdering` is enabled.
|
|
@@ -83,6 +96,7 @@ export declare class Connector {
|
|
|
83
96
|
* _processIncoming handles dedup so already-seen refs are filtered out.
|
|
84
97
|
*/
|
|
85
98
|
private _registerBootstrapHandler;
|
|
99
|
+
private _registerConflictObserver;
|
|
86
100
|
private _registerDbObserver;
|
|
87
101
|
get socket(): Socket;
|
|
88
102
|
get route(): Route;
|
package/dist/db.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Io } from '@rljson/io';
|
|
2
2
|
import { Json, JsonValue } from '@rljson/json';
|
|
3
|
-
import { Edit, EditHistory, InsertHistoryRow, InsertHistoryTimeId, MultiEdit, Ref, Rljson, Route, SliceId, TableType, Tree } from '@rljson/rljson';
|
|
3
|
+
import { Conflict, ConflictCallback, Edit, EditHistory, InsertHistoryRow, InsertHistoryTimeId, MultiEdit, Ref, Rljson, Route, SliceId, TableType, Tree } from '@rljson/rljson';
|
|
4
4
|
import { Controller, ControllerChildProperty, ControllerRefs } from './controller/controller.ts';
|
|
5
5
|
import { Core } from './core.ts';
|
|
6
6
|
import { Join } from './join/join.ts';
|
|
@@ -43,6 +43,7 @@ export declare class Db {
|
|
|
43
43
|
* Notification system to register callbacks on data changes
|
|
44
44
|
*/
|
|
45
45
|
readonly notify: Notify;
|
|
46
|
+
private _conflictCallbacks;
|
|
46
47
|
private _cache;
|
|
47
48
|
/**
|
|
48
49
|
* Get data from a route with optional filtering
|
|
@@ -137,6 +138,24 @@ export declare class Db {
|
|
|
137
138
|
* Unregisters all observers from all routes
|
|
138
139
|
*/
|
|
139
140
|
unregisterAllObservers(route: Route): void;
|
|
141
|
+
/**
|
|
142
|
+
* Registers a callback to be called when a DAG conflict is detected
|
|
143
|
+
* on the given route.
|
|
144
|
+
* @param route - The route to register the conflict callback on
|
|
145
|
+
* @param callback - The callback to invoke with the Conflict
|
|
146
|
+
*/
|
|
147
|
+
registerConflictObserver(route: Route, callback: ConflictCallback): void;
|
|
148
|
+
/**
|
|
149
|
+
* Unregisters a specific conflict callback from the given route.
|
|
150
|
+
* @param route - The route to unregister the callback from
|
|
151
|
+
* @param callback - The callback to remove
|
|
152
|
+
*/
|
|
153
|
+
unregisterConflictObserver(route: Route, callback: ConflictCallback): void;
|
|
154
|
+
/**
|
|
155
|
+
* Unregisters all conflict callbacks from the given route.
|
|
156
|
+
* @param route - The route to clear conflict callbacks for
|
|
157
|
+
*/
|
|
158
|
+
unregisterAllConflictObservers(route: Route): void;
|
|
140
159
|
/**
|
|
141
160
|
* Get a controller for a specific table
|
|
142
161
|
* @param tableKey - The key of the table to get the controller for
|
|
@@ -146,6 +165,22 @@ export declare class Db {
|
|
|
146
165
|
*/
|
|
147
166
|
getController(tableKey: string, refs?: ControllerRefs): Promise<Controller<TableType, any, string>>;
|
|
148
167
|
indexedControllers(route: Route): Promise<Record<string, Controller<any, any, any>>>;
|
|
168
|
+
/**
|
|
169
|
+
* Detects whether the InsertHistory for a table has diverged into
|
|
170
|
+
* multiple branches (multiple "tips" — leaf nodes in the DAG).
|
|
171
|
+
*
|
|
172
|
+
* A tip is a timeId that is NOT referenced as `previous` by any other
|
|
173
|
+
* InsertHistory row. Two or more tips indicate a DAG fork (conflict).
|
|
174
|
+
* @param table - The table name (without "InsertHistory" suffix)
|
|
175
|
+
* @returns A Conflict if a DAG branch is detected, or null otherwise
|
|
176
|
+
*/
|
|
177
|
+
detectDagBranch(table: string): Promise<Conflict | null>;
|
|
178
|
+
/**
|
|
179
|
+
* Fires conflict callbacks registered on the route derived from the table.
|
|
180
|
+
* @param table - The table name
|
|
181
|
+
* @param conflict - The detected conflict
|
|
182
|
+
*/
|
|
183
|
+
private _notifyConflict;
|
|
149
184
|
/**
|
|
150
185
|
* Adds an InsertHistory row to the InsertHistory table of a table
|
|
151
186
|
* @param table - The table the Insert was made on
|
package/dist/db.js
CHANGED
|
@@ -22,6 +22,7 @@ class Connector {
|
|
|
22
22
|
}
|
|
23
23
|
_origin;
|
|
24
24
|
_callbacks = [];
|
|
25
|
+
_conflictCallbacks = [];
|
|
25
26
|
_isListening = false;
|
|
26
27
|
// Two-generation dedup sets — bounded memory
|
|
27
28
|
_sentRefsCurrent = /* @__PURE__ */ new Set();
|
|
@@ -107,6 +108,21 @@ class Connector {
|
|
|
107
108
|
this._callbacks.push(callback);
|
|
108
109
|
}
|
|
109
110
|
// ...........................................................................
|
|
111
|
+
/**
|
|
112
|
+
* Registers a callback that fires when a DAG conflict is detected.
|
|
113
|
+
*
|
|
114
|
+
* A conflict occurs when the InsertHistory for this route's table
|
|
115
|
+
* has multiple "tips" (leaf nodes), indicating concurrent writes
|
|
116
|
+
* from different clients that have not yet been merged.
|
|
117
|
+
*
|
|
118
|
+
* Detection-only: the callback receives a `Conflict` object
|
|
119
|
+
* describing the branches. Resolution is left to upper layers.
|
|
120
|
+
* @param callback - Invoked with the detected Conflict
|
|
121
|
+
*/
|
|
122
|
+
onConflict(callback) {
|
|
123
|
+
this._conflictCallbacks.push(callback);
|
|
124
|
+
}
|
|
125
|
+
// ...........................................................................
|
|
110
126
|
/**
|
|
111
127
|
* Returns the current sequence number.
|
|
112
128
|
* Only meaningful when `causalOrdering` is enabled.
|
|
@@ -143,6 +159,7 @@ class Connector {
|
|
|
143
159
|
this._registerSocketObserver();
|
|
144
160
|
this._registerBootstrapHandler();
|
|
145
161
|
this._registerDbObserver();
|
|
162
|
+
this._registerConflictObserver();
|
|
146
163
|
if (this._syncConfig?.causalOrdering) {
|
|
147
164
|
this._registerGapFillHandler();
|
|
148
165
|
}
|
|
@@ -158,6 +175,7 @@ class Connector {
|
|
|
158
175
|
this._socket.removeAllListeners(this._events.ack);
|
|
159
176
|
}
|
|
160
177
|
this._db.unregisterAllObservers(this._route);
|
|
178
|
+
this._db.unregisterAllConflictObservers(this._route);
|
|
161
179
|
this._isListening = false;
|
|
162
180
|
}
|
|
163
181
|
// ...........................................................................
|
|
@@ -235,6 +253,13 @@ class Connector {
|
|
|
235
253
|
this._processIncoming(p);
|
|
236
254
|
});
|
|
237
255
|
}
|
|
256
|
+
_registerConflictObserver() {
|
|
257
|
+
this._db.registerConflictObserver(this._route, (conflict) => {
|
|
258
|
+
for (const cb of this._conflictCallbacks) {
|
|
259
|
+
cb(conflict);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
238
263
|
_registerDbObserver() {
|
|
239
264
|
this._db.registerObserver(this._route, (ins) => {
|
|
240
265
|
return new Promise((resolve) => {
|
|
@@ -3092,6 +3117,7 @@ class Db {
|
|
|
3092
3117
|
* Notification system to register callbacks on data changes
|
|
3093
3118
|
*/
|
|
3094
3119
|
notify;
|
|
3120
|
+
_conflictCallbacks = /* @__PURE__ */ new Map();
|
|
3095
3121
|
_cache = /* @__PURE__ */ new Map();
|
|
3096
3122
|
// ...........................................................................
|
|
3097
3123
|
/**
|
|
@@ -4061,6 +4087,44 @@ class Db {
|
|
|
4061
4087
|
this.notify.unregisterAll(route);
|
|
4062
4088
|
}
|
|
4063
4089
|
// ...........................................................................
|
|
4090
|
+
/**
|
|
4091
|
+
* Registers a callback to be called when a DAG conflict is detected
|
|
4092
|
+
* on the given route.
|
|
4093
|
+
* @param route - The route to register the conflict callback on
|
|
4094
|
+
* @param callback - The callback to invoke with the Conflict
|
|
4095
|
+
*/
|
|
4096
|
+
registerConflictObserver(route, callback) {
|
|
4097
|
+
const key = route.flat;
|
|
4098
|
+
this._conflictCallbacks.set(key, [
|
|
4099
|
+
...this._conflictCallbacks.get(key) || [],
|
|
4100
|
+
callback
|
|
4101
|
+
]);
|
|
4102
|
+
}
|
|
4103
|
+
// ...........................................................................
|
|
4104
|
+
/**
|
|
4105
|
+
* Unregisters a specific conflict callback from the given route.
|
|
4106
|
+
* @param route - The route to unregister the callback from
|
|
4107
|
+
* @param callback - The callback to remove
|
|
4108
|
+
*/
|
|
4109
|
+
unregisterConflictObserver(route, callback) {
|
|
4110
|
+
const key = route.flat;
|
|
4111
|
+
const callbacks = this._conflictCallbacks.get(key);
|
|
4112
|
+
if (callbacks) {
|
|
4113
|
+
this._conflictCallbacks.set(
|
|
4114
|
+
key,
|
|
4115
|
+
callbacks.filter((cb) => cb !== callback)
|
|
4116
|
+
);
|
|
4117
|
+
}
|
|
4118
|
+
}
|
|
4119
|
+
// ...........................................................................
|
|
4120
|
+
/**
|
|
4121
|
+
* Unregisters all conflict callbacks from the given route.
|
|
4122
|
+
* @param route - The route to clear conflict callbacks for
|
|
4123
|
+
*/
|
|
4124
|
+
unregisterAllConflictObservers(route) {
|
|
4125
|
+
this._conflictCallbacks.delete(route.flat);
|
|
4126
|
+
}
|
|
4127
|
+
// ...........................................................................
|
|
4064
4128
|
/**
|
|
4065
4129
|
* Get a controller for a specific table
|
|
4066
4130
|
* @param tableKey - The key of the table to get the controller for
|
|
@@ -4093,6 +4157,57 @@ class Db {
|
|
|
4093
4157
|
return controllers;
|
|
4094
4158
|
}
|
|
4095
4159
|
// ...........................................................................
|
|
4160
|
+
/**
|
|
4161
|
+
* Detects whether the InsertHistory for a table has diverged into
|
|
4162
|
+
* multiple branches (multiple "tips" — leaf nodes in the DAG).
|
|
4163
|
+
*
|
|
4164
|
+
* A tip is a timeId that is NOT referenced as `previous` by any other
|
|
4165
|
+
* InsertHistory row. Two or more tips indicate a DAG fork (conflict).
|
|
4166
|
+
* @param table - The table name (without "InsertHistory" suffix)
|
|
4167
|
+
* @returns A Conflict if a DAG branch is detected, or null otherwise
|
|
4168
|
+
*/
|
|
4169
|
+
async detectDagBranch(table) {
|
|
4170
|
+
const insertHistoryTable = table + "InsertHistory";
|
|
4171
|
+
const hasTable = await this.core.hasTable(insertHistoryTable);
|
|
4172
|
+
if (!hasTable) return null;
|
|
4173
|
+
const dump = await this.core.dumpTable(insertHistoryTable);
|
|
4174
|
+
const rows = dump[insertHistoryTable]._data;
|
|
4175
|
+
if (rows.length < 2) return null;
|
|
4176
|
+
const referencedAsParent = /* @__PURE__ */ new Set();
|
|
4177
|
+
for (const row of rows) {
|
|
4178
|
+
if (row.previous) {
|
|
4179
|
+
for (const p of row.previous) {
|
|
4180
|
+
referencedAsParent.add(p);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
const tips = rows.filter((row) => !referencedAsParent.has(row.timeId));
|
|
4185
|
+
if (tips.length > 1) {
|
|
4186
|
+
return {
|
|
4187
|
+
table,
|
|
4188
|
+
type: "dagBranch",
|
|
4189
|
+
detectedAt: Date.now(),
|
|
4190
|
+
branches: tips.map((t) => t.timeId)
|
|
4191
|
+
};
|
|
4192
|
+
}
|
|
4193
|
+
return null;
|
|
4194
|
+
}
|
|
4195
|
+
// ...........................................................................
|
|
4196
|
+
/**
|
|
4197
|
+
* Fires conflict callbacks registered on the route derived from the table.
|
|
4198
|
+
* @param table - The table name
|
|
4199
|
+
* @param conflict - The detected conflict
|
|
4200
|
+
*/
|
|
4201
|
+
_notifyConflict(table, conflict) {
|
|
4202
|
+
const route = Route.fromFlat(`/${table}`);
|
|
4203
|
+
const callbacks = this._conflictCallbacks.get(route.flat);
|
|
4204
|
+
if (callbacks) {
|
|
4205
|
+
for (const cb of callbacks) {
|
|
4206
|
+
cb(conflict);
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
// ...........................................................................
|
|
4096
4211
|
/**
|
|
4097
4212
|
* Adds an InsertHistory row to the InsertHistory table of a table
|
|
4098
4213
|
* @param table - The table the Insert was made on
|
|
@@ -4107,6 +4222,10 @@ class Db {
|
|
|
4107
4222
|
_type: "insertHistory"
|
|
4108
4223
|
}
|
|
4109
4224
|
});
|
|
4225
|
+
const conflict = await this.detectDagBranch(table);
|
|
4226
|
+
if (conflict) {
|
|
4227
|
+
this._notifyConflict(table, conflict);
|
|
4228
|
+
}
|
|
4110
4229
|
}
|
|
4111
4230
|
// ...........................................................................
|
|
4112
4231
|
/**
|