@peerbit/log 6.1.0 → 6.2.0

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/dist/benchmark/native-graph.js +447 -5
  2. package/dist/benchmark/native-graph.js.map +1 -1
  3. package/dist/src/clock.d.ts +1 -0
  4. package/dist/src/clock.d.ts.map +1 -1
  5. package/dist/src/clock.js +32 -0
  6. package/dist/src/clock.js.map +1 -1
  7. package/dist/src/entry-create.d.ts +1 -0
  8. package/dist/src/entry-create.d.ts.map +1 -1
  9. package/dist/src/entry-create.js.map +1 -1
  10. package/dist/src/entry-index.d.ts +288 -12
  11. package/dist/src/entry-index.d.ts.map +1 -1
  12. package/dist/src/entry-index.js +1478 -33
  13. package/dist/src/entry-index.js.map +1 -1
  14. package/dist/src/entry-v0.d.ts +184 -1
  15. package/dist/src/entry-v0.d.ts.map +1 -1
  16. package/dist/src/entry-v0.js +1021 -12
  17. package/dist/src/entry-v0.js.map +1 -1
  18. package/dist/src/entry.d.ts +73 -1
  19. package/dist/src/entry.d.ts.map +1 -1
  20. package/dist/src/entry.js +124 -1
  21. package/dist/src/entry.js.map +1 -1
  22. package/dist/src/index.d.ts +1 -1
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +1 -1
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/log.d.ts +62 -13
  27. package/dist/src/log.d.ts.map +1 -1
  28. package/dist/src/log.js +2282 -100
  29. package/dist/src/log.js.map +1 -1
  30. package/dist/src/runtime.d.ts +2 -0
  31. package/dist/src/runtime.d.ts.map +1 -0
  32. package/dist/src/runtime.js +10 -0
  33. package/dist/src/runtime.js.map +1 -0
  34. package/dist/src/trim.d.ts +14 -2
  35. package/dist/src/trim.d.ts.map +1 -1
  36. package/dist/src/trim.js +92 -4
  37. package/dist/src/trim.js.map +1 -1
  38. package/package.json +18 -15
  39. package/src/clock.ts +34 -0
  40. package/src/entry-create.ts +1 -0
  41. package/src/entry-index.ts +2232 -68
  42. package/src/entry-v0.ts +1488 -13
  43. package/src/entry.ts +235 -1
  44. package/src/index.ts +7 -1
  45. package/src/log.ts +3246 -206
  46. package/src/runtime.ts +16 -0
  47. package/src/trim.ts +133 -7
package/src/entry-v0.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ deserialize,
2
3
  field,
3
4
  fixedArray,
4
5
  option,
@@ -6,10 +7,11 @@ import {
6
7
  variant,
7
8
  vec,
8
9
  } from "@dao-xyz/borsh";
