@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.
Files changed (47) hide show
  1. package/README.md +2 -2
  2. package/dist/benchmark/index.d.ts +2 -0
  3. package/dist/benchmark/index.d.ts.map +1 -0
  4. package/dist/benchmark/index.js +126 -0
  5. package/dist/benchmark/index.js.map +1 -0
  6. package/dist/benchmark/replication.d.ts +2 -0
  7. package/dist/benchmark/replication.d.ts.map +1 -0
  8. package/dist/benchmark/replication.js +174 -0
  9. package/dist/benchmark/replication.js.map +1 -0
  10. package/dist/src/constants.d.ts +2 -0
  11. package/dist/src/constants.d.ts.map +1 -0
  12. package/dist/src/constants.js +2 -0
  13. package/dist/src/constants.js.map +1 -0
  14. package/dist/src/index.d.ts +5 -0
  15. package/dist/src/index.d.ts.map +1 -0
  16. package/dist/src/index.js +5 -0
  17. package/dist/src/index.js.map +1 -0
  18. package/dist/src/program.d.ts +90 -0
  19. package/dist/src/program.d.ts.map +1 -0
  20. package/{lib/esm/document-store.js → dist/src/program.js} +141 -109
  21. package/dist/src/program.js.map +1 -0
  22. package/dist/src/search.d.ts +118 -0
  23. package/dist/src/search.d.ts.map +1 -0
  24. package/{lib/esm/document-index.js → dist/src/search.js} +246 -446
  25. package/dist/src/search.js.map +1 -0
  26. package/package.json +69 -43
  27. package/src/constants.ts +1 -0
  28. package/src/index.ts +4 -3
  29. package/src/{document-store.ts → program.ts} +216 -183
  30. package/src/search.ts +997 -0
  31. package/LICENSE +0 -202
  32. package/lib/esm/document-index.d.ts +0 -147
  33. package/lib/esm/document-index.js.map +0 -1
  34. package/lib/esm/document-store.d.ts +0 -72
  35. package/lib/esm/document-store.js.map +0 -1
  36. package/lib/esm/index.d.ts +0 -3
  37. package/lib/esm/index.js +0 -4
  38. package/lib/esm/index.js.map +0 -1
  39. package/lib/esm/query.d.ts +0 -191
  40. package/lib/esm/query.js +0 -615
  41. package/lib/esm/query.js.map +0 -1
  42. package/lib/esm/utils.d.ts +0 -3
  43. package/lib/esm/utils.js +0 -12
  44. package/lib/esm/utils.js.map +0 -1
  45. package/src/document-index.ts +0 -1268
  46. package/src/query.ts +0 -525
  47. 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
- InMemoryIndex,
36
- MAX_DOCUMENT_SIZE
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
- export type TransactionContext<T> = {
58
- entry: Entry<Operation<T>>;
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 MaybePromise = Promise<boolean> | boolean;
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
- operation: PutOperation<T> | DeleteOperation,
65
- context: TransactionContext<T>
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
- key?: string | string[];
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<T>>;
86
+ } & SharedLogOptions<Operation>;
82
87
 
83
88
  @variant("documents")
84
- export class Documents<T extends Record<string, any>>
85
- extends Program<SetupOptions<T>, DocumentEvents<T> & ProgramEvents>
86
- implements InMemoryIndex<T>
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<T>>;
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: options.index?.fields || ((obj) => obj),
146
- indexBy: options.index?.key,
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<T>> | string) {
201
+ private async _resolveEntry(history: Entry<Operation> | string) {
182
202
  return typeof history === "string"
183
203
  ? (await this.log.log.get(history)) ||
184
- (await Entry.fromMultihash<Operation<T>>(
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(entry: Entry<Operation<T>>): Promise<boolean> {
199
- const l0 = await this._canAppend(entry);
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
- const payload = await entry.getPayloadValue();
206
- if (payload instanceof PutOperation) {
207
- (payload as PutOperation<T>).getValue(this.index.valueEncoding); // Decode they value so callbacks can jsut do .value
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
- payload as PutOperation<T> | DeleteOperation,
214
- {
215
- entry
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(entry: Entry<Operation<T>>): Promise<boolean> {
234
- const resolve = async (history: Entry<Operation<T>> | string) => {
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
- (await Entry.fromMultihash(this.log.log.blocks, history))
280
+ (await Entry.fromMultihash(this.log.log.blocks, history))
238
281
  : history;
239
282
  };
240
- const pointsToHistory = async (history: Entry<Operation<T>> | string) => {
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<T>;
270
-
271
- const key = this._index.indexByResolver(
272
- putOperation.getValue(this.index.valueEncoding)
273
- ) as Keyable;
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
- checkKeyable(key);
318
+ const key = types.toId(keyValue);
276
319
 
277
- const existingDocument = this.index.index.get(asString(key));
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
- return pointsToHistory(doc);
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.index.get(operation.key);
347
+ const existingDocument = await this._index.engine.get(operation.key);
303
348
  if (!existingDocument) {
304
349
  // already deleted
305
- return true; // assume ok
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
- return pointsToHistory(doc); // references the existing document
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<T>> & { unique?: boolean }
380
+ options?: SharedAppendOptions<Operation> & { unique?: boolean }
329
381
  ) {
330
- const key = this._index.indexByResolver(doc as any as Keyable);
331
- checkKeyable(key);
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 > MAX_DOCUMENT_SIZE) {
388
+ if (ser.length > MAX_BATCH_SIZE) {
334
389
  throw new Error(
335
- `Document is too large (${
336
- ser.length * 1e-6
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
- await this._index.getDetailed(key, {
345
- local: true,
346
- remote: { sync: true } // only query remote if we know they exist
347
- })
348
- )?.[0]?.results[0];
349
-
350
- return this.log.append(
351
- new PutOperation({
352
- key: asString(key),
353
- data: ser,
354
- value: doc
355
- }),
356
- {
357
- ...options,
358
- meta: {
359
- next: existingDocument
360
- ? [await this._resolveEntry(existingDocument.context.head)]
361
- : [],
362
- ...options?.meta
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(key: Keyable, options?: SharedAppendOptions<Operation<T>>) {
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: asString(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(change: Change<Operation<T>>): Promise<void> {
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<T>>>();
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 entries = [...change.added, ...(removed || [])]
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
- for (const [_key, entries] of visited) {
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
- const key = payload.key;
487
+ let value =
488
+ (isAppendOperation &&
489
+ reference?.operation === payload &&
490
+ reference?.document) ||
491
+ this.index.valueEncoding.decoder(payload.data);
445
492
 
446
- let value = this.deserializeOrPass(payload);
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
- const context = new Context({
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.deserializeOrPass(payload);
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
- value = await this.getDocumentFromEntry(entries[1]!);
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.index.delete(key);
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
  }