@peerbit/document 6.0.6 → 6.0.7-218a5bb
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/README.md +2 -2
- package/dist/benchmark/index.d.ts +2 -0
- package/dist/benchmark/index.d.ts.map +1 -0
- package/dist/benchmark/index.js +126 -0
- package/dist/benchmark/index.js.map +1 -0
- package/dist/benchmark/replication.d.ts +2 -0
- package/dist/benchmark/replication.d.ts.map +1 -0
- package/dist/benchmark/replication.js +174 -0
- package/dist/benchmark/replication.js.map +1 -0
- package/dist/src/constants.d.ts +2 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +2 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/program.d.ts +90 -0
- package/dist/src/program.d.ts.map +1 -0
- package/{lib/esm/document-store.js → dist/src/program.js} +141 -109
- package/dist/src/program.js.map +1 -0
- package/dist/src/search.d.ts +118 -0
- package/dist/src/search.d.ts.map +1 -0
- package/{lib/esm/document-index.js → dist/src/search.js} +246 -446
- package/dist/src/search.js.map +1 -0
- package/package.json +69 -43
- package/src/constants.ts +1 -0
- package/src/index.ts +4 -3
- package/src/{document-store.ts → program.ts} +216 -183
- package/src/search.ts +997 -0
- package/LICENSE +0 -202
- package/lib/esm/document-index.d.ts +0 -147
- package/lib/esm/document-index.js.map +0 -1
- package/lib/esm/document-store.d.ts +0 -72
- package/lib/esm/document-store.js.map +0 -1
- package/lib/esm/index.d.ts +0 -3
- package/lib/esm/index.js +0 -4
- package/lib/esm/index.js.map +0 -1
- package/lib/esm/query.d.ts +0 -191
- package/lib/esm/query.js +0 -615
- package/lib/esm/query.js.map +0 -1
- package/lib/esm/utils.d.ts +0 -3
- package/lib/esm/utils.js +0 -12
- package/lib/esm/utils.js.map +0 -1
- package/src/document-index.ts +0 -1268
- package/src/query.ts +0 -525
- package/src/utils.ts +0 -17
|
@@ -1,43 +1,38 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AbstractType,
|
|
2
|
+
type AbstractType,
|
|
3
3
|
BorshError,
|
|
4
|
-
deserialize,
|
|
5
4
|
field,
|
|
6
5
|
serialize,
|
|
7
6
|
variant
|
|
8
7
|
} from "@dao-xyz/borsh";
|
|
9
|
-
import { Change, Entry, EntryType, TrimOptions } from "@peerbit/log";
|
|
10
|
-
import { Program, ProgramEvents } from "@peerbit/program";
|
|
8
|
+
import { type Change, Entry, EntryType, type TrimOptions } from "@peerbit/log";
|
|
9
|
+
import { Program, type ProgramEvents } from "@peerbit/program";
|
|
11
10
|
import { AccessError, DecryptedThing } from "@peerbit/crypto";
|
|
12
11
|
import { logger as loggerFn } from "@peerbit/logger";
|
|
13
|
-
import { AppendOptions } from "@peerbit/log";
|
|
14
12
|
import { CustomEvent } from "@libp2p/interface";
|
|
15
13
|
import {
|
|
16
|
-
RoleOptions,
|
|
14
|
+
type RoleOptions,
|
|
17
15
|
Observer,
|
|
18
16
|
Replicator,
|
|
19
17
|
SharedLog,
|
|
20
|
-
SharedLogOptions,
|
|
21
|
-
SharedAppendOptions
|
|
18
|
+
type SharedLogOptions,
|
|
19
|
+
type SharedAppendOptions
|
|
22
20
|
} from "@peerbit/shared-log";
|
|
21
|
+
import * as types from "@peerbit/document-interface";
|
|
23
22
|
|
|
24
23
|
export type { RoleOptions }; // For convenience (so that consumers does not have to do the import above from shared-log packages)
|
|
25
24
|
|
|
26
25
|
import {
|
|
27
|
-
IndexableFields,
|
|
26
|
+
type IndexableFields,
|
|
28
27
|
BORSH_ENCODING_OPERATION,
|
|
29
28
|
DeleteOperation,
|
|
30
29
|
DocumentIndex,
|
|
31
30
|
Operation,
|
|
32
31
|
PutOperation,
|
|
33
|
-
CanSearch,
|
|
34
|
-
CanRead
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from "./document-index.js";
|
|
38
|
-
import { asString, checkKeyable, Keyable } from "./utils.js";
|
|
39
|
-
import { Context, Results } from "./query.js";
|
|
40
|
-
export { MAX_DOCUMENT_SIZE };
|
|
32
|
+
type CanSearch,
|
|
33
|
+
type CanRead
|
|
34
|
+
} from "./search.js";
|
|
35
|
+
import { MAX_BATCH_SIZE } from "./constants.js";
|
|
41
36
|
|
|
42
37
|
const logger = loggerFn({ module: "document" });
|
|
43
38
|
|
|
@@ -54,39 +49,49 @@ export interface DocumentEvents<T> {
|
|
|
54
49
|
change: CustomEvent<DocumentsChange<T>>;
|
|
55
50
|
}
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
type MaybePromise<T> = Promise<T> | T;
|
|
53
|
+
|
|
54
|
+
type CanPerformPut<T> = {
|
|
55
|
+
type: "put";
|
|
56
|
+
value: T;
|
|
57
|
+
operation: PutOperation;
|
|
58
|
+
entry: Entry<PutOperation>;
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
type
|
|
61
|
+
type CanPerformDelete<T> = {
|
|
62
|
+
type: "delete";
|
|
63
|
+
operation: DeleteOperation;
|
|
64
|
+
entry: Entry<DeleteOperation>;
|
|
65
|
+
};
|
|
62
66
|
|
|
67
|
+
export type CanPerformOperations<T> = CanPerformPut<T> | CanPerformDelete<T>;
|
|
63
68
|
export type CanPerform<T> = (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
) => MaybePromise;
|
|
69
|
+
properties: CanPerformOperations<T>
|
|
70
|
+
) => MaybePromise<boolean>;
|
|
67
71
|
|
|
68
72
|
export type SetupOptions<T> = {
|
|
69
73
|
type: AbstractType<T>;
|
|
70
|
-
canOpen?: (program: T) => MaybePromise
|
|
74
|
+
canOpen?: (program: T) => MaybePromise<boolean>;
|
|
71
75
|
canPerform?: CanPerform<T>;
|
|
76
|
+
id?: (obj: any) => types.IdPrimitive;
|
|
72
77
|
index?: {
|
|
73
|
-
|
|
74
|
-
fields?: IndexableFields<T>;
|
|
78
|
+
idProperty?: string | string[];
|
|
75
79
|
canSearch?: CanSearch;
|
|
76
80
|
canRead?: CanRead<T>;
|
|
81
|
+
fields?: IndexableFields<T>;
|
|
77
82
|
};
|
|
78
83
|
log?: {
|
|
79
84
|
trim?: TrimOptions;
|
|
80
85
|
};
|
|
81
|
-
} & SharedLogOptions<Operation
|
|
86
|
+
} & SharedLogOptions<Operation>;
|
|
82
87
|
|
|
83
88
|
@variant("documents")
|
|
84
|
-
export class Documents<T extends
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
{
|
|
89
|
+
export class Documents<T> extends Program<
|
|
90
|
+
SetupOptions<T>,
|
|
91
|
+
DocumentEvents<T> & ProgramEvents
|
|
92
|
+
> {
|
|
88
93
|
@field({ type: SharedLog })
|
|
89
|
-
log: SharedLog<Operation
|
|
94
|
+
log: SharedLog<Operation>;
|
|
90
95
|
|
|
91
96
|
@field({ type: "bool" })
|
|
92
97
|
immutable: boolean; // "Can I overwrite a document?"
|
|
@@ -98,11 +103,9 @@ export class Documents<T extends Record<string, any>>
|
|
|
98
103
|
|
|
99
104
|
private _optionCanPerform?: CanPerform<T>;
|
|
100
105
|
private _manuallySynced: Set<string>;
|
|
106
|
+
private idResolver: (any: any) => types.IdPrimitive;
|
|
101
107
|
|
|
102
|
-
canOpen?: (
|
|
103
|
-
program: T,
|
|
104
|
-
entry: Entry<Operation<T>>
|
|
105
|
-
) => Promise<boolean> | boolean;
|
|
108
|
+
canOpen?: (program: T, entry: Entry<Operation>) => Promise<boolean> | boolean;
|
|
106
109
|
|
|
107
110
|
constructor(properties?: {
|
|
108
111
|
id?: Uint8Array;
|
|
@@ -134,17 +137,34 @@ export class Documents<T extends Record<string, any>>
|
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
this._optionCanPerform = options.canPerform;
|
|
137
|
-
|
|
138
140
|
this._manuallySynced = new Set();
|
|
139
|
-
|
|
141
|
+
const idProperty = options.index?.idProperty || "id";
|
|
142
|
+
const idResolver =
|
|
143
|
+
options.id ||
|
|
144
|
+
(typeof idProperty === "string"
|
|
145
|
+
? (obj: any) => obj[idProperty as string]
|
|
146
|
+
: (obj: any) => types.extractFieldValue(obj, idProperty as string[]));
|
|
147
|
+
|
|
148
|
+
this.idResolver = idResolver;
|
|
149
|
+
|
|
150
|
+
let transform: IndexableFields<T>;
|
|
151
|
+
if (options.index?.fields) {
|
|
152
|
+
if (typeof options.index.fields === "function") {
|
|
153
|
+
transform = options.index.fields;
|
|
154
|
+
} else {
|
|
155
|
+
transform = options.index.fields;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
transform = (obj) => obj as Record<string, any>; // TODO check types
|
|
159
|
+
}
|
|
140
160
|
await this._index.open({
|
|
141
161
|
type: this._clazz,
|
|
142
162
|
log: this.log,
|
|
143
163
|
canRead: options?.index?.canRead,
|
|
144
164
|
canSearch: options.index?.canSearch,
|
|
145
|
-
fields:
|
|
146
|
-
indexBy:
|
|
147
|
-
sync: async (result: Results<T>) => {
|
|
165
|
+
fields: transform,
|
|
166
|
+
indexBy: idProperty,
|
|
167
|
+
sync: async (result: types.Results<T>) => {
|
|
148
168
|
// here we arrive for all the results we want to persist.
|
|
149
169
|
// we we need to do here is
|
|
150
170
|
// 1. add the entry to a list of entries that we should persist through prunes
|
|
@@ -166,7 +186,7 @@ export class Documents<T extends Record<string, any>>
|
|
|
166
186
|
trim: options?.log?.trim,
|
|
167
187
|
role: options?.role,
|
|
168
188
|
replicas: options?.replicas,
|
|
169
|
-
sync: (entry) => {
|
|
189
|
+
sync: (entry: any) => {
|
|
170
190
|
// here we arrive when ever a insertion/pruning behaviour processes an entry
|
|
171
191
|
// returning true means that it should persist
|
|
172
192
|
return this._manuallySynced.has(entry.gid);
|
|
@@ -178,13 +198,10 @@ export class Documents<T extends Record<string, any>>
|
|
|
178
198
|
return this.log.recover();
|
|
179
199
|
}
|
|
180
200
|
|
|
181
|
-
private async _resolveEntry(history: Entry<Operation
|
|
201
|
+
private async _resolveEntry(history: Entry<Operation> | string) {
|
|
182
202
|
return typeof history === "string"
|
|
183
203
|
? (await this.log.log.get(history)) ||
|
|
184
|
-
|
|
185
|
-
this.log.log.blocks,
|
|
186
|
-
history
|
|
187
|
-
))
|
|
204
|
+
(await Entry.fromMultihash<Operation>(this.log.log.blocks, history))
|
|
188
205
|
: history;
|
|
189
206
|
}
|
|
190
207
|
|
|
@@ -195,25 +212,48 @@ export class Documents<T extends Record<string, any>>
|
|
|
195
212
|
return this.log.role;
|
|
196
213
|
}
|
|
197
214
|
|
|
198
|
-
async canAppend(
|
|
199
|
-
|
|
215
|
+
async canAppend(
|
|
216
|
+
entry: Entry<Operation>,
|
|
217
|
+
reference?: { document: T; operation: PutOperation }
|
|
218
|
+
): Promise<boolean> {
|
|
219
|
+
const l0 = await this._canAppend(entry as Entry<Operation>, reference);
|
|
200
220
|
if (!l0) {
|
|
201
221
|
return false;
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
225
|
+
let operation: PutOperation | DeleteOperation = l0;
|
|
226
|
+
let document: T | undefined = reference?.document;
|
|
227
|
+
if (!document) {
|
|
228
|
+
if (l0 instanceof PutOperation) {
|
|
229
|
+
document = this._index.valueEncoding.decoder(l0.data);
|
|
230
|
+
if (!document) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
} else if (l0 instanceof DeleteOperation) {
|
|
234
|
+
// Nothing to do here by default
|
|
235
|
+
// checking if the document exists is not necessary
|
|
236
|
+
// since it might already be deleted
|
|
237
|
+
} else {
|
|
238
|
+
throw new Error("Unsupported operation");
|
|
239
|
+
}
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
if (this._optionCanPerform) {
|
|
211
243
|
if (
|
|
212
244
|
!(await this._optionCanPerform(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
245
|
+
operation instanceof PutOperation
|
|
246
|
+
? {
|
|
247
|
+
type: "put",
|
|
248
|
+
value: document!,
|
|
249
|
+
operation,
|
|
250
|
+
entry: entry as any as Entry<PutOperation>
|
|
251
|
+
}
|
|
252
|
+
: {
|
|
253
|
+
type: "delete",
|
|
254
|
+
operation,
|
|
255
|
+
entry: entry as any as Entry<DeleteOperation>
|
|
256
|
+
}
|
|
217
257
|
))
|
|
218
258
|
) {
|
|
219
259
|
return false;
|
|
@@ -230,14 +270,17 @@ export class Documents<T extends Record<string, any>>
|
|
|
230
270
|
return true;
|
|
231
271
|
}
|
|
232
272
|
|
|
233
|
-
async _canAppend(
|
|
234
|
-
|
|
273
|
+
async _canAppend(
|
|
274
|
+
entry: Entry<Operation>,
|
|
275
|
+
reference?: { document: T; operation: PutOperation }
|
|
276
|
+
): Promise<PutOperation | DeleteOperation | false> {
|
|
277
|
+
const resolve = async (history: Entry<Operation> | string) => {
|
|
235
278
|
return typeof history === "string"
|
|
236
279
|
? this.log.log.get(history) ||
|
|
237
|
-
|
|
280
|
+
(await Entry.fromMultihash(this.log.log.blocks, history))
|
|
238
281
|
: history;
|
|
239
282
|
};
|
|
240
|
-
const pointsToHistory = async (history: Entry<Operation
|
|
283
|
+
const pointsToHistory = async (history: Entry<Operation> | string) => {
|
|
241
284
|
// make sure nexts only points to this document at some point in history
|
|
242
285
|
let current = await resolve(history);
|
|
243
286
|
|
|
@@ -261,21 +304,22 @@ export class Documents<T extends Record<string, any>>
|
|
|
261
304
|
keychain: this.node.services.keychain
|
|
262
305
|
});
|
|
263
306
|
const operation =
|
|
264
|
-
entry._payload instanceof DecryptedThing
|
|
307
|
+
reference?.operation || entry._payload instanceof DecryptedThing
|
|
265
308
|
? entry.payload.getValue(entry.encoding)
|
|
266
309
|
: await entry.getPayloadValue();
|
|
267
310
|
if (operation instanceof PutOperation) {
|
|
268
311
|
// check nexts
|
|
269
|
-
const putOperation = operation as PutOperation
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
312
|
+
const putOperation = operation as PutOperation;
|
|
313
|
+
let value =
|
|
314
|
+
reference?.document ??
|
|
315
|
+
this.index.valueEncoding.decoder(putOperation.data);
|
|
316
|
+
const keyValue = this.idResolver(value);
|
|
274
317
|
|
|
275
|
-
|
|
318
|
+
const key = types.toId(keyValue);
|
|
276
319
|
|
|
277
|
-
const existingDocument = this.index.
|
|
278
|
-
if (existingDocument) {
|
|
320
|
+
const existingDocument = await this.index.engine.get(key);
|
|
321
|
+
if (existingDocument && existingDocument.context.head !== entry.hash) {
|
|
322
|
+
// econd condition can false if we reset the operation log, while not resetting the index. For example when doing .recover
|
|
279
323
|
if (this.immutable) {
|
|
280
324
|
//Key already exist and this instance Documents can note overrite/edit'
|
|
281
325
|
return false;
|
|
@@ -289,7 +333,8 @@ export class Documents<T extends Record<string, any>>
|
|
|
289
333
|
logger.error("Failed to find Document from head");
|
|
290
334
|
return false;
|
|
291
335
|
}
|
|
292
|
-
|
|
336
|
+
const referenceHistoryCorrectly = await pointsToHistory(doc);
|
|
337
|
+
return referenceHistoryCorrectly ? putOperation : false;
|
|
293
338
|
} else {
|
|
294
339
|
if (entry.next.length !== 0) {
|
|
295
340
|
return false;
|
|
@@ -299,18 +344,26 @@ export class Documents<T extends Record<string, any>>
|
|
|
299
344
|
if (entry.next.length !== 1) {
|
|
300
345
|
return false;
|
|
301
346
|
}
|
|
302
|
-
const existingDocument = this._index.
|
|
347
|
+
const existingDocument = await this._index.engine.get(operation.key);
|
|
303
348
|
if (!existingDocument) {
|
|
304
349
|
// already deleted
|
|
305
|
-
return
|
|
350
|
+
return operation; // assume ok
|
|
306
351
|
}
|
|
307
352
|
let doc = await this.log.log.get(existingDocument.context.head);
|
|
308
353
|
if (!doc) {
|
|
309
354
|
logger.error("Failed to find Document from head");
|
|
310
355
|
return false;
|
|
311
356
|
}
|
|
312
|
-
|
|
357
|
+
if (await pointsToHistory(doc)) {
|
|
358
|
+
// references the existing document
|
|
359
|
+
return operation;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
} else {
|
|
363
|
+
throw new Error("Unsupported operation");
|
|
313
364
|
}
|
|
365
|
+
|
|
366
|
+
return operation;
|
|
314
367
|
} catch (error) {
|
|
315
368
|
if (error instanceof AccessError) {
|
|
316
369
|
return false; // we cant index because we can not decrypt
|
|
@@ -320,65 +373,72 @@ export class Documents<T extends Record<string, any>>
|
|
|
320
373
|
}
|
|
321
374
|
throw error;
|
|
322
375
|
}
|
|
323
|
-
return true;
|
|
324
376
|
}
|
|
325
377
|
|
|
326
378
|
public async put(
|
|
327
379
|
doc: T,
|
|
328
|
-
options?: SharedAppendOptions<Operation
|
|
380
|
+
options?: SharedAppendOptions<Operation> & { unique?: boolean }
|
|
329
381
|
) {
|
|
330
|
-
const
|
|
331
|
-
|
|
382
|
+
const keyValue = this.idResolver(doc);
|
|
383
|
+
|
|
384
|
+
// type check the key
|
|
385
|
+
types.checkId(keyValue);
|
|
386
|
+
|
|
332
387
|
const ser = serialize(doc);
|
|
333
|
-
if (ser.length >
|
|
388
|
+
if (ser.length > MAX_BATCH_SIZE) {
|
|
334
389
|
throw new Error(
|
|
335
|
-
`Document is too large (${
|
|
336
|
-
|
|
337
|
-
}) mb). Needs to be less than ${MAX_DOCUMENT_SIZE * 1e-6} mb`
|
|
390
|
+
`Document is too large (${ser.length * 1e-6
|
|
391
|
+
}) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`
|
|
338
392
|
);
|
|
339
393
|
}
|
|
340
394
|
|
|
341
395
|
const existingDocument = options?.unique
|
|
342
396
|
? undefined
|
|
343
397
|
: (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
398
|
+
await this._index.getDetailed(keyValue, {
|
|
399
|
+
local: true,
|
|
400
|
+
remote: { sync: true } // only query remote if we know they exist
|
|
401
|
+
})
|
|
402
|
+
)?.[0]?.results[0];
|
|
403
|
+
|
|
404
|
+
const operation = new PutOperation({
|
|
405
|
+
data: ser
|
|
406
|
+
});
|
|
407
|
+
const appended = await this.log.append(operation, {
|
|
408
|
+
...options,
|
|
409
|
+
meta: {
|
|
410
|
+
next: existingDocument
|
|
411
|
+
? [await this._resolveEntry(existingDocument.context.head)]
|
|
412
|
+
: [],
|
|
413
|
+
...options?.meta
|
|
414
|
+
},
|
|
415
|
+
canAppend: (entry) => {
|
|
416
|
+
return this.canAppend(entry, { document: doc, operation });
|
|
417
|
+
},
|
|
418
|
+
onChange: (change) => {
|
|
419
|
+
return this.handleChanges(change, { document: doc, operation });
|
|
364
420
|
}
|
|
365
|
-
);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
return appended;
|
|
366
424
|
}
|
|
367
425
|
|
|
368
|
-
async del(
|
|
426
|
+
async del(id: types.Ideable, options?: SharedAppendOptions<Operation>) {
|
|
427
|
+
const key = types.toId(id);
|
|
369
428
|
const existing = (
|
|
370
429
|
await this._index.getDetailed(key, {
|
|
371
430
|
local: true,
|
|
372
431
|
remote: { sync: true }
|
|
373
432
|
})
|
|
374
433
|
)?.[0]?.results[0];
|
|
434
|
+
|
|
375
435
|
if (!existing) {
|
|
376
436
|
throw new Error(`No entry with key '${key}' in the database`);
|
|
377
437
|
}
|
|
378
438
|
|
|
379
439
|
return this.log.append(
|
|
380
440
|
new DeleteOperation({
|
|
381
|
-
key
|
|
441
|
+
key
|
|
382
442
|
}),
|
|
383
443
|
{
|
|
384
444
|
...options,
|
|
@@ -391,59 +451,54 @@ export class Documents<T extends Record<string, any>>
|
|
|
391
451
|
);
|
|
392
452
|
}
|
|
393
453
|
|
|
394
|
-
async handleChanges(
|
|
454
|
+
async handleChanges(
|
|
455
|
+
change: Change<Operation>,
|
|
456
|
+
reference?: { document: T; operation: PutOperation }
|
|
457
|
+
): Promise<void> {
|
|
458
|
+
const isAppendOperation =
|
|
459
|
+
change?.added.length === 1 ? !!change.added[0] : false;
|
|
460
|
+
|
|
395
461
|
const removed = [...(change.removed || [])];
|
|
396
|
-
const removedSet = new Map<string, Entry<Operation
|
|
462
|
+
const removedSet = new Map<string, Entry<Operation>>();
|
|
397
463
|
for (const r of removed) {
|
|
398
464
|
removedSet.set(r.hash, r);
|
|
399
465
|
}
|
|
400
|
-
const
|
|
466
|
+
const sortedEntries = [...change.added, ...(removed || [])]
|
|
401
467
|
.sort(this.log.log.sortFn)
|
|
402
468
|
.reverse(); // sort so we get newest to oldest
|
|
403
469
|
|
|
404
470
|
// There might be a case where change.added and change.removed contains the same document id. Usaully because you use the "trim" option
|
|
405
471
|
// in combination with inserting the same document. To mitigate this, we loop through the changes and modify the behaviour for this
|
|
406
472
|
|
|
407
|
-
let visited = new Map<string, Entry<Operation<T>>[]>();
|
|
408
|
-
for (const item of entries) {
|
|
409
|
-
const payload =
|
|
410
|
-
item._payload instanceof DecryptedThing
|
|
411
|
-
? item.payload.getValue(item.encoding)
|
|
412
|
-
: await item.getPayloadValue();
|
|
413
|
-
let itemKey: string;
|
|
414
|
-
if (
|
|
415
|
-
payload instanceof PutOperation ||
|
|
416
|
-
payload instanceof DeleteOperation
|
|
417
|
-
) {
|
|
418
|
-
itemKey = payload.key;
|
|
419
|
-
} else {
|
|
420
|
-
throw new Error("Unsupported operation type");
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
let arr = visited.get(itemKey);
|
|
424
|
-
if (!arr) {
|
|
425
|
-
arr = [];
|
|
426
|
-
visited.set(itemKey, arr);
|
|
427
|
-
}
|
|
428
|
-
arr.push(item);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
473
|
let documentsChanged: DocumentsChange<T> = {
|
|
432
474
|
added: [],
|
|
433
475
|
removed: []
|
|
434
476
|
};
|
|
435
477
|
|
|
436
|
-
|
|
478
|
+
let modified: Set<string | number | bigint> = new Set();
|
|
479
|
+
for (const item of sortedEntries) {
|
|
437
480
|
try {
|
|
438
|
-
const item = entries[0];
|
|
439
481
|
const payload =
|
|
440
482
|
item._payload instanceof DecryptedThing
|
|
441
483
|
? item.payload.getValue(item.encoding)
|
|
442
484
|
: await item.getPayloadValue();
|
|
485
|
+
|
|
443
486
|
if (payload instanceof PutOperation && !removedSet.has(item.hash)) {
|
|
444
|
-
|
|
487
|
+
let value =
|
|
488
|
+
(isAppendOperation &&
|
|
489
|
+
reference?.operation === payload &&
|
|
490
|
+
reference?.document) ||
|
|
491
|
+
this.index.valueEncoding.decoder(payload.data);
|
|
445
492
|
|
|
446
|
-
|
|
493
|
+
// get index key from value
|
|
494
|
+
const keyObject = this.idResolver(value);
|
|
495
|
+
|
|
496
|
+
const key = types.toId(keyObject);
|
|
497
|
+
|
|
498
|
+
// document is already updated with more recent entry
|
|
499
|
+
if (modified.has(key.primitive)) {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
447
502
|
|
|
448
503
|
// Program specific
|
|
449
504
|
if (value instanceof Program) {
|
|
@@ -460,26 +515,8 @@ export class Documents<T extends Record<string, any>>
|
|
|
460
515
|
}
|
|
461
516
|
}
|
|
462
517
|
documentsChanged.added.push(value);
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
created:
|
|
466
|
-
this._index.index.get(key)?.context.created ||
|
|
467
|
-
item.meta.clock.timestamp.wallTime,
|
|
468
|
-
modified: item.meta.clock.timestamp.wallTime,
|
|
469
|
-
head: item.hash,
|
|
470
|
-
gid: item.gid
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
const valueToIndex = this._index.toIndex(value, context);
|
|
474
|
-
this._index.index.set(key, {
|
|
475
|
-
key: payload.key,
|
|
476
|
-
value: isPromise(valueToIndex) ? await valueToIndex : valueToIndex,
|
|
477
|
-
context,
|
|
478
|
-
reference:
|
|
479
|
-
valueToIndex === value || value instanceof Program
|
|
480
|
-
? { value, last: payload }
|
|
481
|
-
: undefined
|
|
482
|
-
});
|
|
518
|
+
this._index.put(value, item, key);
|
|
519
|
+
modified.add(key.primitive);
|
|
483
520
|
} else if (
|
|
484
521
|
(payload instanceof DeleteOperation && !removedSet.has(item.hash)) ||
|
|
485
522
|
payload instanceof PutOperation ||
|
|
@@ -487,16 +524,30 @@ export class Documents<T extends Record<string, any>>
|
|
|
487
524
|
) {
|
|
488
525
|
this._manuallySynced.delete(item.gid);
|
|
489
526
|
|
|
490
|
-
const key = (payload as DeleteOperation | PutOperation<T>).key;
|
|
491
|
-
if (!this.index.index.has(key)) {
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
527
|
let value: T;
|
|
528
|
+
let key: string | number | bigint;
|
|
529
|
+
|
|
496
530
|
if (payload instanceof PutOperation) {
|
|
497
|
-
value = this.
|
|
531
|
+
value = this.index.valueEncoding.decoder(payload.data);
|
|
532
|
+
key = types.toIdeable(this.idResolver(value));
|
|
533
|
+
// document is already updated with more recent entry
|
|
534
|
+
if (modified.has(key)) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
498
537
|
} else if (payload instanceof DeleteOperation) {
|
|
499
|
-
|
|
538
|
+
key = payload.key.primitive;
|
|
539
|
+
// document is already updated with more recent entry
|
|
540
|
+
if (modified.has(key)) {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const document = await this._index.get(key, {
|
|
544
|
+
local: true,
|
|
545
|
+
remote: false
|
|
546
|
+
});
|
|
547
|
+
if (!document) {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
value = document;
|
|
500
551
|
} else {
|
|
501
552
|
throw new Error("Unexpected");
|
|
502
553
|
}
|
|
@@ -508,7 +559,9 @@ export class Documents<T extends Record<string, any>>
|
|
|
508
559
|
}
|
|
509
560
|
|
|
510
561
|
// update index
|
|
511
|
-
this._index.
|
|
562
|
+
this._index.del(key);
|
|
563
|
+
|
|
564
|
+
modified.add(key);
|
|
512
565
|
} else {
|
|
513
566
|
// Unknown operation
|
|
514
567
|
}
|
|
@@ -524,24 +577,4 @@ export class Documents<T extends Record<string, any>>
|
|
|
524
577
|
new CustomEvent("change", { detail: documentsChanged })
|
|
525
578
|
);
|
|
526
579
|
}
|
|
527
|
-
|
|
528
|
-
private async getDocumentFromEntry(entry: Entry<Operation<T>>) {
|
|
529
|
-
const payloadValue = await entry.getPayloadValue();
|
|
530
|
-
if (payloadValue instanceof PutOperation) {
|
|
531
|
-
return payloadValue.getValue(this.index.valueEncoding);
|
|
532
|
-
}
|
|
533
|
-
throw new Error("Unexpected");
|
|
534
|
-
}
|
|
535
|
-
deserializeOrPass(value: PutOperation<T>): T {
|
|
536
|
-
if (value._value) {
|
|
537
|
-
return value._value;
|
|
538
|
-
} else {
|
|
539
|
-
value._value = deserialize(value.data, this.index.type);
|
|
540
|
-
return value._value!;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
function isPromise(value) {
|
|
546
|
-
return Boolean(value && typeof value.then === "function");
|
|
547
580
|
}
|