9
- import { type Blocks } from "@peerbit/blocks-interface";
10
+ import { type Blocks, calculateRawCid } from "@peerbit/blocks-interface";
10
11
  import {
11
12
  AccessError,
12
13
  DecryptedThing,
14
+ Ed25519Keypair,
13
15
  Ed25519PublicKey,
14
16
  type Identity,
15
17
  MaybeEncrypted,
@@ -28,14 +30,499 @@ import { LamportClock as Clock, HLC, Timestamp } from "./clock.js";
28
30
  import { type Encoding, NO_ENCODING } from "./encoding.js";
29
31
  import { ShallowEntry, ShallowMeta } from "./entry-shallow.js";
30
32
  import { EntryType } from "./entry-type.js";
31
- import { type CanAppend, Entry } from "./entry.js";
33
+ import {
34
+ type CanAppend,
35
+ Entry,
36
+ type PreparedAppendChain,
37
+ type PreparedAppendCommitOnlyChain,
38
+ type PreparedNativeLogEntry,
39
+ } from "./entry.js";
32
40
  import type { SortableEntry } from "./log-sorting.js";
33
41
  import { logger as baseLogger } from "./logger.js";
34
42
  import { Payload } from "./payload.js";
43
+ import { canUseOptionalNativeModuleImports } from "./runtime.js";
35
44
  import { equals } from "./utils.js";
36
45
 
37
46
  const log = baseLogger.newScope("entry-v0");
38
47
  const traceLogger = log.trace as typeof log & { enabled?: boolean };
48
+ const RANDOM_GID_BYTES = 32;
49
+ const RANDOM_GID_POOL_SIZE = 64;
50
+ const NATIVE_RAW_CID_BATCH_MIN_BYTES = 1 << 20;
51
+ let randomGidPool: string[] = [];
52
+
53
+ const createRandomGid = (): string => {
54
+ if (randomGidPool.length === 0) {
55
+ const bytes = randomBytes(RANDOM_GID_BYTES * RANDOM_GID_POOL_SIZE);
56
+ randomGidPool = new Array<string>(RANDOM_GID_POOL_SIZE);
57
+ for (let i = 0; i < RANDOM_GID_POOL_SIZE; i++) {
58
+ const offset = i * RANDOM_GID_BYTES;
59
+ randomGidPool[i] = toBase64(
60
+ bytes.subarray(offset, offset + RANDOM_GID_BYTES),
61
+ );
62
+ }
63
+ }
64
+ return randomGidPool.pop()!;
65
+ };
66
+
67
+ type NativePlainChainInput = {
68
+ clockId: Uint8Array;
69
+ privateKey: Uint8Array;
70
+ publicKey: Uint8Array;
71
+ wallTimes: Array<bigint | number | string>;
72
+ logicals?: number[];
73
+ gid: string;
74
+ initialNext?: string[];
75
+ type?: number;
76
+ metaDatas?: Array<Uint8Array | undefined>;
77
+ payloadDatas: Uint8Array[];
78
+ };
79
+
80
+ type NativePlainEntryInput = {
81
+ clockId: Uint8Array;
82
+ privateKey: Uint8Array;
83
+ publicKey: Uint8Array;
84
+ wallTime: bigint | number | string;
85
+ logical?: number;
86
+ gid: string;
87
+ next?: string[];
88
+ type?: number;
89
+ metaData?: Uint8Array;
90
+ payloadData: Uint8Array;
91
+ includeMaterializationBytes?: boolean;
92
+ includeAppendFactsBytes?: boolean;
93
+ trimLengthTo?: number;
94
+ resolveTrimmedEntries?: boolean;
95
+ };
96
+
97
+ type NativePlainEntriesInput = {
98
+ clockId: Uint8Array;
99
+ privateKey: Uint8Array;
100
+ publicKey: Uint8Array;
101
+ wallTimes: Array<bigint | number | string>;
102
+ logicals?: number[];
103
+ gids: string[];
104
+ nexts?: string[][];
105
+ type?: number;
106
+ metaDatas?: Array<Uint8Array | undefined>;
107
+ payloadDatas: Uint8Array[];
108
+ };
109
+
110
+ type NativePreparedPlainEntry = {
111
+ bytes?: Uint8Array;
112
+ cid: string;
113
+ byteLength: number;
114
+ signature?: Uint8Array;
115
+ next: string[];
116
+ metaBytes?: Uint8Array;
117
+ payloadBytes?: Uint8Array;
118
+ signatureBytes?: Uint8Array;
119
+ hashDigestBytes?: Uint8Array;
120
+ trimmedEntries?: PreparedNativeLogEntry[];
121
+ trimmedEntryHashes?: string[];
122
+ };
123
+
124
+ export type PreparedRawEntryV0Facts = {
125
+ cid: string;
126
+ hashDigestBytes: Uint8Array;
127
+ byteLength: number;
128
+ clockId: Uint8Array;
129
+ wallTime: bigint;
130
+ logical: number;
131
+ gid: string;
132
+ next: string[];
133
+ type: EntryType;
134
+ metaBytes: Uint8Array;
135
+ metaData?: Uint8Array;
136
+ payloadByteLength: number;
137
+ signatureVerified: boolean;
138
+ requestedReplicas?: number;
139
+ hashNumber?: string;
140
+ };
141
+
142
+ type MaybePromise<T> = T | Promise<T>;
143
+
144
+ type PlainAppendChainCommitOnlyProperties<T> = {
145
+ data: T[];
146
+ payloadDatas?: Uint8Array[];
147
+ meta?: {
148
+ clocks: () => Clock[];
149
+ gid?: string;
150
+ type?: EntryType;
151
+ gidSeed?: Uint8Array;
152
+ data?: Uint8Array;
153
+ next?: SortableEntry[];
154
+ };
155
+ encoding: Encoding<T>;
156
+ identity: Identity;
157
+ deferStore: boolean;
158
+ nativeGraph?: NativeEntryV0Graph;
159
+ nativeBlockStore?: unknown;
160
+ includeMaterializationBytes?: boolean;
161
+ includeAppendFactsBytes?: boolean;
162
+ nativeTrimLengthTo?: number;
163
+ };
164
+
165
+ type NativeEntryV0Graph = {
166
+ prepareEntryV0PlainChainAndPut?(
167
+ input: NativePlainChainInput,
168
+ ): MaybePromise<NativePreparedPlainEntry[]>;
169
+ prepareEntryV0PlainEntryAndPut?(
170
+ input: NativePlainEntryInput,
171
+ ): MaybePromise<NativePreparedPlainEntry>;
172
+ prepareEntryV0PlainChainCommit?(
173
+ input: NativePlainChainInput,
174
+ blockStore: unknown,
175
+ ): MaybePromise<NativePreparedPlainEntry[] | undefined>;
176
+ prepareEntryV0PlainEntryCommit?(
177
+ input: NativePlainEntryInput,
178
+ blockStore: unknown,
179
+ ): MaybePromise<NativePreparedPlainEntry | undefined>;
180
+ prepareEntryV0PlainEntriesCommit?(
181
+ input: NativePlainEntriesInput,
182
+ blockStore: unknown,
183
+ ): MaybePromise<NativePreparedPlainEntry[] | undefined>;
184
+ };
185
+
186
+ type NativeEntryV0Encoder = {
187
+ encodeEntryV0Signable(input: {
188
+ clockId: Uint8Array;
189
+ wallTime: bigint;
190
+ logical?: number;
191
+ gid: string;
192
+ next?: string[];
193
+ type?: number;
194
+ metaData?: Uint8Array;
195
+ payloadData: Uint8Array;
196
+ }): Promise<Uint8Array>;
197
+ encodeEntryV0Storage(input: {
198
+ clockId: Uint8Array;
199
+ wallTime: bigint;
200
+ logical?: number;
201
+ gid: string;
202
+ next?: string[];
203
+ type?: number;
204
+ metaData?: Uint8Array;
205
+ payloadData: Uint8Array;
206
+ signature: Uint8Array;
207
+ signaturePublicKey: Uint8Array;
208
+ prehash?: number;
209
+ }): Promise<Uint8Array>;
210
+ encodeEntryV0StorageWithCid?(input: {
211
+ clockId: Uint8Array;
212
+ wallTime: bigint;
213
+ logical?: number;
214
+ gid: string;
215
+ next?: string[];
216
+ type?: number;
217
+ metaData?: Uint8Array;
218
+ payloadData: Uint8Array;
219
+ signature: Uint8Array;
220
+ signaturePublicKey: Uint8Array;
221
+ prehash?: number;
222
+ }): Promise<{ bytes: Uint8Array; cid: string }>;
223
+ prepareEntryV0PlainChain?(
224
+ input: NativePlainChainInput,
225
+ ): Promise<NativePreparedPlainEntry[]>;
226
+ prepareEntryV0PlainEntry?(
227
+ input: NativePlainEntryInput,
228
+ ): Promise<NativePreparedPlainEntry>;
229
+ calculateRawCidV1(bytes: Uint8Array): Promise<string>;
230
+ calculateRawCidV1Batch?(blocks: Uint8Array[]): Promise<string[]>;
231
+ entryV0PlainPayloadDataFromStorage?(
232
+ bytes: Uint8Array,
233
+ ): Promise<Uint8Array>;
234
+ prepareRawEntryV0Batch?(
235
+ blocks: Uint8Array[],
236
+ ): Promise<PreparedRawEntryV0Facts[]>;
237
+ verifyEd25519Batch?(
238
+ inputs: Ed25519VerifyBatchInput[],
239
+ ): Promise<boolean[]>;
240
+ verifyEntryV0Ed25519Batch?(
241
+ inputs: EntryV0Ed25519VerifyInput[],
242
+ ): Promise<boolean[]>;
243
+ verifyEntryV0Ed25519StorageBatch?(
244
+ blocks: Uint8Array[],
245
+ ): Promise<boolean[]>;
246
+ };
247
+
248
+ let nativeEntryV0EncoderPromise:
249
+ | Promise<NativeEntryV0Encoder | undefined>
250
+ | undefined;
251
+ let nativeEntryV0EncoderLoaded = false;
252
+ let nativeEntryV0Encoder: NativeEntryV0Encoder | undefined;
253
+
254
+ const isPromiseLike = <T>(value: MaybePromise<T>): value is Promise<T> =>
255
+ !!value && typeof (value as Promise<T>).then === "function";
256
+
257
+ const getSynchronousBlockBytes = (
258
+ store: unknown,
259
+ cid: string,
260
+ ): Uint8Array | undefined => {
261
+ const value = (
262
+ store as { get?: (cid: string) => unknown } | undefined
263
+ )?.get?.(cid);
264
+ return value instanceof Uint8Array ? value : undefined;
265
+ };
266
+
267
+ const nativeEntryV0EncoderFromModule = (mod: {
268
+ encodeEntryV0Signable?: NativeEntryV0Encoder["encodeEntryV0Signable"];
269
+ encodeEntryV0Storage?: NativeEntryV0Encoder["encodeEntryV0Storage"];
270
+ encodeEntryV0StorageWithCid?: NativeEntryV0Encoder["encodeEntryV0StorageWithCid"];
271
+ prepareEntryV0PlainChain?: NativeEntryV0Encoder["prepareEntryV0PlainChain"];
272
+ prepareEntryV0PlainEntry?: NativeEntryV0Encoder["prepareEntryV0PlainEntry"];
273
+ calculateRawCidV1?: NativeEntryV0Encoder["calculateRawCidV1"];
274
+ calculateRawCidV1Batch?: NativeEntryV0Encoder["calculateRawCidV1Batch"];
275
+ entryV0PlainPayloadDataFromStorage?: NativeEntryV0Encoder[
276
+ "entryV0PlainPayloadDataFromStorage"
277
+ ];
278
+ prepareRawEntryV0Batch?: NativeEntryV0Encoder["prepareRawEntryV0Batch"];
279
+ verifyEd25519Batch?: NativeEntryV0Encoder["verifyEd25519Batch"];
280
+ verifyEntryV0Ed25519Batch?: NativeEntryV0Encoder["verifyEntryV0Ed25519Batch"];
281
+ verifyEntryV0Ed25519StorageBatch?: NativeEntryV0Encoder["verifyEntryV0Ed25519StorageBatch"];
282
+ }): NativeEntryV0Encoder | undefined => {
283
+ if (
284
+ !mod.encodeEntryV0Signable ||
285
+ !mod.encodeEntryV0Storage ||
286
+ !mod.calculateRawCidV1
287
+ ) {
288
+ return undefined;
289
+ }
290
+ return {
291
+ encodeEntryV0Signable: mod.encodeEntryV0Signable,
292
+ encodeEntryV0Storage: mod.encodeEntryV0Storage,
293
+ encodeEntryV0StorageWithCid: mod.encodeEntryV0StorageWithCid,
294
+ prepareEntryV0PlainChain: mod.prepareEntryV0PlainChain,
295
+ prepareEntryV0PlainEntry: mod.prepareEntryV0PlainEntry,
296
+ calculateRawCidV1: mod.calculateRawCidV1,
297
+ calculateRawCidV1Batch: mod.calculateRawCidV1Batch,
298
+ entryV0PlainPayloadDataFromStorage:
299
+ mod.entryV0PlainPayloadDataFromStorage,
300
+ prepareRawEntryV0Batch: mod.prepareRawEntryV0Batch,
301
+ verifyEd25519Batch: mod.verifyEd25519Batch,
302
+ verifyEntryV0Ed25519Batch: mod.verifyEntryV0Ed25519Batch,
303
+ verifyEntryV0Ed25519StorageBatch: mod.verifyEntryV0Ed25519StorageBatch,
304
+ };
305
+ };
306
+
307
+ const loadNativeEntryV0Encoder = ():
308
+ | NativeEntryV0Encoder
309
+ | undefined
310
+ | Promise<NativeEntryV0Encoder | undefined> => {
311
+ if (nativeEntryV0EncoderLoaded) {
312
+ return nativeEntryV0Encoder;
313
+ }
314
+ if (!canUseOptionalNativeModuleImports()) {
315
+ nativeEntryV0EncoderLoaded = true;
316
+ return undefined;
317
+ }
318
+ if (!nativeEntryV0EncoderPromise) {
319
+ try {
320
+ nativeEntryV0EncoderPromise = import(
321
+ /* @vite-ignore */ ["@peerbit", "log-rust"].join("/")
322
+ )
323
+ .then((mod) => {
324
+ nativeEntryV0Encoder = nativeEntryV0EncoderFromModule(mod);
325
+ nativeEntryV0EncoderLoaded = true;
326
+ return nativeEntryV0Encoder;
327
+ })
328
+ .catch(() => {
329
+ nativeEntryV0Encoder = undefined;
330
+ nativeEntryV0EncoderLoaded = true;
331
+ return undefined;
332
+ });
333
+ } catch {
334
+ nativeEntryV0Encoder = undefined;
335
+ nativeEntryV0EncoderLoaded = true;
336
+ return undefined;
337
+ }
338
+ }
339
+ return nativeEntryV0EncoderPromise;
340
+ };
341
+
342
+ export const calculateRawCidV1Batch = async (
343
+ blocks: Uint8Array[],
344
+ ): Promise<string[]> => {
345
+ if (blocks.length === 0) {
346
+ return [];
347
+ }
348
+ const totalBytes = blocks.reduce((sum, block) => sum + block.byteLength, 0);
349
+ if (totalBytes < NATIVE_RAW_CID_BATCH_MIN_BYTES) {
350
+ // Small raw-head messages are faster through the JS hasher because they
351
+ // avoid copying every block into wasm.
352
+ return Promise.all(
353
+ blocks.map(async (bytes) => (await calculateRawCid(bytes)).cid),
354
+ );
355
+ }
356
+ const nativeEncoder = loadNativeEntryV0Encoder();
357
+ const resolvedNativeEncoder = isPromiseLike(nativeEncoder)
358
+ ? await nativeEncoder
359
+ : nativeEncoder;
360
+ if (resolvedNativeEncoder?.calculateRawCidV1Batch) {
361
+ return resolvedNativeEncoder.calculateRawCidV1Batch(blocks);
362
+ }
363
+ return Promise.all(
364
+ blocks.map(async (bytes) => (await calculateRawCid(bytes)).cid),
365
+ );
366
+ };
367
+
368
+ export const prepareRawEntryV0Batch = async (
369
+ blocks: Uint8Array[],
370
+ ): Promise<PreparedRawEntryV0Facts[] | undefined> => {
371
+ if (blocks.length === 0) {
372
+ return [];
373
+ }
374
+ const nativeEncoder = loadNativeEntryV0Encoder();
375
+ const resolvedNativeEncoder = isPromiseLike(nativeEncoder)
376
+ ? await nativeEncoder
377
+ : nativeEncoder;
378
+ return resolvedNativeEncoder?.prepareRawEntryV0Batch?.(blocks);
379
+ };
380
+
381
+ export const entryV0PlainPayloadDataFromStorage = async (
382
+ bytes: Uint8Array,
383
+ ): Promise<Uint8Array | undefined> => {
384
+ const nativeEncoder = loadNativeEntryV0Encoder();
385
+ const resolvedNativeEncoder = isPromiseLike(nativeEncoder)
386
+ ? await nativeEncoder
387
+ : nativeEncoder;
388
+ return resolvedNativeEncoder?.entryV0PlainPayloadDataFromStorage?.(bytes);
389
+ };
390
+
391
+ export type Ed25519VerifyBatchInput = {
392
+ signature: Uint8Array;
393
+ publicKey: Uint8Array;
394
+ message: Uint8Array;
395
+ };
396
+
397
+ export type EntryV0Ed25519VerifyInput = {
398
+ clockId: Uint8Array;
399
+ wallTime: bigint;
400
+ logical?: number;
401
+ gid: string;
402
+ next?: string[];
403
+ type?: number;
404
+ metaData?: Uint8Array;
405
+ payloadData: Uint8Array;
406
+ signature: Uint8Array;
407
+ publicKey: Uint8Array;
408
+ };
409
+
410
+ export const verifyEd25519Batch = async (
411
+ inputs: Ed25519VerifyBatchInput[],
412
+ ): Promise<boolean[] | undefined> => {
413
+ if (inputs.length === 0) {
414
+ return [];
415
+ }
416
+ const nativeEncoder = loadNativeEntryV0Encoder();
417
+ const resolvedNativeEncoder = isPromiseLike(nativeEncoder)
418
+ ? await nativeEncoder
419
+ : nativeEncoder;
420
+ return resolvedNativeEncoder?.verifyEd25519Batch?.(inputs);
421
+ };
422
+
423
+ export const verifyEntryV0Ed25519Batch = async (
424
+ inputs: EntryV0Ed25519VerifyInput[],
425
+ ): Promise<boolean[] | undefined> => {
426
+ if (inputs.length === 0) {
427
+ return [];
428
+ }
429
+ const nativeEncoder = loadNativeEntryV0Encoder();
430
+ const resolvedNativeEncoder = isPromiseLike(nativeEncoder)
431
+ ? await nativeEncoder
432
+ : nativeEncoder;
433
+ return resolvedNativeEncoder?.verifyEntryV0Ed25519Batch?.(inputs);
434
+ };
435
+
436
+ export const verifyEntryV0Ed25519StorageBatch = async (
437
+ blocks: Uint8Array[],
438
+ ): Promise<boolean[] | undefined> => {
439
+ if (blocks.length === 0) {
440
+ return [];
441
+ }
442
+ const nativeEncoder = loadNativeEntryV0Encoder();
443
+ const resolvedNativeEncoder = isPromiseLike(nativeEncoder)
444
+ ? await nativeEncoder
445
+ : nativeEncoder;
446
+ return resolvedNativeEncoder?.verifyEntryV0Ed25519StorageBatch?.(blocks);
447
+ };
448
+
449
+ export const verifyEntryV0Ed25519BatchFromEntries = async (
450
+ entries: Entry<any>[],
451
+ ): Promise<boolean[] | undefined> => {
452
+ if (entries.length === 0) {
453
+ return [];
454
+ }
455
+ const preparedStorageBytes: Uint8Array[] = [];
456
+ let allHavePreparedStorageBytes = true;
457
+ for (const entry of entries) {
458
+ const bytes = Entry.getPreparedStorageBytes(entry);
459
+ if (!bytes) {
460
+ allHavePreparedStorageBytes = false;
461
+ break;
462
+ }
463
+ preparedStorageBytes.push(bytes);
464
+ }
465
+ if (allHavePreparedStorageBytes) {
466
+ try {
467
+ const verified =
468
+ await verifyEntryV0Ed25519StorageBatch(preparedStorageBytes);
469
+ if (verified) {
470
+ return verified;
471
+ }
472
+ } catch {
473
+ // Fall through to field extraction for encrypted or non-Ed25519 entries.
474
+ }
475
+ }
476
+ const inputs: EntryV0Ed25519VerifyInput[] = [];
477
+ for (const entry of entries) {
478
+ if (!(entry instanceof EntryV0)) {
479
+ return undefined;
480
+ }
481
+ const rawEntry = entry as EntryV0<any> & {
482
+ _meta: unknown;
483
+ _payload: unknown;
484
+ };
485
+ if (
486
+ !(rawEntry._meta instanceof DecryptedThing) ||
487
+ !(rawEntry._payload instanceof DecryptedThing)
488
+ ) {
489
+ return undefined;
490
+ }
491
+ let signatures: SignatureWithKey[];
492
+ let meta: Meta;
493
+ let payload: Payload<any>;
494
+ try {
495
+ signatures = entry.signatures;
496
+ meta = entry.meta;
497
+ payload = entry.payload;
498
+ } catch {
499
+ return undefined;
500
+ }
501
+ if (signatures.length !== 1) {
502
+ return undefined;
503
+ }
504
+ const signature = signatures[0]!;
505
+ if (
506
+ !(signature.publicKey instanceof Ed25519PublicKey) ||
507
+ signature.prehash !== 0
508
+ ) {
509
+ return undefined;
510
+ }
511
+ inputs.push({
512
+ clockId: meta.clock.id,
513
+ wallTime: meta.clock.timestamp.wallTime,
514
+ logical: meta.clock.timestamp.logical,
515
+ gid: meta.gid,
516
+ next: meta.next,
517
+ type: meta.type,
518
+ metaData: meta.data,
519
+ payloadData: payload.data,
520
+ signature: signature.signature,
521
+ publicKey: signature.publicKey.publicKey,
522
+ });
523
+ }
524
+ return verifyEntryV0Ed25519Batch(inputs);
525
+ };
39
526
 
40
527
  export type MaybeEncryptionPublicKey =
41
528
  | X25519PublicKey
@@ -178,6 +665,7 @@ export class EntryV0<T>
178
665
 
179
666
  private _keychain?: CryptoKeychain;
180
667
  private _encoding?: Encoding<T>;
668
+ private _hashDigestBytes?: Uint8Array;
181
669
 
182
670
  constructor(obj: {
183
671
  payload: MaybeEncrypted<Payload<T>>;
@@ -229,6 +717,18 @@ export class EntryV0<T>
229
717
  return this.meta;
230
718
  }
231
719
 
720
+ getMetaBytes(): Uint8Array | undefined {
721
+ try {
722
+ return this._meta.decrypted._data;
723
+ } catch {
724
+ return undefined;
725
+ }
726
+ }
727
+
728
+ getHashDigestBytes(): Uint8Array | undefined {
729
+ return this._hashDigestBytes;
730
+ }
731
+
232
732
  async getClock(): Promise<Clock> {
233
733
  return (await this.getMeta()).clock;
234
734
  }
@@ -395,7 +895,916 @@ export class EntryV0<T>
395
895
  }
396
896
 
397
897
  static createGid(seed?: Uint8Array): Promise<string> | string {
398
- return seed ? sha256Base64(seed) : toBase64(randomBytes(32));
898
+ return seed ? sha256Base64(seed) : createRandomGid();
899
+ }
900
+
901
+ static createGids(count: number): string[] {
902
+ if (count === 0) {
903
+ return [];
904
+ }
905
+ const bytes = randomBytes(count * RANDOM_GID_BYTES);
906
+ const gids = new Array<string>(count);
907
+ for (let i = 0; i < count; i++) {
908
+ const offset = i * RANDOM_GID_BYTES;
909
+ gids[i] = toBase64(
910
+ bytes.subarray(offset, offset + RANDOM_GID_BYTES),
911
+ );
912
+ }
913
+ return gids;
914
+ }
915
+
916
+ static async createPlainAppendChain<T>(properties: {
917
+ data: T[];
918
+ payloadDatas?: Uint8Array[];
919
+ meta?: {
920
+ clocks: () => Clock[];
921
+ gid?: string;
922
+ type?: EntryType;
923
+ gidSeed?: Uint8Array;
924
+ data?: Uint8Array;
925
+ next?: SortableEntry[];
926
+ };
927
+ encoding: Encoding<T>;
928
+ identity: Identity;
929
+ deferStore: boolean;
930
+ cachePreparedEntries?: boolean;
931
+ nativeGraph?: NativeEntryV0Graph;
932
+ nativeBlockStore?: unknown;
933
+ }): Promise<Entry<T>[] | undefined> {
934
+ return (await EntryV0.createPlainAppendChainBatch(properties))?.entries;
935
+ }
936
+
937
+ static async createPlainAppendEntriesBatch<T>(properties: {
938
+ data: T[];
939
+ payloadDatas?: Uint8Array[];
940
+ meta: {
941
+ clocks: () => Clock[];
942
+ gids: string[];
943
+ nexts?: SortableEntry[][];
944
+ type?: EntryType;
945
+ datas?: Array<Uint8Array | undefined>;
946
+ };
947
+ encoding: Encoding<T>;
948
+ identity: Identity;
949
+ deferStore: boolean;
950
+ cachePreparedEntries?: boolean;
951
+ nativeGraph?: NativeEntryV0Graph;
952
+ nativeBlockStore?: unknown;
953
+ }): Promise<PreparedAppendChain<T> | undefined> {
954
+ if (!properties.deferStore) {
955
+ return undefined;
956
+ }
957
+ if (!(properties.identity instanceof Ed25519Keypair)) {
958
+ return undefined;
959
+ }
960
+ const nativeCommit =
961
+ properties.nativeGraph?.prepareEntryV0PlainEntriesCommit;
962
+ if (!nativeCommit) {
963
+ return undefined;
964
+ }
965
+ if (
966
+ properties.meta.gids.length !== properties.data.length ||
967
+ (properties.meta.nexts &&
968
+ properties.meta.nexts.length !== properties.data.length) ||
969
+ (properties.payloadDatas &&
970
+ properties.payloadDatas.length !== properties.data.length)
971
+ ) {
972
+ throw new Error("Expected one gid and next list per entry");
973
+ }
974
+
975
+ const clocks = properties.meta.clocks();
976
+ if (clocks.length !== properties.data.length) {
977
+ throw new Error("Expected one clock per entry");
978
+ }
979
+ for (let i = 0; i < clocks.length; i++) {
980
+ for (const next of properties.meta.nexts?.[i] ?? []) {
981
+ if (
982
+ Timestamp.compare(next.meta.clock.timestamp, clocks[i]!.timestamp) >=
983
+ 0
984
+ ) {
985
+ throw new Error(
986
+ "Expecting next(s) to happen before entry, got: " +
987
+ next.meta.clock.timestamp +
988
+ " > " +
989
+ clocks[i]!.timestamp,
990
+ );
991
+ }
992
+ }
993
+ }
994
+
995
+ const payloadDatas =
996
+ properties.payloadDatas ??
997
+ properties.data.map((data) => properties.encoding.encoder(data));
998
+ const input: NativePlainEntriesInput = {
999
+ clockId: properties.identity.publicKey.bytes,
1000
+ privateKey: properties.identity.privateKey.privateKey,
1001
+ publicKey: properties.identity.publicKey.publicKey,
1002
+ wallTimes: clocks.map((clock) => clock.timestamp.wallTime),
1003
+ logicals: clocks.map((clock) => clock.timestamp.logical),
1004
+ gids: properties.meta.gids,
1005
+ nexts: properties.meta.nexts?.map((nexts) =>
1006
+ nexts.map((next) => next.hash),
1007
+ ),
1008
+ type: properties.meta.type,
1009
+ metaDatas: properties.meta.datas,
1010
+ payloadDatas,
1011
+ };
1012
+ const preparedValue = nativeCommit.call(
1013
+ properties.nativeGraph,
1014
+ input,
1015
+ properties.nativeBlockStore,
1016
+ );
1017
+ const prepared = isPromiseLike(preparedValue)
1018
+ ? await preparedValue
1019
+ : preparedValue;
1020
+ if (!prepared) {
1021
+ return undefined;
1022
+ }
1023
+ if (prepared.length !== properties.data.length) {
1024
+ throw new Error("Unexpected prepared entry batch length");
1025
+ }
1026
+
1027
+ const entries: PreparedAppendChain<T>["entries"] = [];
1028
+ const shallowEntries: PreparedAppendChain<T>["shallowEntries"] = [];
1029
+ const appendFacts: NonNullable<PreparedAppendChain<T>["appendFacts"]> = [];
1030
+ const nativeEntries: NonNullable<PreparedAppendChain<T>["nativeEntries"]> =
1031
+ [];
1032
+ const entryType = properties.meta.type ?? EntryType.APPEND;
1033
+ for (let index = 0; index < prepared.length; index++) {
1034
+ const preparedEntry = prepared[index]!;
1035
+ const clock = clocks[index]!;
1036
+ const entryGid = properties.meta.gids[index]!;
1037
+ const metaData = properties.meta.datas?.[index];
1038
+ const meta = new Meta({
1039
+ clock,
1040
+ gid: entryGid,
1041
+ type: entryType,
1042
+ data: metaData,
1043
+ next: preparedEntry.next,
1044
+ });
1045
+ const payloadSize = payloadDatas[index]!.byteLength;
1046
+ const payload =
1047
+ properties.cachePreparedEntries === false
1048
+ ? undefined
1049
+ : new Payload<T>({
1050
+ data: payloadDatas[index]!,
1051
+ value: properties.data[index],
1052
+ encoding: properties.encoding,
1053
+ });
1054
+ const signature =
1055
+ properties.cachePreparedEntries === false
1056
+ ? undefined
1057
+ : new SignatureWithKey({
1058
+ signature: preparedEntry.signature!,
1059
+ publicKey: properties.identity.publicKey,
1060
+ prehash: 0,
1061
+ });
1062
+ const entry = new EntryV0<T>({
1063
+ meta: new DecryptedThing({
1064
+ data: preparedEntry.metaBytes,
1065
+ value: meta,
1066
+ }),
1067
+ payload: new DecryptedThing({
1068
+ data: preparedEntry.payloadBytes,
1069
+ value: payload,
1070
+ }),
1071
+ signatures: new Signatures({
1072
+ signatures: [
1073
+ new DecryptedThing({
1074
+ data: preparedEntry.signatureBytes,
1075
+ value: signature,
1076
+ }),
1077
+ ],
1078
+ }),
1079
+ createdLocally: true,
1080
+ });
1081
+ if (properties.cachePreparedEntries === false) {
1082
+ entry.hash = preparedEntry.cid;
1083
+ entry.size = preparedEntry.byteLength;
1084
+ } else {
1085
+ if (!preparedEntry.bytes) {
1086
+ throw new Error("Missing prepared entry bytes");
1087
+ }
1088
+ entry.hash = Entry.prepareMultihashBytes(
1089
+ entry,
1090
+ preparedEntry.bytes,
1091
+ preparedEntry.cid,
1092
+ );
1093
+ }
1094
+ entry._hashDigestBytes = preparedEntry.hashDigestBytes;
1095
+ const shallowEntry = new ShallowEntry({
1096
+ hash: entry.hash,
1097
+ payloadSize,
1098
+ head: true,
1099
+ meta: new ShallowMeta({
1100
+ gid: entryGid,
1101
+ data: metaData,
1102
+ clock,
1103
+ next: preparedEntry.next,
1104
+ type: entryType,
1105
+ }),
1106
+ });
1107
+ const nativeEntry =
1108
+ properties.cachePreparedEntries === false
1109
+ ? undefined
1110
+ : {
1111
+ hash: entry.hash,
1112
+ gid: entryGid,
1113
+ next: preparedEntry.next,
1114
+ type: entryType,
1115
+ head: true,
1116
+ payloadSize,
1117
+ data: metaData,
1118
+ clock: {
1119
+ timestamp: {
1120
+ wallTime: clock.timestamp.wallTime,
1121
+ logical: clock.timestamp.logical,
1122
+ },
1123
+ },
1124
+ };
1125
+ if (properties.cachePreparedEntries !== false) {
1126
+ Entry.prepareShallowEntry(entry, shallowEntry);
1127
+ Entry.prepareNativeLogEntry(entry, nativeEntry!);
1128
+ entry.init({ encoding: properties.encoding });
1129
+ }
1130
+ entries.push(entry);
1131
+ shallowEntries.push(shallowEntry);
1132
+ appendFacts.push({
1133
+ hash: entry.hash,
1134
+ gid: entryGid,
1135
+ next: preparedEntry.next,
1136
+ wallTime: clock.timestamp.wallTime,
1137
+ logical: clock.timestamp.logical,
1138
+ clockId: clock.id,
1139
+ type: entryType,
1140
+ metaData,
1141
+ payloadSize,
1142
+ metaBytes: preparedEntry.metaBytes,
1143
+ hashDigestBytes: preparedEntry.hashDigestBytes,
1144
+ });
1145
+ if (nativeEntry) {
1146
+ nativeEntries.push(nativeEntry);
1147
+ }
1148
+ }
1149
+
1150
+ return {
1151
+ entries,
1152
+ shallowEntries,
1153
+ appendFacts,
1154
+ nativeEntries,
1155
+ nativeGraphUpdated: true,
1156
+ nativeBlocksCommitted: true,
1157
+ };
1158
+ }
1159
+
1160
+ static createPlainAppendChainCommitOnly<T>(
1161
+ properties: PlainAppendChainCommitOnlyProperties<T>,
1162
+ ): MaybePromise<PreparedAppendCommitOnlyChain<T> | undefined> {
1163
+ if (!properties.deferStore || properties.data.length !== 1) {
1164
+ return undefined;
1165
+ }
1166
+ if (!(properties.identity instanceof Ed25519Keypair)) {
1167
+ return undefined;
1168
+ }
1169
+ if (
1170
+ !properties.nativeGraph?.prepareEntryV0PlainEntryCommit &&
1171
+ !properties.nativeGraph?.prepareEntryV0PlainEntryAndPut
1172
+ ) {
1173
+ return undefined;
1174
+ }
1175
+
1176
+ const nexts = properties.meta?.next ?? [];
1177
+ const nextHashes: string[] = [];
1178
+ let gid: string | null = null;
1179
+ if (nexts.length > 0) {
1180
+ if (properties.meta?.gid) {
1181
+ throw new Error(
1182
+ "Expecting '.meta.gid' property to be undefined if '.meta.next' is provided",
1183
+ );
1184
+ }
1185
+ for (const next of nexts) {
1186
+ if (!next.hash) {
1187
+ throw new Error("Expecting hash to be defined to next entries");
1188
+ }
1189
+ nextHashes.push(next.hash);
1190
+ gid =
1191
+ gid == null
1192
+ ? next.meta.gid
1193
+ : next.meta.gid < (gid as string)
1194
+ ? next.meta.gid
1195
+ : gid;
1196
+ }
1197
+ } else if (properties.meta?.gid) {
1198
+ gid = properties.meta.gid;
1199
+ } else {
1200
+ const createdGid = EntryV0.createGid(properties.meta?.gidSeed);
1201
+ return isPromiseLike(createdGid)
1202
+ ? createdGid.then((resolvedGid) =>
1203
+ EntryV0.finishPlainAppendChainCommitOnly(
1204
+ properties,
1205
+ nexts,
1206
+ nextHashes,
1207
+ resolvedGid,
1208
+ ),
1209
+ )
1210
+ : EntryV0.finishPlainAppendChainCommitOnly(
1211
+ properties,
1212
+ nexts,
1213
+ nextHashes,
1214
+ createdGid,
1215
+ );
1216
+ }
1217
+
1218
+ return EntryV0.finishPlainAppendChainCommitOnly(
1219
+ properties,
1220
+ nexts,
1221
+ nextHashes,
1222
+ gid!,
1223
+ );
1224
+ }
1225
+
1226
+ private static finishPlainAppendChainCommitOnly<T>(
1227
+ properties: PlainAppendChainCommitOnlyProperties<T>,
1228
+ nexts: SortableEntry[],
1229
+ nextHashes: string[],
1230
+ gid: string,
1231
+ ): MaybePromise<PreparedAppendCommitOnlyChain<T> | undefined> {
1232
+ if (
1233
+ !properties.nativeGraph ||
1234
+ !(properties.identity instanceof Ed25519Keypair)
1235
+ ) {
1236
+ return undefined;
1237
+ }
1238
+ const nativeGraph = properties.nativeGraph;
1239
+ const identity = properties.identity;
1240
+ const clocks = properties.meta?.clocks();
1241
+ if (!clocks || clocks.length !== 1) {
1242
+ throw new Error("Expected one clock per entry");
1243
+ }
1244
+ const clock = clocks[0]!;
1245
+ for (const next of nexts) {
1246
+ if (Timestamp.compare(next.meta.clock.timestamp, clock.timestamp) >= 0) {
1247
+ throw new Error(
1248
+ "Expecting next(s) to happen before entry, got: " +
1249
+ next.meta.clock.timestamp +
1250
+ " > " +
1251
+ clock.timestamp,
1252
+ );
1253
+ }
1254
+ }
1255
+
1256
+ const payloadData =
1257
+ properties.payloadDatas?.[0] ??
1258
+ properties.encoding.encoder(properties.data[0]!);
1259
+ const entryType = properties.meta?.type ?? EntryType.APPEND;
1260
+ const singleInput: NativePlainEntryInput = {
1261
+ clockId: identity.publicKey.bytes,
1262
+ privateKey: identity.privateKey.privateKey,
1263
+ publicKey: identity.publicKey.publicKey,
1264
+ wallTime: clock.timestamp.wallTime,
1265
+ logical: clock.timestamp.logical,
1266
+ gid: gid!,
1267
+ next: nextHashes,
1268
+ type: entryType,
1269
+ metaData: properties.meta?.data,
1270
+ payloadData,
1271
+ includeMaterializationBytes: properties.includeMaterializationBytes,
1272
+ includeAppendFactsBytes: properties.includeAppendFactsBytes,
1273
+ trimLengthTo: properties.nativeTrimLengthTo,
1274
+ resolveTrimmedEntries:
1275
+ properties.nativeTrimLengthTo == null ? undefined : false,
1276
+ };
1277
+ let nativeBlocksCommitted = false;
1278
+ let nativeGraphUpdated = false;
1279
+ const finishWithPreparedEntry = (
1280
+ preparedEntry: NativePreparedPlainEntry | undefined,
1281
+ ): PreparedAppendCommitOnlyChain<T> | undefined => {
1282
+ if (!preparedEntry) {
1283
+ return undefined;
1284
+ }
1285
+
1286
+ const payloadSize = payloadData.byteLength;
1287
+ const shallowEntry = new ShallowEntry({
1288
+ hash: preparedEntry.cid,
1289
+ payloadSize,
1290
+ head: true,
1291
+ meta: new ShallowMeta({
1292
+ gid: gid!,
1293
+ data: properties.meta?.data,
1294
+ clock,
1295
+ next: preparedEntry.next,
1296
+ type: entryType,
1297
+ }),
1298
+ });
1299
+ const appendFacts = {
1300
+ hash: preparedEntry.cid,
1301
+ gid: gid!,
1302
+ next: preparedEntry.next,
1303
+ wallTime: clock.timestamp.wallTime,
1304
+ logical: clock.timestamp.logical,
1305
+ clockId: clock.id,
1306
+ type: entryType,
1307
+ metaData: properties.meta?.data,
1308
+ payloadSize,
1309
+ metaBytes: preparedEntry.metaBytes,
1310
+ hashDigestBytes: preparedEntry.hashDigestBytes,
1311
+ };
1312
+ let materialized: Entry<T> | undefined;
1313
+ const materializeEntry = (index = 0): Entry<T> => {
1314
+ if (index !== 0) {
1315
+ throw new Error("Prepared commit-only append only has one entry");
1316
+ }
1317
+ if (materialized) {
1318
+ return materialized;
1319
+ }
1320
+ const payload = new Payload<T>({
1321
+ data: payloadData,
1322
+ value: properties.data[0],
1323
+ encoding: properties.encoding,
1324
+ });
1325
+ if (
1326
+ !preparedEntry.metaBytes ||
1327
+ !preparedEntry.payloadBytes ||
1328
+ !preparedEntry.signature ||
1329
+ !preparedEntry.signatureBytes
1330
+ ) {
1331
+ const bytes =
1332
+ preparedEntry.bytes ??
1333
+ getSynchronousBlockBytes(
1334
+ properties.nativeBlockStore,
1335
+ preparedEntry.cid,
1336
+ );
1337
+ if (!bytes) {
1338
+ throw new Error("Missing prepared entry bytes");
1339
+ }
1340
+ const entry = deserialize(bytes, Entry) as EntryV0<T>;
1341
+ entry.hash = preparedEntry.cid;
1342
+ entry.size = preparedEntry.byteLength;
1343
+ entry.createdLocally = true;
1344
+ entry._hashDigestBytes = preparedEntry.hashDigestBytes;
1345
+ Entry.prepareShallowEntry(entry, shallowEntry);
1346
+ entry.init({ encoding: properties.encoding });
1347
+ materialized = entry;
1348
+ return entry;
1349
+ }
1350
+ const signature = new SignatureWithKey({
1351
+ signature: preparedEntry.signature,
1352
+ publicKey: properties.identity.publicKey,
1353
+ prehash: 0,
1354
+ });
1355
+ const meta = new Meta({
1356
+ clock,
1357
+ gid: gid!,
1358
+ type: entryType,
1359
+ data: properties.meta?.data,
1360
+ next: preparedEntry.next,
1361
+ });
1362
+ const entry = new EntryV0<T>({
1363
+ meta: new DecryptedThing({
1364
+ data: preparedEntry.metaBytes,
1365
+ value: meta,
1366
+ }),
1367
+ payload: new DecryptedThing({
1368
+ data: preparedEntry.payloadBytes,
1369
+ value: payload,
1370
+ }),
1371
+ signatures: new Signatures({
1372
+ signatures: [
1373
+ new DecryptedThing({
1374
+ data: preparedEntry.signatureBytes,
1375
+ value: signature,
1376
+ }),
1377
+ ],
1378
+ }),
1379
+ createdLocally: true,
1380
+ });
1381
+ entry.hash = preparedEntry.cid;
1382
+ entry.size = preparedEntry.byteLength;
1383
+ entry._hashDigestBytes = preparedEntry.hashDigestBytes;
1384
+ Entry.prepareShallowEntry(entry, shallowEntry);
1385
+ entry.init({ encoding: properties.encoding });
1386
+ materialized = entry;
1387
+ return entry;
1388
+ };
1389
+ const preparedBlock =
1390
+ preparedEntry.bytes && !nativeBlocksCommitted
1391
+ ? Entry.preparedBlockFromBytes(preparedEntry.bytes, preparedEntry.cid)
1392
+ : undefined;
1393
+
1394
+ return {
1395
+ materializeEntry,
1396
+ materializeEntries: () => [materializeEntry(0)],
1397
+ blocks: preparedBlock ? [preparedBlock] : undefined,
1398
+ shallowEntries: [shallowEntry],
1399
+ appendFacts: [appendFacts],
1400
+ trimmedNativeEntries: preparedEntry.trimmedEntries,
1401
+ trimmedNativeEntryHashes: preparedEntry.trimmedEntryHashes,
1402
+ trimmedNativeBlocksDeleted: nativeBlocksCommitted,
1403
+ nativeGraphUpdated,
1404
+ nativeBlocksCommitted,
1405
+ };
1406
+ };
1407
+ const prepareAndPutFallback = ():
1408
+ | PreparedAppendCommitOnlyChain<T>
1409
+ | undefined
1410
+ | Promise<PreparedAppendCommitOnlyChain<T> | undefined> => {
1411
+ const nativePrepareAndPut = nativeGraph.prepareEntryV0PlainEntryAndPut;
1412
+ const preparedEntryValue = nativePrepareAndPut
1413
+ ? nativePrepareAndPut.call(nativeGraph, {
1414
+ ...singleInput,
1415
+ })
1416
+ : undefined;
1417
+ if (isPromiseLike(preparedEntryValue)) {
1418
+ return preparedEntryValue.then((preparedEntry) => {
1419
+ nativeGraphUpdated = !!preparedEntry && !!nativePrepareAndPut;
1420
+ return finishWithPreparedEntry(preparedEntry);
1421
+ });
1422
+ }
1423
+ nativeGraphUpdated = !!preparedEntryValue && !!nativePrepareAndPut;
1424
+ return finishWithPreparedEntry(preparedEntryValue);
1425
+ };
1426
+ const nativeCommit = nativeGraph.prepareEntryV0PlainEntryCommit;
1427
+ if (nativeCommit) {
1428
+ const preparedEntryValue = nativeCommit.call(
1429
+ nativeGraph,
1430
+ singleInput,
1431
+ properties.nativeBlockStore,
1432
+ );
1433
+ if (isPromiseLike(preparedEntryValue)) {
1434
+ return preparedEntryValue.then((preparedEntry) => {
1435
+ if (preparedEntry) {
1436
+ nativeBlocksCommitted = true;
1437
+ nativeGraphUpdated = true;
1438
+ return finishWithPreparedEntry(preparedEntry);
1439
+ }
1440
+ return prepareAndPutFallback();
1441
+ });
1442
+ }
1443
+ if (preparedEntryValue) {
1444
+ nativeBlocksCommitted = true;
1445
+ nativeGraphUpdated = true;
1446
+ return finishWithPreparedEntry(preparedEntryValue);
1447
+ }
1448
+ }
1449
+ return prepareAndPutFallback();
1450
+ }
1451
+
1452
+ static async createPlainAppendChainBatch<T>(properties: {
1453
+ data: T[];
1454
+ payloadDatas?: Uint8Array[];
1455
+ meta?: {
1456
+ clocks: () => Clock[];
1457
+ gid?: string;
1458
+ type?: EntryType;
1459
+ gidSeed?: Uint8Array;
1460
+ data?: Uint8Array;
1461
+ next?: SortableEntry[];
1462
+ };
1463
+ encoding: Encoding<T>;
1464
+ identity: Identity;
1465
+ deferStore: boolean;
1466
+ cachePreparedEntries?: boolean;
1467
+ nativeGraph?: NativeEntryV0Graph;
1468
+ nativeBlockStore?: unknown;
1469
+ }): Promise<PreparedAppendChain<T> | undefined> {
1470
+ if (!properties.deferStore) {
1471
+ return undefined;
1472
+ }
1473
+ if (!(properties.identity instanceof Ed25519Keypair)) {
1474
+ return undefined;
1475
+ }
1476
+ const nativeEncoderValue = loadNativeEntryV0Encoder();
1477
+ const nativeEncoder = isPromiseLike(nativeEncoderValue)
1478
+ ? await nativeEncoderValue
1479
+ : nativeEncoderValue;
1480
+ if (!nativeEncoder?.prepareEntryV0PlainChain) {
1481
+ return undefined;
1482
+ }
1483
+
1484
+ const nexts = properties.meta?.next ?? [];
1485
+ const nextHashes: string[] = [];
1486
+ let gid: string | null = null;
1487
+ if (nexts.length > 0) {
1488
+ if (properties.meta?.gid) {
1489
+ throw new Error(
1490
+ "Expecting '.meta.gid' property to be undefined if '.meta.next' is provided",
1491
+ );
1492
+ }
1493
+ for (const next of nexts) {
1494
+ if (!next.hash) {
1495
+ throw new Error("Expecting hash to be defined to next entries");
1496
+ }
1497
+ nextHashes.push(next.hash);
1498
+ gid =
1499
+ gid == null
1500
+ ? next.meta.gid
1501
+ : next.meta.gid < (gid as string)
1502
+ ? next.meta.gid
1503
+ : gid;
1504
+ }
1505
+ } else {
1506
+ if (properties.meta?.gid) {
1507
+ gid = properties.meta.gid;
1508
+ } else {
1509
+ const createdGid = EntryV0.createGid(properties.meta?.gidSeed);
1510
+ gid = isPromiseLike(createdGid) ? await createdGid : createdGid;
1511
+ }
1512
+ }
1513
+
1514
+ const clocks = properties.meta?.clocks();
1515
+ if (!clocks || clocks.length !== properties.data.length) {
1516
+ throw new Error("Expected one clock per entry");
1517
+ }
1518
+ for (const next of nexts) {
1519
+ if (
1520
+ Timestamp.compare(next.meta.clock.timestamp, clocks[0]!.timestamp) >= 0
1521
+ ) {
1522
+ throw new Error(
1523
+ "Expecting next(s) to happen before entry, got: " +
1524
+ next.meta.clock.timestamp +
1525
+ " > " +
1526
+ clocks[0]!.timestamp,
1527
+ );
1528
+ }
1529
+ }
1530
+ for (let i = 1; i < clocks.length; i++) {
1531
+ if (
1532
+ Timestamp.compare(clocks[i - 1]!.timestamp, clocks[i]!.timestamp) >= 0
1533
+ ) {
1534
+ throw new Error(
1535
+ "Expecting generated clocks to increase across appendMany",
1536
+ );
1537
+ }
1538
+ }
1539
+
1540
+ const entryType = properties.meta?.type ?? EntryType.APPEND;
1541
+ const clockId = properties.identity.publicKey.bytes;
1542
+ const privateKey = properties.identity.privateKey.privateKey;
1543
+ const publicKey = properties.identity.publicKey.publicKey;
1544
+ let nativeBlocksCommitted = false;
1545
+ let nativeGraphUpdated = false;
1546
+ let prepared: NativePreparedPlainEntry[] | undefined;
1547
+ let payloadDatas: Uint8Array[] | undefined;
1548
+ if (properties.data.length === 1) {
1549
+ const clock = clocks[0]!;
1550
+ const payloadData =
1551
+ properties.payloadDatas?.[0] ??
1552
+ properties.encoding.encoder(properties.data[0]!);
1553
+ const singleInput: NativePlainEntryInput = {
1554
+ clockId,
1555
+ privateKey,
1556
+ publicKey,
1557
+ wallTime: clock.timestamp.wallTime,
1558
+ logical: clock.timestamp.logical,
1559
+ gid: gid!,
1560
+ next: nextHashes,
1561
+ type: entryType,
1562
+ metaData: properties.meta?.data,
1563
+ payloadData,
1564
+ };
1565
+ const nativeCommit =
1566
+ properties.nativeGraph?.prepareEntryV0PlainEntryCommit;
1567
+ const preparedEntryValue = nativeCommit
1568
+ ? nativeCommit.call(
1569
+ properties.nativeGraph,
1570
+ singleInput,
1571
+ properties.nativeBlockStore,
1572
+ )
1573
+ : undefined;
1574
+ const preparedEntry = isPromiseLike(preparedEntryValue)
1575
+ ? await preparedEntryValue
1576
+ : preparedEntryValue;
1577
+ if (preparedEntry) {
1578
+ nativeBlocksCommitted = true;
1579
+ nativeGraphUpdated = true;
1580
+ prepared = [preparedEntry];
1581
+ payloadDatas = [payloadData];
1582
+ } else {
1583
+ const nativePrepareAndPut =
1584
+ properties.nativeGraph?.prepareEntryV0PlainEntryAndPut;
1585
+ const scalarPreparedEntryValue = nativePrepareAndPut
1586
+ ? nativePrepareAndPut.call(properties.nativeGraph, singleInput)
1587
+ : nativeEncoder.prepareEntryV0PlainEntry?.(singleInput);
1588
+ const scalarPreparedEntry = isPromiseLike(scalarPreparedEntryValue)
1589
+ ? await scalarPreparedEntryValue
1590
+ : scalarPreparedEntryValue;
1591
+ if (scalarPreparedEntry) {
1592
+ nativeGraphUpdated = !!nativePrepareAndPut;
1593
+ prepared = [scalarPreparedEntry];
1594
+ payloadDatas = [payloadData];
1595
+ }
1596
+ }
1597
+ }
1598
+
1599
+ if (!prepared) {
1600
+ const wallTimes = new Array<bigint | number | string>(
1601
+ properties.data.length,
1602
+ );
1603
+ const logicals = new Array<number>(properties.data.length);
1604
+ const metaDatas = new Array<Uint8Array | undefined>(
1605
+ properties.data.length,
1606
+ );
1607
+ if (
1608
+ properties.payloadDatas &&
1609
+ properties.payloadDatas.length !== properties.data.length
1610
+ ) {
1611
+ throw new Error("Expected one payload data value per entry");
1612
+ }
1613
+ payloadDatas = properties.payloadDatas
1614
+ ? [...properties.payloadDatas]
1615
+ : new Array<Uint8Array>(properties.data.length);
1616
+ for (let i = 0; i < properties.data.length; i++) {
1617
+ const clock = clocks[i]!;
1618
+ wallTimes[i] = clock.timestamp.wallTime;
1619
+ logicals[i] = clock.timestamp.logical;
1620
+ metaDatas[i] = properties.meta?.data;
1621
+ payloadDatas[i] ??= properties.encoding.encoder(properties.data[i]!);
1622
+ }
1623
+ const nativePlainChainInput: NativePlainChainInput = {
1624
+ clockId,
1625
+ privateKey,
1626
+ publicKey,
1627
+ wallTimes,
1628
+ logicals,
1629
+ gid: gid!,
1630
+ initialNext: nextHashes,
1631
+ type: entryType,
1632
+ metaDatas,
1633
+ payloadDatas,
1634
+ };
1635
+ const nativeCommit =
1636
+ properties.nativeGraph?.prepareEntryV0PlainChainCommit;
1637
+ const preparedValue = nativeCommit
1638
+ ? nativeCommit.call(
1639
+ properties.nativeGraph,
1640
+ nativePlainChainInput,
1641
+ properties.nativeBlockStore,
1642
+ )
1643
+ : undefined;
1644
+ prepared = isPromiseLike(preparedValue)
1645
+ ? await preparedValue
1646
+ : preparedValue;
1647
+ if (prepared) {
1648
+ nativeBlocksCommitted = true;
1649
+ nativeGraphUpdated = true;
1650
+ } else {
1651
+ const nativePrepareAndPut =
1652
+ properties.nativeGraph?.prepareEntryV0PlainChainAndPut;
1653
+ const preparedFallbackValue = nativePrepareAndPut
1654
+ ? nativePrepareAndPut.call(
1655
+ properties.nativeGraph,
1656
+ nativePlainChainInput,
1657
+ )
1658
+ : nativeEncoder.prepareEntryV0PlainChain(nativePlainChainInput);
1659
+ prepared = isPromiseLike(preparedFallbackValue)
1660
+ ? await preparedFallbackValue
1661
+ : preparedFallbackValue;
1662
+ nativeGraphUpdated = !!nativePrepareAndPut;
1663
+ }
1664
+ }
1665
+
1666
+ const entries: PreparedAppendChain<T>["entries"] = [];
1667
+ const blocks: NonNullable<PreparedAppendChain<T>["blocks"]> = [];
1668
+ const shallowEntries: PreparedAppendChain<T>["shallowEntries"] = [];
1669
+ const appendFacts: NonNullable<PreparedAppendChain<T>["appendFacts"]> = [];
1670
+ const nativeEntries: NonNullable<PreparedAppendChain<T>["nativeEntries"]> =
1671
+ [];
1672
+ if (!payloadDatas) {
1673
+ throw new Error("Expected payload data for prepared append chain");
1674
+ }
1675
+
1676
+ for (let index = 0; index < prepared.length; index++) {
1677
+ const preparedEntry = prepared[index]!;
1678
+ const clock = clocks[index]!;
1679
+ const meta = new Meta({
1680
+ clock,
1681
+ gid: gid!,
1682
+ type: entryType,
1683
+ data: properties.meta?.data,
1684
+ next: preparedEntry.next,
1685
+ });
1686
+ const payloadSize = payloadDatas[index]!.byteLength;
1687
+ const payload =
1688
+ properties.cachePreparedEntries === false
1689
+ ? undefined
1690
+ : new Payload<T>({
1691
+ data: payloadDatas[index]!,
1692
+ value: properties.data[index],
1693
+ encoding: properties.encoding,
1694
+ });
1695
+ const signature =
1696
+ properties.cachePreparedEntries === false
1697
+ ? undefined
1698
+ : new SignatureWithKey({
1699
+ signature: preparedEntry.signature!,
1700
+ publicKey: properties.identity.publicKey,
1701
+ prehash: 0,
1702
+ });
1703
+ const entry = new EntryV0<T>({
1704
+ meta: new DecryptedThing({
1705
+ data: preparedEntry.metaBytes,
1706
+ value: meta,
1707
+ }),
1708
+ payload: new DecryptedThing({
1709
+ data: preparedEntry.payloadBytes,
1710
+ value: payload,
1711
+ }),
1712
+ signatures: new Signatures({
1713
+ signatures: [
1714
+ new DecryptedThing({
1715
+ data: preparedEntry.signatureBytes,
1716
+ value: signature,
1717
+ }),
1718
+ ],
1719
+ }),
1720
+ createdLocally: true,
1721
+ });
1722
+ const preparedBlock =
1723
+ preparedEntry.bytes && !nativeBlocksCommitted
1724
+ ? Entry.preparedBlockFromBytes(preparedEntry.bytes, preparedEntry.cid)
1725
+ : undefined;
1726
+ if (properties.cachePreparedEntries === false) {
1727
+ entry.hash = preparedEntry.cid;
1728
+ entry.size = preparedEntry.byteLength;
1729
+ } else {
1730
+ if (!preparedEntry.bytes) {
1731
+ throw new Error("Missing prepared entry bytes");
1732
+ }
1733
+ entry.hash = Entry.prepareMultihashBytes(
1734
+ entry,
1735
+ preparedEntry.bytes,
1736
+ preparedEntry.cid,
1737
+ );
1738
+ }
1739
+ entry._hashDigestBytes = preparedEntry.hashDigestBytes;
1740
+ const shallowEntry = new ShallowEntry({
1741
+ hash: entry.hash,
1742
+ payloadSize,
1743
+ head: index === prepared.length - 1,
1744
+ meta: new ShallowMeta({
1745
+ gid: gid!,
1746
+ data: properties.meta?.data,
1747
+ clock,
1748
+ next: preparedEntry.next,
1749
+ type: entryType,
1750
+ }),
1751
+ });
1752
+ const nativeEntry =
1753
+ nativeGraphUpdated && properties.cachePreparedEntries === false
1754
+ ? undefined
1755
+ : {
1756
+ hash: entry.hash,
1757
+ gid: gid!,
1758
+ next: preparedEntry.next,
1759
+ type: entryType,
1760
+ head: index === prepared.length - 1,
1761
+ payloadSize,
1762
+ data: properties.meta?.data,
1763
+ clock: {
1764
+ timestamp: {
1765
+ wallTime: clock.timestamp.wallTime,
1766
+ logical: clock.timestamp.logical,
1767
+ },
1768
+ },
1769
+ };
1770
+ if (properties.cachePreparedEntries !== false) {
1771
+ Entry.prepareShallowEntry(entry, shallowEntry);
1772
+ Entry.prepareNativeLogEntry(entry, nativeEntry!);
1773
+ }
1774
+ if (properties.cachePreparedEntries !== false) {
1775
+ entry.init({ encoding: properties.encoding });
1776
+ }
1777
+ entries.push(entry);
1778
+ if (preparedBlock) {
1779
+ blocks.push(preparedBlock);
1780
+ }
1781
+ shallowEntries.push(shallowEntry);
1782
+ appendFacts.push({
1783
+ hash: entry.hash,
1784
+ gid: gid!,
1785
+ next: preparedEntry.next,
1786
+ wallTime: clock.timestamp.wallTime,
1787
+ logical: clock.timestamp.logical,
1788
+ clockId: clock.id,
1789
+ type: entryType,
1790
+ metaData: properties.meta?.data,
1791
+ payloadSize,
1792
+ metaBytes: preparedEntry.metaBytes,
1793
+ hashDigestBytes: preparedEntry.hashDigestBytes,
1794
+ });
1795
+ if (nativeEntry) {
1796
+ nativeEntries.push(nativeEntry);
1797
+ }
1798
+ }
1799
+ return {
1800
+ entries,
1801
+ blocks: blocks.length > 0 ? blocks : undefined,
1802
+ shallowEntries,
1803
+ appendFacts,
1804
+ nativeEntries,
1805
+ nativeGraphUpdated,
1806
+ nativeBlocksCommitted,
1807
+ };
399
1808
  }
400
1809
 
401
1810
  static async create<T>(properties: {
@@ -412,6 +1821,7 @@ export class EntryV0<T>
412
1821
  encoding?: Encoding<T>;
413
1822
  canAppend?: CanAppend<T>;
414
1823
  encryption?: EntryEncryption;
1824
+ deferStore?: boolean;
415
1825
  identity: Identity;
416
1826
  signers?: ((
417
1827
  data: Uint8Array,
@@ -505,12 +1915,13 @@ export class EntryV0<T>
505
1915
  properties.meta?.gid ||
506
1916
  (await EntryV0.createGid(properties.meta?.gidSeed));
507
1917
  }
1918
+ const entryType = properties.meta?.type ?? EntryType.APPEND;
508
1919
 
509
1920
  const metadataEncrypted = await maybeEncrypt(
510
1921
  new Meta({
511
1922
  clock,
512
1923
  gid: gid!,
513
- type: properties.meta?.type ?? EntryType.APPEND,
1924
+ type: entryType,
514
1925
  data: properties.meta?.data,
515
1926
  next: nextHashes,
516
1927
  }),
@@ -523,6 +1934,21 @@ export class EntryV0<T>
523
1934
  properties.encryption?.keypair,
524
1935
  properties.encryption?.receiver.payload,
525
1936
  );
1937
+ const nativeEncoder = properties.encryption
1938
+ ? undefined
1939
+ : await loadNativeEntryV0Encoder();
1940
+ const nativePlainInput = nativeEncoder
1941
+ ? {
1942
+ clockId: clock.id,
1943
+ wallTime: clock.timestamp.wallTime,
1944
+ logical: clock.timestamp.logical,
1945
+ gid: gid!,
1946
+ next: nextHashes,
1947
+ type: entryType,
1948
+ metaData: properties.meta?.data,
1949
+ payloadData: payloadToSave.data,
1950
+ }
1951
+ : undefined;
526
1952
 
527
1953
  // Sign id, encrypted payload, clock, nexts, refs
528
1954
  const entry: EntryV0<T> = new EntryV0<T>({
@@ -532,14 +1958,17 @@ export class EntryV0<T>
532
1958
  createdLocally: true,
533
1959
  });
534
1960
 
535
- const signers = properties.signers || [
536
- properties.identity.sign.bind(properties.identity),
537
- ];
538
-
539
- const signableBytes = entry.getSignableBytes();
540
- let signatures = await Promise.all(
541
- signers.map((signer) => signer(signableBytes)),
542
- );
1961
+ const signableBytes =
1962
+ nativeEncoder && nativePlainInput
1963
+ ? await nativeEncoder.encodeEntryV0Signable(nativePlainInput)
1964
+ : entry.getSignableBytes();
1965
+ let signatures = properties.signers
1966
+ ? properties.signers.length === 1
1967
+ ? [await properties.signers[0]!(signableBytes)]
1968
+ : await Promise.all(
1969
+ properties.signers.map((signer) => signer(signableBytes)),
1970
+ )
1971
+ : [await properties.identity.sign(signableBytes)];
543
1972
  signatures = signatures.sort((a, b) => compare(a.signature, b.signature));
544
1973
 
545
1974
  const encryptedSignatures: MaybeEncrypted<SignatureWithKey>[] = [];
@@ -569,8 +1998,54 @@ export class EntryV0<T>
569
1998
  throw new AccessError("Not allowed to append");
570
1999
  }
571
2000
 
2001
+ let nativeStorage:
2002
+ | {
2003
+ bytes: Uint8Array;
2004
+ cid: string;
2005
+ }
2006
+ | undefined;
2007
+ if (
2008
+ nativeEncoder &&
2009
+ nativePlainInput &&
2010
+ !properties.canAppend &&
2011
+ signatures.length === 1 &&
2012
+ signatures[0]!.publicKey instanceof Ed25519PublicKey
2013
+ ) {
2014
+ const storageInput = {
2015
+ ...nativePlainInput,
2016
+ signature: signatures[0]!.signature,
2017
+ signaturePublicKey: signatures[0]!.publicKey.publicKey,
2018
+ prehash: signatures[0]!.prehash,
2019
+ };
2020
+ nativeStorage = nativeEncoder.encodeEntryV0StorageWithCid
2021
+ ? await nativeEncoder.encodeEntryV0StorageWithCid(storageInput)
2022
+ : await (async () => {
2023
+ const storageBytes =
2024
+ await nativeEncoder.encodeEntryV0Storage(storageInput);
2025
+ return {
2026
+ bytes: storageBytes,
2027
+ cid: await nativeEncoder.calculateRawCidV1(storageBytes),
2028
+ };
2029
+ })();
2030
+ }
2031
+
572
2032
  // Append hash
573
- entry.hash = await Entry.toMultihash(properties.store, entry);
2033
+ entry.hash = properties.deferStore
2034
+ ? nativeStorage
2035
+ ? Entry.prepareMultihashBytes(
2036
+ entry,
2037
+ nativeStorage.bytes,
2038
+ nativeStorage.cid,
2039
+ )
2040
+ : await Entry.prepareMultihash(entry)
2041
+ : nativeStorage
2042
+ ? await Entry.toMultihashBytes(
2043
+ properties.store,
2044
+ entry,
2045
+ nativeStorage.bytes,
2046
+ nativeStorage.cid,
2047
+ )
2048
+ : await Entry.toMultihash(properties.store, entry);
574
2049
 
575
2050
  entry.init({ encoding: properties.encoding });
576
2051