@peerbit/document 6.0.7 → 7.0.0-3a75d6e

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 (66) 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 +125 -0
  5. package/dist/benchmark/index.js.map +1 -0
  6. package/dist/benchmark/memory/index.d.ts +2 -0
  7. package/dist/benchmark/memory/index.d.ts.map +1 -0
  8. package/dist/benchmark/memory/index.js +122 -0
  9. package/dist/benchmark/memory/index.js.map +1 -0
  10. package/dist/benchmark/memory/insert.d.ts +2 -0
  11. package/dist/benchmark/memory/insert.d.ts.map +1 -0
  12. package/dist/benchmark/memory/insert.js +133 -0
  13. package/dist/benchmark/memory/insert.js.map +1 -0
  14. package/dist/benchmark/memory/utils.d.ts +13 -0
  15. package/dist/benchmark/memory/utils.d.ts.map +1 -0
  16. package/dist/benchmark/memory/utils.js +2 -0
  17. package/dist/benchmark/memory/utils.js.map +1 -0
  18. package/dist/benchmark/replication.d.ts +2 -0
  19. package/dist/benchmark/replication.d.ts.map +1 -0
  20. package/dist/benchmark/replication.js +172 -0
  21. package/dist/benchmark/replication.js.map +1 -0
  22. package/dist/src/borsh.d.ts +2 -0
  23. package/dist/src/borsh.d.ts.map +1 -0
  24. package/dist/src/borsh.js +16 -0
  25. package/dist/src/borsh.js.map +1 -0
  26. package/dist/src/constants.d.ts +2 -0
  27. package/dist/src/constants.d.ts.map +1 -0
  28. package/dist/src/constants.js +2 -0
  29. package/dist/src/constants.js.map +1 -0
  30. package/dist/src/index.d.ts +4 -0
  31. package/dist/src/index.d.ts.map +1 -0
  32. package/dist/src/index.js +4 -0
  33. package/dist/src/index.js.map +1 -0
  34. package/dist/src/program.d.ts +87 -0
  35. package/dist/src/program.d.ts.map +1 -0
  36. package/{lib/esm/document-store.js → dist/src/program.js} +159 -138
  37. package/dist/src/program.js.map +1 -0
  38. package/dist/src/search.d.ts +132 -0
  39. package/dist/src/search.d.ts.map +1 -0
  40. package/dist/src/search.js +845 -0
  41. package/dist/src/search.js.map +1 -0
  42. package/package.json +74 -43
  43. package/src/borsh.ts +19 -0
  44. package/src/constants.ts +1 -0
  45. package/src/index.ts +3 -3
  46. package/src/program.ts +580 -0
  47. package/src/search.ts +1217 -0
  48. package/LICENSE +0 -202
  49. package/lib/esm/document-index.d.ts +0 -147
  50. package/lib/esm/document-index.js +0 -942
  51. package/lib/esm/document-index.js.map +0 -1
  52. package/lib/esm/document-store.d.ts +0 -72
  53. package/lib/esm/document-store.js.map +0 -1
  54. package/lib/esm/index.d.ts +0 -3
  55. package/lib/esm/index.js +0 -4
  56. package/lib/esm/index.js.map +0 -1
  57. package/lib/esm/query.d.ts +0 -191
  58. package/lib/esm/query.js +0 -615
  59. package/lib/esm/query.js.map +0 -1
  60. package/lib/esm/utils.d.ts +0 -3
  61. package/lib/esm/utils.js +0 -12
  62. package/lib/esm/utils.js.map +0 -1
  63. package/src/document-index.ts +0 -1268
  64. package/src/document-store.ts +0 -547
  65. package/src/query.ts +0 -525
  66. package/src/utils.ts +0 -17
