@peerbit/document 15.0.11 → 15.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/document",
3
- "version": "15.0.11",
3
+ "version": "15.0.13",
4
4
  "description": "Document store implementation",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -61,20 +61,20 @@
61
61
  "p-defer": "^4.0.0",
62
62
  "uint8arrays": "^5.1.0",
63
63
  "@peerbit/cache": "3.1.1",
64
+ "@peerbit/document-interface": "3.2.66",
64
65
  "@peerbit/crypto": "3.1.6",
65
- "@peerbit/document-interface": "3.2.64",
66
- "@peerbit/indexer-cache": "0.2.14",
67
- "@peerbit/indexer-interface": "3.0.11",
68
- "@peerbit/indexer-simple": "1.2.15",
69
- "@peerbit/indexer-sqlite3": "3.0.16",
70
- "@peerbit/log": "6.2.21",
71
- "@peerbit/program": "6.0.53",
66
+ "@peerbit/indexer-cache": "0.2.15",
67
+ "@peerbit/indexer-interface": "3.0.12",
68
+ "@peerbit/indexer-simple": "1.2.16",
69
+ "@peerbit/indexer-sqlite3": "3.0.18",
70
+ "@peerbit/log": "6.2.23",
72
71
  "@peerbit/logger": "2.0.2",
72
+ "@peerbit/program": "6.0.54",
73
73
  "@peerbit/pubsub": "5.4.2",
74
- "@peerbit/rpc": "6.1.21",
74
+ "@peerbit/shared-log": "16.0.13",
75
+ "@peerbit/rpc": "6.1.22",
75
76
  "@peerbit/stream-interface": "6.0.16",
76
- "@peerbit/time": "3.0.1",
77
- "@peerbit/shared-log": "16.0.11"
77
+ "@peerbit/time": "3.0.1"
78
78
  },
79
79
  "optionalDependencies": {
80
80
  "@peerbit/document-rust": "0.1.1"
@@ -87,8 +87,8 @@
87
87
  "uuid": "^11.1.1",
88
88
  "@peerbit/log-rust": "1.1.4",
89
89
  "@peerbit/native-backbone": "0.2.10",
90
- "@peerbit/test-utils": "3.1.31",
91
- "peerbit": "5.3.31"
90
+ "@peerbit/test-utils": "3.1.33",
91
+ "peerbit": "5.3.33"
92
92
  },
