@peerbit/document 15.0.12 → 15.0.14

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.
@@ -32,7 +32,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
32
32
  }
33
33
  return useValue ? value : void 0;
34
34
  };
35
- import { BorshError, field, serialize, variant, } from "@dao-xyz/borsh";
35
+ import { BorshError, deserialize, field, serialize, variant, } from "@dao-xyz/borsh";
36
36
  import { AccessError } from "@peerbit/crypto";
37
37
  import { Context, NotFoundError, } from "@peerbit/document-interface";
38
38
  import { extractDocumentFieldSimple, initializeDocumentRust, planDocumentContext, planDocumentContextBatch, tryPlanDocumentContext, tryPlanDocumentContextBatch, } from "./native-rust.js";
@@ -41,6 +41,7 @@ import { Entry, EntryType, LamportClock, ShallowEntry, ShallowMeta, Timestamp, e
41
41
  import { logger as loggerFn } from "@peerbit/logger";
42
42
  import { Program } from "@peerbit/program";
43
43
  import { SharedLog, } from "@peerbit/shared-log";
44
+ import { detachCanPerformCallbackProperties, detachEntryPayloadForCallback, } from "./callback-detachment.js";
44
45
  import { MAX_BATCH_SIZE } from "./constants.js";
45
46
  import { BORSH_ENCODING_OPERATION, DeleteOperation, PutOperation, PutWithKeyOperation, coerceDeleteOperation, isDeleteOperation, isPutOperation, } from "./operation.js";
46
47
  import { createCanPerformPolicyEvaluator, createCanPerformDeletePolicyEvaluator, getCanPerformPolicyDescriptor, canPerformPolicyDeleteFieldPaths, canPerformPolicyNeedsDeleteValue, canPerformPolicyNeedsPreviousEntries, canPerformPolicyPutNeedsEntryPublicKeys, canPerformPolicySignedByFieldPaths, } from "./policy.js";
@@ -392,9 +393,16 @@ let Documents = (() => {
392
393
  _documentChangeListenerTrackingInitialized = false;
393
394
  _documentBackend;
394
395
  _canAppendDecodedDocuments = new WeakMap();
396
+ acceptPutOperation(operation, document, hasDocument) {
397
+ if (hasDocument) {
398
+ this._canAppendDecodedDocuments.set(operation, document);
399
+ }
400
+ return operation;
401
+ }
395
402
  _nativeDocumentIdExtractionPlan;
396
403
  _nativeDocumentFieldExtractionPlans;
397
404
  _hasLogTrim = false;
405
+ _hasCustomIdResolver = false;
398
406
  idResolver;
399
407
  domain;
400
408
  strictHistory;
@@ -418,6 +426,70 @@ let Documents = (() => {
418
426
  ? new NativeDocumentBackend(this.createNativeDocumentBackendContext())
419
427
  : new CompatDocumentBackend(this.putCompatDocumentBackend.bind(this), this.putManyCompatDocumentBackend.bind(this), this.delCompatDocumentBackend.bind(this));
420
428
  }
429
+ detachDomainEntryCallback(domain) {
430
+ const boundMethods = new Map();
431
+ const detachedFromEntry = (entry) => {
432
+ const fromEntry = Reflect.get(domain, "fromEntry", domain);
433
+ return Reflect.apply(fromEntry, domain, [
434
+ entry instanceof Entry
435
+ ? detachEntryPayloadForCallback(entry)
436
+ : entry,
437
+ ]);
438
+ };
439
+ const facade = Object.create(Object.getPrototypeOf(domain));
440
+ let callbackDomain;
441
+ callbackDomain = new Proxy(facade, {
442
+ get(_target, property) {
443
+ if (property === "fromEntry") {
444
+ return detachedFromEntry;
445
+ }
446
+ if (property === "valueOf") {
447
+ return () => callbackDomain;
448
+ }
449
+ const value = Reflect.get(domain, property, domain);
450
+ if (property === "constructor" || typeof value !== "function") {
451
+ return value;
452
+ }
453
+ const existing = boundMethods.get(property);
454
+ if (existing && existing.source === value) {
455
+ return existing.bound;
456
+ }
457
+ const bound = value.bind(domain);
458
+ boundMethods.set(property, { source: value, bound });
459
+ return bound;
460
+ },
461
+ set(_target, property, value) {
462
+ return Reflect.set(domain, property, value, domain);
463
+ },
464
+ has(_target, property) {
465
+ return Reflect.has(domain, property);
466
+ },
467
+ ownKeys() {
468
+ return Reflect.ownKeys(domain);
469
+ },
470
+ getOwnPropertyDescriptor(_target, property) {
471
+ const descriptor = Reflect.getOwnPropertyDescriptor(domain, property);
472
+ if (!descriptor) {
473
+ return undefined;
474
+ }
475
+ if (property === "fromEntry") {
476
+ return "value" in descriptor
477
+ ? {
478
+ ...descriptor,
479
+ value: detachedFromEntry,
480
+ configurable: true,
481
+ }
482
+ : {
483
+ ...descriptor,
484
+ get: () => detachedFromEntry,
485
+ configurable: true,
486
+ };
487
+ }
488
+ return { ...descriptor, configurable: true };
489
+ },
490
+ });
491
+ return callbackDomain;
492
+ }
421
493
  createNativeDocumentBackendContext() {
422
494
  return {
423
495
  assertPlainPutSupported: (doc, options) => {
@@ -1027,9 +1099,11 @@ let Documents = (() => {
1027
1099
  * Those callers must keep throwing: outside change delivery, a closed index is
1028
1100
  * a caller error, not a state to paper over.
1029
1101
  */
1030
- getLocalIndexedContextForChange(key) {
1102
+ getLocalIndexedContextForChange(key, orderedSession) {
1031
1103
  try {
1032
- const result = this.getLocalIndexedContext(key);
1104
+ const result = (orderedSession
1105
+ ? orderedSession.get(key, { shape: INDEX_CONTEXT_SHAPE })
1106
+ : this.getLocalIndexedContext(key));
1033
1107
  return (isPromiseLike(result)
1034
1108
  ? result.catch((error) => this.recoverClosedIndexContextRead(error))
1035
1109
  : result);
@@ -1214,6 +1288,7 @@ let Documents = (() => {
1214
1288
  (typeof idProperty === "string"
1215
1289
  ? (obj) => obj[idProperty]
1216
1290
  : (obj) => indexerTypes.extractFieldValue(obj, idProperty));
1291
+ this._hasCustomIdResolver = options.id != null;
1217
1292
  this.idResolver = idResolver;
1218
1293
  this.strictHistory = options.strictHistory ?? false;
1219
1294
  this._hasLogTrim = options.log?.trim != null;
@@ -1250,7 +1325,9 @@ let Documents = (() => {
1250
1325
  // B12: the historical document->log compatibility mapping (6 -> log v8,
1251
1326
  // 7 -> log v9) is retired; the rejection at the top of open() fires for
1252
1327
  // any defined value before this point.
1253
- this.domain = options.domain?.(this);
1328
+ this.domain = options.domain
1329
+ ? this.detachDomainEntryCallback(options.domain(this))
1330
+ : undefined;
1254
1331
  let keepFunction;
1255
1332
  if (options?.keep === "self") {
1256
1333
  this.keepCache = new Set();
@@ -1291,7 +1368,16 @@ let Documents = (() => {
1291
1368
  };
1292
1369
  }
1293
1370
  else {
1294
- keepFunction = options?.keep;
1371
+ const keep = options?.keep;
1372
+ keepFunction = keep
1373
+ ? function (entry) {
1374
+ return Reflect.apply(keep, this, [
1375
+ entry instanceof Entry
1376
+ ? detachEntryPayloadForCallback(entry)
1377
+ : entry,
1378
+ ]);
1379
+ }
1380
+ : undefined;
1295
1381
  }
1296
1382
  await this.log.open({
1297
1383
  encoding: BORSH_ENCODING_OPERATION,
@@ -1314,7 +1400,7 @@ let Documents = (() => {
1314
1400
  distributionDebounceTime: options?.distributionDebounceTime,
1315
1401
  strictFullReplicaFallback: false,
1316
1402
  domain: options?.domain
1317
- ? () => options.domain(this)
1403
+ ? () => this.detachDomainEntryCallback(options.domain(this))
1318
1404
  : undefined,
1319
1405
  eagerBlocks: options?.eagerBlocks,
1320
1406
  fanout: options?.fanout,
@@ -1373,41 +1459,41 @@ let Documents = (() => {
1373
1459
  return false;
1374
1460
  }
1375
1461
  try {
1376
- let operation = l0;
1462
+ const operation = l0;
1377
1463
  if (this._optionCanPerform) {
1378
1464
  if (this._optionCanPerformNativePolicy && this.isNativeMode()) {
1379
1465
  return this.nativeCanPerformAllowsAppend(this._optionCanPerformNativePolicy, operation, entry, reference?.document);
1380
1466
  }
1381
- let document = reference?.document;
1382
- if (!document) {
1383
- if (isPutOperation(l0)) {
1384
- document =
1385
- this._canAppendDecodedDocuments.get(l0) ??
1386
- this._index.valueEncoding.decoder(l0.data);
1387
- if (!document) {
1388
- return false;
1389
- }
1390
- }
1391
- else if (isDeleteOperation(l0)) {
1392
- // Nothing to do here by default.
1393
- // Checking if the document exists is not necessary since it
1394
- // might already be deleted.
1395
- }
1396
- else {
1397
- throw new Error("Unsupported operation");
1467
+ let document;
1468
+ if (isPutOperation(l0)) {
1469
+ const cachedDocument = this._canAppendDecodedDocuments.get(l0);
1470
+ this._canAppendDecodedDocuments.delete(l0);
1471
+ document =
1472
+ cachedDocument ??
1473
+ this._index.valueEncoding.decoder(new Uint8Array(l0.data));
1474
+ if (!document) {
1475
+ return false;
1398
1476
  }
1399
1477
  }
1478
+ else if (isDeleteOperation(l0)) {
1479
+ // Nothing to do here by default.
1480
+ // Checking if the document exists is not necessary since it
1481
+ // might already be deleted.
1482
+ }
1483
+ else {
1484
+ throw new Error("Unsupported operation");
1485
+ }
1400
1486
  const previousEntries = this._optionCanPerformNativePolicy &&
1401
1487
  isPutOperation(operation) &&
1402
1488
  canPerformPolicyNeedsPreviousEntries(this._optionCanPerformNativePolicy)
1403
- ? await this.resolveCanPerformPreviousEntries(entry)
1489
+ ? (await this.resolveCanPerformPreviousEntries(entry)).map(detachEntryPayloadForCallback)
1404
1490
  : undefined;
1405
1491
  const deleteValue = this._optionCanPerformNativePolicy &&
1406
1492
  isDeleteOperation(operation) &&
1407
1493
  canPerformPolicyNeedsDeleteValue(this._optionCanPerformNativePolicy)
1408
1494
  ? await this.resolveCanPerformDeleteValue(operation)
1409
1495
  : undefined;
1410
- if (!(await this._optionCanPerform(isPutOperation(operation)
1496
+ if (!(await this._optionCanPerform(detachCanPerformCallbackProperties(isPutOperation(operation)
1411
1497
  ? {
1412
1498
  type: "put",
1413
1499
  value: document,
@@ -1420,7 +1506,7 @@ let Documents = (() => {
1420
1506
  value: deleteValue,
1421
1507
  operation,
1422
1508
  entry: entry,
1423
- }))) {
1509
+ })))) {
1424
1510
  return false;
1425
1511
  }
1426
1512
  }
@@ -1483,6 +1569,9 @@ let Documents = (() => {
1483
1569
  }
1484
1570
  return entries;
1485
1571
  }
1572
+ detachDocumentValueForCallback(document) {
1573
+ return this._index.valueEncoding.decoder(this._index.valueEncoding.encoder(document));
1574
+ }
1486
1575
  async resolveCanPerformDeleteValue(operation, options) {
1487
1576
  const key = operation.key instanceof indexerTypes.IdKey
1488
1577
  ? operation.key
@@ -1494,11 +1583,11 @@ let Documents = (() => {
1494
1583
  }
1495
1584
  const indexedDocument = await this.getLocalIdentityDocumentByHead(existingHead);
1496
1585
  if (indexedDocument) {
1497
- return indexedDocument;
1586
+ return this.detachDocumentValueForCallback(indexedDocument);
1498
1587
  }
1499
1588
  const indexedPolicyDocument = await this.getLocalIndexedDocumentForNativeDeletePolicy(key);
1500
1589
  if (indexedPolicyDocument) {
1501
- return indexedPolicyDocument;
1590
+ return deserialize(serialize(indexedPolicyDocument), this._index.indexedType);
1502
1591
  }
1503
1592
  if (options?.allowEntryFallback === false) {
1504
1593
  return;
@@ -1510,7 +1599,7 @@ let Documents = (() => {
1510
1599
  if (!isPutOperation(existingOperation)) {
1511
1600
  return;
1512
1601
  }
1513
- return this._index.valueEncoding.decoder(existingOperation.data);
1602
+ return this._index.valueEncoding.decoder(new Uint8Array(existingOperation.data));
1514
1603
  }
1515
1604
  async _canAppend(entry, reference) {
1516
1605
  const resolve = async (history) => {
@@ -1552,9 +1641,13 @@ let Documents = (() => {
1552
1641
  if (isPutOperation(operation)) {
1553
1642
  // check nexts
1554
1643
  const putOperation = operation;
1644
+ let decodedDocumentForCanPerform;
1645
+ let hasDecodedDocumentForCanPerform = false;
1555
1646
  let keyValue;
1556
1647
  if (reference?.document) {
1557
- keyValue = this.idResolver(reference.document);
1648
+ keyValue = this.idResolver(this._hasCustomIdResolver
1649
+ ? this.index.valueEncoding.decoder(new Uint8Array(putOperation.data))
1650
+ : reference.document);
1558
1651
  }
1559
1652
  else {
1560
1653
  keyValue = await this.getNativeDocumentIdFromPutOperation(putOperation);
@@ -1562,8 +1655,13 @@ let Documents = (() => {
1562
1655
  if (this.isNativeMode()) {
1563
1656
  return false;
1564
1657
  }
1565
- const value = this.index.valueEncoding.decoder(putOperation.data);
1566
- this._canAppendDecodedDocuments.set(putOperation, value);
1658
+ const value = this.index.valueEncoding.decoder(this._hasCustomIdResolver || this._optionCanPerform
1659
+ ? new Uint8Array(putOperation.data)
1660
+ : putOperation.data);
1661
+ if (this._optionCanPerform && !this._hasCustomIdResolver) {
1662
+ decodedDocumentForCanPerform = value;
1663
+ hasDecodedDocumentForCanPerform = true;
1664
+ }
1567
1665
  keyValue = this.idResolver(value);
1568
1666
  }
1569
1667
  }
@@ -1594,7 +1692,7 @@ let Documents = (() => {
1594
1692
  if (entry.meta.next.length > 0) {
1595
1693
  return false; // can not append to immutable document
1596
1694
  }
1597
- return putOperation;
1695
+ return this.acceptPutOperation(putOperation, decodedDocumentForCanPerform, hasDecodedDocumentForCanPerform);
1598
1696
  }
1599
1697
  else {
1600
1698
  if (this.strictHistory) {
@@ -1603,7 +1701,7 @@ let Documents = (() => {
1603
1701
  return false;
1604
1702
  }
1605
1703
  if (entry.meta.next[0] === existingContext.head) {
1606
- return putOperation;
1704
+ return this.acceptPutOperation(putOperation, decodedDocumentForCanPerform, hasDecodedDocumentForCanPerform);
1607
1705
  }
1608
1706
  const prevEntry = await this.log.log.entryIndex.get(existingContext.head);
1609
1707
  if (!prevEntry) {
@@ -1612,16 +1710,19 @@ let Documents = (() => {
1612
1710
  return false;
1613
1711
  }
1614
1712
  const referenceHistoryCorrectly = await pointsToHistory(prevEntry);
1615
- return referenceHistoryCorrectly ? putOperation : false;
1713
+ return referenceHistoryCorrectly
1714
+ ? this.acceptPutOperation(putOperation, decodedDocumentForCanPerform, hasDecodedDocumentForCanPerform)
1715
+ : false;
1616
1716
  }
1617
1717
  else {
1618
- return putOperation;
1718
+ return this.acceptPutOperation(putOperation, decodedDocumentForCanPerform, hasDecodedDocumentForCanPerform);
1619
1719
  }
1620
1720
  }
1621
1721
  }
1622
1722
  else {
1623
1723
  // Keep existing behavior: next pointers may express document dependencies.
1624
1724
  }
1725
+ return this.acceptPutOperation(putOperation, decodedDocumentForCanPerform, hasDecodedDocumentForCanPerform);
1625
1726
  }
1626
1727
  else if (isDeleteOperation(operation)) {
1627
1728
  if (entry.meta.next.length !== 1) {
@@ -1669,7 +1770,6 @@ let Documents = (() => {
1669
1770
  else {
1670
1771
  throw new Error("Unsupported operation");
1671
1772
  }
1672
- return operation;
1673
1773
  }
1674
1774
  catch (error) {
1675
1775
  if (error instanceof AccessError) {
@@ -3320,6 +3420,15 @@ let Documents = (() => {
3320
3420
  }
3321
3421
  async handleChanges(change, reference) {
3322
3422
  logger.trace("handleChanges called", change);
3423
+ if (reference == null &&
3424
+ change.added.length >= 2 &&
3425
+ !this.isNativeMode() &&
3426
+ this._index.canUseOrderedWriteSession()) {
3427
+ return this._index.withOrderedWriteSession((orderedSession) => this.handleChangesInOrder(change, reference, orderedSession));
3428
+ }
3429
+ return this.handleChangesInOrder(change, reference);
3430
+ }
3431
+ async handleChangesInOrder(change, reference, orderedSession) {
3323
3432
  const isAppendOperation = change?.added.length === 1 ? !!change.added[0] : false;
3324
3433
  const removedSet = new Map();
3325
3434
  for (const r of change.removed) {
@@ -3348,6 +3457,7 @@ let Documents = (() => {
3348
3457
  let modified = new Set();
3349
3458
  for (const item of sortedEntries) {
3350
3459
  if (!item) {
3460
+ await orderedSession?.flush();
3351
3461
  continue;
3352
3462
  }
3353
3463
  try {
@@ -3358,6 +3468,7 @@ let Documents = (() => {
3358
3468
  ? reference.operation
3359
3469
  : await this.getAppendOperation(item);
3360
3470
  if (!payload) {
3471
+ await orderedSession?.flush();
3361
3472
  continue;
3362
3473
  }
3363
3474
  if (isPutOperation(payload) && !removedSet.has(item.hash)) {
@@ -3391,14 +3502,20 @@ let Documents = (() => {
3391
3502
  }
3392
3503
  }
3393
3504
  }
3394
- let value = (isReferencedAppendEntry && reference?.document) ||
3395
- this.index.valueEncoding.decoder(payload.data);
3505
+ let value = isReferencedAppendEntry && reference?.document
3506
+ ? reference.document
3507
+ : this.index.valueEncoding.decoder(new Uint8Array(payload.data));
3396
3508
  // get index key from value
3397
3509
  const key = isReferencedAppendEntry && reference?.key
3398
3510
  ? reference.key
3399
- : indexerTypes.toId(this.idResolver(value));
3511
+ : indexerTypes.toId(this.idResolver(this._hasCustomIdResolver
3512
+ ? this.index.valueEncoding.decoder(new Uint8Array(payload.data))
3513
+ : value));
3400
3514
  // document is already updated with more recent entry
3401
3515
  if (modified.has(key.primitive)) {
3516
+ // Duplicate ids deliberately retain the scalar boundary until
3517
+ // their callback and event semantics are proven independently.
3518
+ await orderedSession?.flush();
3402
3519
  continue;
3403
3520
  }
3404
3521
  // if no casual ordering is used, use timestamps to order docs
@@ -3408,7 +3525,7 @@ let Documents = (() => {
3408
3525
  ? reference.existing
3409
3526
  : this.isNativeMode()
3410
3527
  ? this.getNativeModeIndexedContext(key) || null
3411
- : (await this.getLocalIndexedContextForChange(key)) || null;
3528
+ : (await this.getLocalIndexedContextForChange(key, orderedSession)) || null;
3412
3529
  if (!this.strictHistory && existing) {
3413
3530
  // if immutable use oldest, else use newest
3414
3531
  let shouldIgnoreChange = this.immutable
@@ -3417,9 +3534,16 @@ let Documents = (() => {
3417
3534
  : existing.value.__context.modified >
3418
3535
  item.meta.clock.timestamp.wallTime;
3419
3536
  if (shouldIgnoreChange) {
3537
+ await orderedSession?.flush();
3420
3538
  continue;
3421
3539
  }
3422
3540
  }
3541
+ const useOrderedWrite = orderedSession != null &&
3542
+ existing == null &&
3543
+ !(value instanceof Program);
3544
+ if (!useOrderedWrite) {
3545
+ await orderedSession?.flush();
3546
+ }
3423
3547
  // Program specific
3424
3548
  if (value instanceof Program) {
3425
3549
  // if replicator, then open
@@ -3433,13 +3557,14 @@ let Documents = (() => {
3433
3557
  modified.add(key.primitive);
3434
3558
  continue;
3435
3559
  }
3436
- const { context, indexable } = await this._index.put(value, key, item, existing);
3560
+ const { context, indexable } = await this._index.put(value, key, item, existing, useOrderedWrite ? orderedSession : undefined);
3437
3561
  documentsChanged?.added.push(coerceWithIndexed(coerceWithContext(value, context), indexable));
3438
3562
  modified.add(key.primitive);
3439
3563
  }
3440
3564
  else if ((isDeleteOperation(payload) && !removedSet.has(item.hash)) ||
3441
3565
  isPutOperation(payload) ||
3442
3566
  removedSet.has(item.hash)) {
3567
+ await orderedSession?.flush();
3443
3568
  if (!documentsChanged &&
3444
3569
  (await this.collectRemovedPutChangeFromNativeId(payload, modified))) {
3445
3570
  continue;
@@ -3453,11 +3578,13 @@ let Documents = (() => {
3453
3578
  }
3454
3579
  catch (error) {
3455
3580
  if (error instanceof AccessError) {
3581
+ await orderedSession?.flush();
3456
3582
  continue;
3457
3583
  }
3458
3584
  throw error;
3459
3585
  }
3460
3586
  }
3587
+ await orderedSession?.flush();
3461
3588
  if (canRemoveByIndexedHead && change.removed.length > 0) {
3462
3589
  const handled = await this.collectRemovedDocumentChangesFromIndexedHeads(change.removed, modified);
3463
3590
  const remainingRemoved = change.removed.filter((entry) => !handled.has(entry.hash));