@@ -1,547 +0,0 @@
1
- import {
2
- AbstractType,
3
- BorshError,
4
- deserialize,
5
- field,
6
- serialize,
7
- variant
8
- } from "@dao-xyz/borsh";
9
- import { Change, Entry, EntryType, TrimOptions } from "@peerbit/log";
10
- import { Program, ProgramEvents } from "@peerbit/program";
11
- import { AccessError, DecryptedThing } from "@peerbit/crypto";
12
- import { logger as loggerFn } from "@peerbit/logger";
13
- import { AppendOptions } from "@peerbit/log";
14
- import { CustomEvent } from "@libp2p/interface";
15
- import {
16
- RoleOptions,
17
- Observer,
18
- Replicator,
19
- SharedLog,
20
- SharedLogOptions,
21
- SharedAppendOptions
22
- } from "@peerbit/shared-log";
23
-
24
- export type { RoleOptions }; // For convenience (so that consumers does not have to do the import above from shared-log packages)
25
-
26
- import {
27
- IndexableFields,
28
- BORSH_ENCODING_OPERATION,
29
- DeleteOperation,
30
- DocumentIndex,
31
- Operation,
32
- 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 };
41
-
42
- const logger = loggerFn({ module: "document" });
43
-
44
- export class OperationError extends Error {
45
- constructor(message?: string) {
46
- super(message);
47
- }
48
- }
49
- export interface DocumentsChange<T> {
50
- added: T[];
51
- removed: T[];
52
- }
53
- export interface DocumentEvents<T> {
54
- change: CustomEvent<DocumentsChange<T>>;
55
- }
56
-
57
- export type TransactionContext<T> = {
58
- entry: Entry<Operation<T>>;
59
- };
60
-
61
- type MaybePromise = Promise<boolean> | boolean;
62
-
63
- export type CanPerform<T> = (
64
- operation: PutOperation<T> | DeleteOperation,
65
- context: TransactionContext<T>
66
- ) => MaybePromise;
67
-
68
- export type SetupOptions<T> = {
69
- type: AbstractType<T>;
70
- canOpen?: (program: T) => MaybePromise;
71
- canPerform?: CanPerform<T>;
72
- index?: {
73
- key?: string | string[];
74
- fields?: IndexableFields<T>;
75
- canSearch?: CanSearch;
76
- canRead?: CanRead<T>;
77
- };
78
- log?: {
79
- trim?: TrimOptions;
80
- };
81
- } & SharedLogOptions<Operation<T>>;
82
-
83
- @variant("documents")
84
- export class Documents<T extends Record<string, any>>
85
- extends Program<SetupOptions<T>, DocumentEvents<T> & ProgramEvents>
86
- implements InMemoryIndex<T>
87
- {
88
- @field({ type: SharedLog })
89
- log: SharedLog<Operation<T>>;
90
-
91
- @field({ type: "bool" })
92
- immutable: boolean; // "Can I overwrite a document?"
93
-
94
- @field({ type: DocumentIndex })
95
- private _index: DocumentIndex<T>;
96
-
97
- private _clazz: AbstractType<T>;
98
-
99
- private _optionCanPerform?: CanPerform<T>;
100
- private _manuallySynced: Set<string>;
101
-
102
- canOpen?: (
103
- program: T,
104
- entry: Entry<Operation<T>>
105
- ) => Promise<boolean> | boolean;
106
-
107
- constructor(properties?: {
108
- id?: Uint8Array;
109
- immutable?: boolean;
110
- index?: DocumentIndex<T>;
111
- }) {
112
- super();
113
-
114
- this.log = new SharedLog(properties);
115
- this.immutable = properties?.immutable ?? false;
116
- this._index = properties?.index || new DocumentIndex();
117
- }
118
-
119
- get index(): DocumentIndex<T> {
120
- return this._index;
121
- }
122
-
123
- async open(options: SetupOptions<T>) {
124
- this._clazz = options.type;
125
- this.canOpen = options.canOpen;
126
-
127
- /* eslint-disable */
128
- if (Program.isPrototypeOf(this._clazz)) {
129
- if (!this.canOpen) {
130
- throw new Error(
131
- "Document store needs to be opened with canOpen option when the document type is a Program"
132
- );
133
- }
134
- }
135
-
136
- this._optionCanPerform = options.canPerform;
137
-
138
- this._manuallySynced = new Set();
139
-
140
- await this._index.open({
141
- type: this._clazz,
142
- log: this.log,
143
- canRead: options?.index?.canRead,
144
- canSearch: options.index?.canSearch,
145
- fields: options.index?.fields || ((obj) => obj),
146
- indexBy: options.index?.key,
147
- sync: async (result: Results<T>) => {
148
- // here we arrive for all the results we want to persist.
149
- // we we need to do here is
150
- // 1. add the entry to a list of entries that we should persist through prunes
151
- let heads: string[] = [];
152
- for (const entry of result.results) {
153
- this._manuallySynced.add(entry.context.gid);
154
- heads.push(entry.context.head);
155
- }
156
- return this.log.log.join(heads);
157
- },
158
- dbType: this.constructor
159
- });
160
-
161
- await this.log.open({
162
- encoding: BORSH_ENCODING_OPERATION,
163
- canReplicate: options?.canReplicate,
164
- canAppend: this.canAppend.bind(this),
165
- onChange: this.handleChanges.bind(this),
166
- trim: options?.log?.trim,
167
- role: options?.role,
168
- replicas: options?.replicas,
169
- sync: (entry) => {
170
- // here we arrive when ever a insertion/pruning behaviour processes an entry
171
- // returning true means that it should persist
172
- return this._manuallySynced.has(entry.gid);
173
- }
174
- });
175
- }
176
-
177
- async recover() {
178
- return this.log.recover();
179
- }
180
-
181
- private async _resolveEntry(history: Entry<Operation<T>> | string) {
182
- return typeof history === "string"
183
- ? (await this.log.log.get(history)) ||
184
- (await Entry.fromMultihash<Operation<T>>(
185
- this.log.log.blocks,
186
- history
187
- ))
188
- : history;
189
- }
190
-
191
- async updateRole(role: RoleOptions) {
192
- await this.log.updateRole(role);
193
- }
194
- get role(): Replicator | Observer {
195
- return this.log.role;
196
- }
197
-
198
- async canAppend(entry: Entry<Operation<T>>): Promise<boolean> {
199
- const l0 = await this._canAppend(entry);
200
- if (!l0) {
201
- return false;
202
- }
203
-
204
- 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
208
- }
209
-
210
- if (this._optionCanPerform) {
211
- if (
212
- !(await this._optionCanPerform(
213
- payload as PutOperation<T> | DeleteOperation,
214
- {
215
- entry
216
- }
217
- ))
218
- ) {
219
- return false;
220
- }
221
- }
222
- } catch (error) {
223
- if (error instanceof BorshError) {
224
- logger.warn("Received payload that could not be decoded, skipping");
225
- return false;
226
- }
227
- throw error;
228
- }
229
-
230
- return true;
231
- }
232
-
233
- async _canAppend(entry: Entry<Operation<T>>): Promise<boolean> {
234
- const resolve = async (history: Entry<Operation<T>> | string) => {
235
- return typeof history === "string"
236
- ? this.log.log.get(history) ||
237
- (await Entry.fromMultihash(this.log.log.blocks, history))
238
- : history;
239
- };
240
- const pointsToHistory = async (history: Entry<Operation<T>> | string) => {
241
- // make sure nexts only points to this document at some point in history
242
- let current = await resolve(history);
243
-
244
- const next = entry.next[0];
245
- while (
246
- current?.hash &&
247
- next !== current?.hash &&
248
- current.next.length > 0
249
- ) {
250
- current = await this.log.log.get(current.next[0])!;
251
- }
252
- if (current?.hash === next) {
253
- return true; // Ok, we are pointing this new edit to some exising point in time of the old document
254
- }
255
- return false;
256
- };
257
-
258
- try {
259
- entry.init({
260
- encoding: this.log.log.encoding,
261
- keychain: this.node.services.keychain
262
- });
263
- const operation =
264
- entry._payload instanceof DecryptedThing
265
- ? entry.payload.getValue(entry.encoding)
266
- : await entry.getPayloadValue();
267
- if (operation instanceof PutOperation) {
268
- // 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;
274
-
275
- checkKeyable(key);
276
-
277
- const existingDocument = this.index.index.get(asString(key));
278
- if (existingDocument) {
279
- if (this.immutable) {
280
- //Key already exist and this instance Documents can note overrite/edit'
281
- return false;
282
- }
283
-
284
- if (entry.next.length !== 1) {
285
- return false;
286
- }
287
- let doc = await this.log.log.get(existingDocument.context.head);
288
- if (!doc) {
289
- logger.error("Failed to find Document from head");
290
- return false;
291
- }
292
- return pointsToHistory(doc);
293
- } else {
294
- if (entry.next.length !== 0) {
295
- return false;
296
- }
297
- }
298
- } else if (operation instanceof DeleteOperation) {
299
- if (entry.next.length !== 1) {
300
- return false;
301
- }
302
- const existingDocument = this._index.index.get(operation.key);
303
- if (!existingDocument) {
304
- // already deleted
305
- return true; // assume ok
306
- }
307
- let doc = await this.log.log.get(existingDocument.context.head);
308
- if (!doc) {
309
- logger.error("Failed to find Document from head");
310
- return false;
311
- }
312
- return pointsToHistory(doc); // references the existing document
313
- }
314
- } catch (error) {
315
- if (error instanceof AccessError) {
316
- return false; // we cant index because we can not decrypt
317
- } else if (error instanceof BorshError) {
318
- logger.warn("Received payload that could not be decoded, skipping");
319
- return false;
320
- }
321
- throw error;
322
- }
323
- return true;
324
- }
325
-
326
- public async put(
327
- doc: T,
328
- options?: SharedAppendOptions<Operation<T>> & { unique?: boolean }
329
- ) {
330
- const key = this._index.indexByResolver(doc as any as Keyable);
331
- checkKeyable(key);
332
- const ser = serialize(doc);
333
- if (ser.length > MAX_DOCUMENT_SIZE) {
334
- 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`
338
- );
339
- }
340
-
341
- const existingDocument = options?.unique
342
- ? undefined
343
- : (
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
- } //
364
- }
365
- );
366
- }
367
-
368
- async del(key: Keyable, options?: SharedAppendOptions<Operation<T>>) {
369
- const existing = (
370
- await this._index.getDetailed(key, {
371
- local: true,
372
- remote: { sync: true }
373
- })
374
- )?.[0]?.results[0];
375
- if (!existing) {
376
- throw new Error(`No entry with key '${key}' in the database`);
377
- }
378
-
379
- return this.log.append(
380
- new DeleteOperation({
381
- key: asString(key)
382
- }),
383
- {
384
- ...options,
385
- meta: {
386
- next: [await this._resolveEntry(existing.context.head)],
387
- type: EntryType.CUT,
388
- ...options?.meta
389
- }
390
- } //
391
- );
392
- }
393
-
394
- async handleChanges(change: Change<Operation<T>>): Promise<void> {
395
- const removed = [...(change.removed || [])];
396
- const removedSet = new Map<string, Entry<Operation<T>>>();
397
- for (const r of removed) {
398
- removedSet.set(r.hash, r);
399
- }
400
- const entries = [...change.added, ...(removed || [])]
401
- .sort(this.log.log.sortFn)
402
- .reverse(); // sort so we get newest to oldest
403
-
404
- // There might be a case where change.added and change.removed contains the same document id. Usaully because you use the "trim" option
405
- // in combination with inserting the same document. To mitigate this, we loop through the changes and modify the behaviour for this
406
-
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
- let documentsChanged: DocumentsChange<T> = {
432
- added: [],
433
- removed: []
434
- };
435
-
436
- for (const [_key, entries] of visited) {
437
- try {
438
- const item = entries[0];
439
- const payload =
440
- item._payload instanceof DecryptedThing
441
- ? item.payload.getValue(item.encoding)
442
- : await item.getPayloadValue();
443
- if (payload instanceof PutOperation && !removedSet.has(item.hash)) {
444
- const key = payload.key;
445
-
446
- let value = this.deserializeOrPass(payload);
447
-
448
- // Program specific
449
- if (value instanceof Program) {
450
- // if replicator, then open
451
- if (
452
- (await this.canOpen!(value, item)) &&
453
- this.log.role instanceof Replicator &&
454
- (await this.log.replicator(item)) // TODO types, throw runtime error if replicator is not provided
455
- ) {
456
- value = (await this.node.open(value, {
457
- parent: this as Program<any, any>,
458
- existing: "reuse"
459
- })) as any as T; // TODO types
460
- }
461
- }
462
- 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
- });
483
- } else if (
484
- (payload instanceof DeleteOperation && !removedSet.has(item.hash)) ||
485
- payload instanceof PutOperation ||
486
- removedSet.has(item.hash)
487
- ) {
488
- this._manuallySynced.delete(item.gid);
489
-
490
- const key = (payload as DeleteOperation | PutOperation<T>).key;
491
- if (!this.index.index.has(key)) {
492
- continue;
493
- }
494
-
495
- let value: T;
496
- if (payload instanceof PutOperation) {
497
- value = this.deserializeOrPass(payload);
498
- } else if (payload instanceof DeleteOperation) {
499
- value = await this.getDocumentFromEntry(entries[1]!);
500
- } else {
501
- throw new Error("Unexpected");
502
- }
503
-
504
- documentsChanged.removed.push(value);
505
-
506
- if (value instanceof Program) {
507
- await value.drop(this);
508
- }
509
-
510
- // update index
511
- this._index.index.delete(key);
512
- } else {
513
- // Unknown operation
514
- }
515
- } catch (error) {
516
- if (error instanceof AccessError) {
517
- continue;
518
- }
519
- throw error;
520
- }
521
- }
522
-
523
- this.events.dispatchEvent(
524
- new CustomEvent("change", { detail: documentsChanged })
525
- );
526
- }
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
- }