93
93
  "repository": {
94
94
  "type": "git",
@@ -102,6 +102,8 @@
102
102
  "scripts": {
103
103
  "clean": "aegir clean",
104
104
  "build": "aegir build --no-bundle",
105
+ "bench:ordered-remote-projection": "node --loader ts-node/esm ./benchmark/ordered-remote-projection.ts",
106
+ "bench:shared-fs-cold-join": "node --loader ts-node/esm ./benchmark/shared-fs-cold-join.ts",
105
107
  "test": "aegir test --target node",
106
108
  "test:document-rust-core": "npm run build && PEERBIT_SHARED_LOG_RUST_CORE=1 aegir test -t node --grep '^(?!.*(uses the validated local append path|uses the independent native prepared batch path|uses native shared-log planning for replicated target-none puts|uses commit-only local puts when coordinate persistence is deferred)).*(native conformance guard |operations basic |operations get |operations index |operations search |iterate sort |count approximate |query distribution |returnIndexed |caching |custom index |prefetch )' -- --forbid-pending",
107
109
  "lint": "aegir lint",
package/src/program.ts CHANGED
@@ -74,6 +74,7 @@ import {
74
74
  type CanRead,
75
75
  type CanSearch,
76
76
  DocumentIndex,
77
+ type DocumentIndexWriteSession,
77
78
  type GetOptions,
78
79
  INDEX_CONTEXT_SHAPE,
79
80
  type IndexedContextOnly,
@@ -2025,6 +2026,7 @@ export class Documents<
2025
2026
  */
2026
2027
  private getLocalIndexedContextForChange(
2027
2028
  key: indexerTypes.IdKey,
2029
+ orderedSession?: DocumentIndexWriteSession<I>,
2028
2030
  ): Promise<indexerTypes.IndexedResult<IndexedContextOnly<I>> | undefined> {
2029
2031
  // Not every backing index resolves asynchronously (the native one answers
2030
2032
  // synchronously, and may throw synchronously), so guard both shapes without
@@ -2033,9 +2035,9 @@ export class Documents<
2033
2035
  | indexerTypes.IndexedResult<IndexedContextOnly<I>>
2034
2036
  | undefined;
2035
2037
  try {
2036
- const result = this.getLocalIndexedContext(
2037
- key,
2038
- ) as MaybePromise<LocalIndexedContext>;
2038
+ const result = (orderedSession
2039
+ ? orderedSession.get(key, { shape: INDEX_CONTEXT_SHAPE })
2040
+ : this.getLocalIndexedContext(key)) as MaybePromise<LocalIndexedContext>;
2039
2041
  return (
2040
2042
  isPromiseLike(result)
2041
2043
  ? result.catch((error) => this.recoverClosedIndexContextRead(error))
@@ -5342,6 +5344,24 @@ export class Documents<
5342
5344
  reference?: PutChangeReference<T, I>,
5343
5345
  ): Promise<void> {
5344
5346
  logger.trace("handleChanges called", change);
5347
+ if (
5348
+ reference == null &&
5349
+ change.added.length >= 2 &&
5350
+ !this.isNativeMode() &&
5351
+ this._index.canUseOrderedWriteSession()
5352
+ ) {
5353
+ return this._index.withOrderedWriteSession((orderedSession) =>
5354
+ this.handleChangesInOrder(change, reference, orderedSession),
5355
+ );
5356
+ }
5357
+ return this.handleChangesInOrder(change, reference);
5358
+ }
5359
+
5360
+ private async handleChangesInOrder(
5361
+ change: Change<Operation>,
5362
+ reference?: PutChangeReference<T, I>,
5363
+ orderedSession?: DocumentIndexWriteSession<I>,
5364
+ ): Promise<void> {
5345
5365
  const isAppendOperation =
5346
5366
  change?.added.length === 1 ? !!change.added[0] : false;
5347
5367
  const removedSet = new Map<string, ShallowOrFullEntry<Operation>>();
@@ -5382,6 +5402,7 @@ export class Documents<
5382
5402
  let modified: Set<string | number | bigint> = new Set();
5383
5403
  for (const item of sortedEntries) {
5384
5404
  if (!item) {
5405
+ await orderedSession?.flush();
5385
5406
  continue;
5386
5407
  }
5387
5408
 
@@ -5394,6 +5415,7 @@ export class Documents<
5394
5415
  ? reference.operation
5395
5416
  : await this.getAppendOperation(item);
5396
5417
  if (!payload) {
5418
+ await orderedSession?.flush();
5397
5419
  continue;
5398
5420
  }
5399
5421
 
@@ -5448,6 +5470,9 @@ export class Documents<
5448
5470
 
5449
5471
  // document is already updated with more recent entry
5450
5472
  if (modified.has(key.primitive)) {
5473
+ // Duplicate ids deliberately retain the scalar boundary until
5474
+ // their callback and event semantics are proven independently.
5475
+ await orderedSession?.flush();
5451
5476
  continue;
5452
5477
  }
5453
5478
 
@@ -5459,7 +5484,10 @@ export class Documents<
5459
5484
  ? reference.existing
5460
5485
  : this.isNativeMode()
5461
5486
  ? this.getNativeModeIndexedContext(key) || null
5462
- : (await this.getLocalIndexedContextForChange(key)) || null;
5487
+ : (await this.getLocalIndexedContextForChange(
5488
+ key,
5489
+ orderedSession,
5490
+ )) || null;
5463
5491
  if (!this.strictHistory && existing) {
5464
5492
  // if immutable use oldest, else use newest
5465
5493
  let shouldIgnoreChange = this.immutable
@@ -5468,10 +5496,19 @@ export class Documents<
5468
5496
  : existing.value.__context.modified >
5469
5497
  item.meta.clock.timestamp.wallTime;
5470
5498
  if (shouldIgnoreChange) {
5499
+ await orderedSession?.flush();
5471
5500
  continue;
5472
5501
  }
5473
5502
  }
5474
5503
 
5504
+ const useOrderedWrite =
5505
+ orderedSession != null &&
5506
+ existing == null &&
5507
+ !(value instanceof Program);
5508
+ if (!useOrderedWrite) {
5509
+ await orderedSession?.flush();
5510
+ }
5511
+
5475
5512
  // Program specific
5476
5513
  if (value instanceof Program) {
5477
5514
  // if replicator, then open
@@ -5497,6 +5534,7 @@ export class Documents<
5497
5534
  key,
5498
5535
  item,
5499
5536
  existing,
5537
+ useOrderedWrite ? orderedSession : undefined,
5500
5538
  );
5501
5539
  documentsChanged?.added.push(
5502
5540
  coerceWithIndexed(coerceWithContext(value, context), indexable),
@@ -5508,6 +5546,7 @@ export class Documents<
5508
5546
  isPutOperation(payload) ||
5509
5547
  removedSet.has(item.hash)
5510
5548
  ) {
5549
+ await orderedSession?.flush();
5511
5550
  if (
5512
5551
  !documentsChanged &&
5513
5552
  (await this.collectRemovedPutChangeFromNativeId(payload, modified))
@@ -5525,12 +5564,14 @@ export class Documents<
5525
5564
  }
5526
5565
  } catch (error) {
5527
5566
  if (error instanceof AccessError) {
5567
+ await orderedSession?.flush();
5528
5568
  continue;
5529
5569
  }
5530
5570
  throw error;
5531
5571
  }
5532
5572
  }
5533
5573
 
5574
+ await orderedSession?.flush();
5534
5575
  if (canRemoveByIndexedHead && change.removed.length > 0) {
5535
5576
  const handled = await this.collectRemovedDocumentChangesFromIndexedHeads(
5536
5577
  change.removed,
package/src/search.ts CHANGED
@@ -739,6 +739,9 @@ export type WithContext<I> = {
739
739
  __context: types.Context;
740
740
  } & I;
741
741
 
742
+ export type DocumentIndexWriteSession<I extends Record<string, any>> =
743
+ indexerTypes.OrderedIndexWriteSession<WithContext<I>>;
744
+
742
745
  export const INDEX_CONTEXT_SHAPE = {
743
746
  __context: {
744
747
  created: true,
@@ -1135,6 +1138,23 @@ export class DocumentIndex<
1135
1138
  return this._valueEncoding;
1136
1139
  }
1137
1140
 
1141
+ public canUseOrderedWriteSession(): boolean {
1142
+ return (
1143
+ !this.isProgramValued &&
1144
+ typeof this.index.withOrderedWriteSession === "function"
1145
+ );
1146
+ }
1147
+
1148
+ public async withOrderedWriteSession<R>(
1149
+ operation: (session: DocumentIndexWriteSession<I>) => MaybePromise<R>,
1150
+ ): Promise<R> {
1151
+ const withSession = this.index.withOrderedWriteSession;
1152
+ if (!withSession) {
1153
+ throw new Error("Backing index does not support ordered write sessions");
1154
+ }
1155
+ return withSession.call(this.index, operation) as MaybePromise<R>;
1156
+ }
1157
+
1138
1158
  private ensurePrefetchAccumulator() {
1139
1159
  if (!this._prefetch) {
1140
1160
  this._prefetch = {
@@ -3090,10 +3110,11 @@ export class DocumentIndex<
3090
3110
  | indexerTypes.IndexedResult<IndexedContextOnly<I>>
3091
3111
  | null
3092
3112
  | undefined,
3113
+ orderedSession?: DocumentIndexWriteSession<I>,
3093
3114
  ): Promise<{ context: types.Context; indexable: I }> {
3094
3115
  const existingDefined =
3095
3116
  existing === undefined
3096
- ? await this.index.get(id, {
3117
+ ? await (orderedSession ?? this.index).get(id, {
3097
3118
  shape: INDEX_CONTEXT_SHAPE,
3098
3119
  })
3099
3120
  : existing;
@@ -3106,10 +3127,16 @@ export class DocumentIndex<
3106
3127
  gid: entry.meta.gid,
3107
3128
  size: entry.payload.byteLength,
3108
3129
  });
3109
- return this.putWithContext(value, id, context, {
3110
- replace: existingDefined != null,
3111
- transformFacts: { entryPublicKeys: entry.publicKeys },
3112
- });
3130
+ return this.putWithContext(
3131
+ value,
3132
+ id,
3133
+ context,
3134
+ {
3135
+ replace: existingDefined != null,
3136
+ transformFacts: { entryPublicKeys: entry.publicKeys },
3137
+ },
3138
+ orderedSession,
3139
+ );
3113
3140
  }
3114
3141
 
3115
3142
  public async putWithContext(
@@ -3117,6 +3144,7 @@ export class DocumentIndex<
3117
3144
  id: indexerTypes.IdKey,
3118
3145
  context: types.Context,
3119
3146
  options?: ContextualPutOptions,
3147
+ orderedSession?: DocumentIndexWriteSession<I>,
3120
3148
  ): Promise<{ context: types.Context; indexable: I }> {
3121
3149
  const idString = id.primitive;
3122
3150
  this.cacheResolvedValue(idString, value);
@@ -3129,10 +3157,17 @@ export class DocumentIndex<
3129
3157
  coerceWithContext(value, context);
3130
3158
 
3131
3159
  try {
3132
- const contextualPut = this.transformerIsIdentity
3133
- ? (this.index as ContextualIndexPut<I>).putWithContext
3134
- : undefined;
3135
- if (contextualPut) {
3160
+ const contextualPut =
3161
+ !orderedSession && this.transformerIsIdentity
3162
+ ? (this.index as ContextualIndexPut<I>).putWithContext
3163
+ : undefined;
3164
+ if (orderedSession) {
3165
+ await orderedSession.put(
3166
+ new this.wrappedIndexedType(valueToIndex, context),
3167
+ id,
3168
+ stripEncodedValue(options),
3169
+ );
3170
+ } else if (contextualPut) {
3136
3171
  const encodedValueParts = this.encodeContextualIndexedValueParts(
3137
3172
  options?.encodedValue,
3138
3173
  context,