@peerbit/document 10.0.4 → 10.1.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 (46) hide show
  1. package/dist/benchmark/index.js +114 -59
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/iterate-replicate-2.js +117 -63
  4. package/dist/benchmark/iterate-replicate-2.js.map +1 -1
  5. package/dist/benchmark/iterate-replicate.js +106 -56
  6. package/dist/benchmark/iterate-replicate.js.map +1 -1
  7. package/dist/benchmark/memory/child.js +114 -59
  8. package/dist/benchmark/memory/child.js.map +1 -1
  9. package/dist/benchmark/replication.js +106 -52
  10. package/dist/benchmark/replication.js.map +1 -1
  11. package/dist/src/domain.d.ts.map +1 -1
  12. package/dist/src/domain.js +1 -3
  13. package/dist/src/domain.js.map +1 -1
  14. package/dist/src/events.d.ts +1 -1
  15. package/dist/src/events.d.ts.map +1 -1
  16. package/dist/src/index.d.ts +1 -1
  17. package/dist/src/index.d.ts.map +1 -1
  18. package/dist/src/index.js.map +1 -1
  19. package/dist/src/most-common-query-predictor.d.ts +3 -3
  20. package/dist/src/most-common-query-predictor.d.ts.map +1 -1
  21. package/dist/src/most-common-query-predictor.js.map +1 -1
  22. package/dist/src/operation.js +175 -81
  23. package/dist/src/operation.js.map +1 -1
  24. package/dist/src/prefetch.d.ts +2 -2
  25. package/dist/src/prefetch.d.ts.map +1 -1
  26. package/dist/src/prefetch.js.map +1 -1
  27. package/dist/src/program.d.ts +2 -2
  28. package/dist/src/program.d.ts.map +1 -1
  29. package/dist/src/program.js +550 -508
  30. package/dist/src/program.js.map +1 -1
  31. package/dist/src/resumable-iterator.d.ts.map +1 -1
  32. package/dist/src/resumable-iterator.js +44 -0
  33. package/dist/src/resumable-iterator.js.map +1 -1
  34. package/dist/src/search.d.ts +14 -10
  35. package/dist/src/search.d.ts.map +1 -1
  36. package/dist/src/search.js +2477 -2120
  37. package/dist/src/search.js.map +1 -1
  38. package/package.json +21 -19
  39. package/src/domain.ts +1 -3
  40. package/src/events.ts +1 -1
  41. package/src/index.ts +1 -0
  42. package/src/most-common-query-predictor.ts +19 -5
  43. package/src/prefetch.ts +12 -3
  44. package/src/program.ts +7 -5
  45. package/src/resumable-iterator.ts +44 -0
  46. package/src/search.ts +564 -196
@@ -1,11 +1,36 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
3
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
4
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
5
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
6
+ var _, done = false;
7
+ for (var i = decorators.length - 1; i >= 0; i--) {
8
+ var context = {};
9
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
10
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
11
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
12
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
13
+ if (kind === "accessor") {
14
+ if (result === void 0) continue;
15
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
16
+ if (_ = accept(result.get)) descriptor.get = _;
17
+ if (_ = accept(result.set)) descriptor.set = _;
18
+ if (_ = accept(result.init)) initializers.unshift(_);
19
+ }
20
+ else if (_ = accept(result)) {
21
+ if (kind === "field") initializers.unshift(_);
22
+ else descriptor[key] = _;
23
+ }
24
+ }
25
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
26
+ done = true;
6
27
  };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
28
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
29
+ var useValue = arguments.length > 2;
30
+ for (var i = 0; i < initializers.length; i++) {
31
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
32
+ }
33
+ return useValue ? value : void 0;
9
34
  };
10
35
  import { BorshError, field, serialize, variant, } from "@dao-xyz/borsh";
11
36
  import { AccessError, SignatureWithKey } from "@peerbit/crypto";
@@ -18,578 +43,595 @@ import { SharedLog, } from "@peerbit/shared-log";
18
43
  import { MAX_BATCH_SIZE } from "./constants.js";
19
44
  import { BORSH_ENCODING_OPERATION, DeleteOperation, PutOperation, PutWithKeyOperation, coerceDeleteOperation, isDeleteOperation, isPutOperation, } from "./operation.js";
20
45
  import { DocumentIndex, coerceWithContext, coerceWithIndexed, } from "./search.js";
21
- const logger = loggerFn({ module: "document" });
46
+ const logger = loggerFn("peerbit:program:document");
47
+ const warn = logger.newScope("warn");
22
48
  export class OperationError extends Error {
23
49
  constructor(message) {
24
50
  super(message);
25
51
  }
26
52
  }
27
- let Documents = class Documents extends Program {
28
- log;
29
- immutable; // "Can I overwrite a document?"
30
- _index;
31
- _clazz;
32
- _optionCanPerform;
33
- idResolver;
34
- domain;
35
- strictHistory;
36
- canOpen;
37
- compatibility;
38
- constructor(properties) {
39
- super();
40
- this.log = new SharedLog(properties);
41
- this.immutable = properties?.immutable ?? false;
42
- this._index = properties?.index || new DocumentIndex();
43
- }
44
- get index() {
45
- return this._index;
46
- }
47
- async maybeSubprogramOpen(value) {
48
- if (await this.canOpen(value)) {
49
- return (await this.node.open(value, {
50
- parent: this,
51
- existing: "reuse",
52
- })); // TODO types
53
+ let Documents = (() => {
54
+ let _classDecorators = [variant("documents")];
55
+ let _classDescriptor;
56
+ let _classExtraInitializers = [];
57
+ let _classThis;
58
+ let _classSuper = Program;
59
+ let _log_decorators;
60
+ let _log_initializers = [];
61
+ let _log_extraInitializers = [];
62
+ let _immutable_decorators;
63
+ let _immutable_initializers = [];
64
+ let _immutable_extraInitializers = [];
65
+ let __index_decorators;
66
+ let __index_initializers = [];
67
+ let __index_extraInitializers = [];
68
+ var Documents = class extends _classSuper {
69
+ static { _classThis = this; }
70
+ static {
71
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
72
+ _log_decorators = [field({ type: SharedLog })];
73
+ _immutable_decorators = [field({ type: "bool" })];
74
+ __index_decorators = [field({ type: DocumentIndex })];
75
+ __esDecorate(null, null, _log_decorators, { kind: "field", name: "log", static: false, private: false, access: { has: obj => "log" in obj, get: obj => obj.log, set: (obj, value) => { obj.log = value; } }, metadata: _metadata }, _log_initializers, _log_extraInitializers);
76
+ __esDecorate(null, null, _immutable_decorators, { kind: "field", name: "immutable", static: false, private: false, access: { has: obj => "immutable" in obj, get: obj => obj.immutable, set: (obj, value) => { obj.immutable = value; } }, metadata: _metadata }, _immutable_initializers, _immutable_extraInitializers);
77
+ __esDecorate(null, null, __index_decorators, { kind: "field", name: "_index", static: false, private: false, access: { has: obj => "_index" in obj, get: obj => obj._index, set: (obj, value) => { obj._index = value; } }, metadata: _metadata }, __index_initializers, __index_extraInitializers);
78
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
79
+ Documents = _classThis = _classDescriptor.value;
80
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
81
+ __runInitializers(_classThis, _classExtraInitializers);
53
82
  }
54
- return value;
55
- }
56
- keepCache = undefined;
57
- async open(options) {
58
- this._clazz = options.type;
59
- this.canOpen = options.canOpen;
60
- /* eslint-disable */
61
- if (Program.isPrototypeOf(this._clazz)) {
62
- if (!this.canOpen) {
63
- throw new Error("Document store needs to be opened with canOpen option when the document type is a Program");
64
- }
83
+ log = __runInitializers(this, _log_initializers, void 0);
84
+ immutable = (__runInitializers(this, _log_extraInitializers), __runInitializers(this, _immutable_initializers, void 0)); // "Can I overwrite a document?"
85
+ _index = (__runInitializers(this, _immutable_extraInitializers), __runInitializers(this, __index_initializers, void 0));
86
+ _clazz = __runInitializers(this, __index_extraInitializers);
87
+ _optionCanPerform;
88
+ idResolver;
89
+ domain;
90
+ strictHistory;
91
+ canOpen;
92
+ compatibility;
93
+ constructor(properties) {
94
+ super();
95
+ this.log = new SharedLog(properties);
96
+ this.immutable = properties?.immutable ?? false;
97
+ this._index = properties?.index || new DocumentIndex();
65
98
  }
66
- this._optionCanPerform = options.canPerform;
67
- const idProperty = options.index?.idProperty ||
68
- indexerTypes.getIdProperty(this._clazz) ||
69
- "id";
70
- const idResolver = options.id ||
71
- (typeof idProperty === "string"
72
- ? (obj) => obj[idProperty]
73
- : (obj) => indexerTypes.extractFieldValue(obj, idProperty));
74
- this.idResolver = idResolver;
75
- this.compatibility = options.compatibility;
76
- this.strictHistory = options.strictHistory ?? false;
77
- await this._index.open({
78
- documentEvents: this.events,
79
- log: this.log,
80
- canRead: options?.index?.canRead,
81
- canSearch: options.index?.canSearch,
82
- documentType: this._clazz,
83
- transform: options.index,
84
- indexBy: idProperty,
85
- compatibility: options.compatibility,
86
- cache: options?.index?.cache,
87
- replicate: async (query, results) => {
88
- // here we arrive for all the results we want to persist.
89
- let mergeSegments = this.domain?.canProjectToOneSegment(query);
90
- await this.log.join(results.results
91
- .flat()
92
- .map((x) => x instanceof ResultIndexedValue && x.entries.length > 0
93
- ? x.entries[0]
94
- : x.context.head), { replicate: { assumeSynced: true, mergeSegments } });
95
- },
96
- dbType: this.constructor,
97
- maybeOpen: this.maybeSubprogramOpen.bind(this),
98
- prefetch: options.index?.prefetch,
99
- includeIndexed: options.index?.includeIndexed,
100
- });
101
- // document v6 and below need log compatibility of v8 or below
102
- // document v7 needs log compatibility of v9
103
- let logCompatiblity = undefined;
104
- if (options.compatibility === 6) {
105
- logCompatiblity = 8;
99
+ get index() {
100
+ return this._index;
106
101
  }
107
- else if (options.compatibility === 7) {
108
- logCompatiblity = 9;
102
+ async maybeSubprogramOpen(value) {
103
+ if (await this.canOpen(value)) {
104
+ return (await this.node.open(value, {
105
+ parent: this,
106
+ existing: "reuse",
107
+ })); // TODO types
108
+ }
109
+ return value;
109
110
  }
110
- this.domain = options.domain?.(this);
111
- let keepFunction;
112
- if (options?.keep === "self") {
113
- this.keepCache = new Set();
114
- keepFunction = async (e) => {
115
- if (this.keepCache?.has(e.hash)) {
116
- return true;
117
- }
118
- let signatures = undefined;
119
- if (e instanceof Entry) {
120
- signatures = e.signatures;
121
- }
122
- else {
123
- const entry = await this.log.log.get(e.hash);
124
- signatures = entry?.signatures;
125
- }
126
- if (!signatures) {
127
- return false;
111
+ keepCache = undefined;
112
+ async open(options) {
113
+ this._clazz = options.type;
114
+ this.canOpen = options.canOpen;
115
+ /* eslint-disable */
116
+ if (Program.isPrototypeOf(this._clazz)) {
117
+ if (!this.canOpen) {
118
+ throw new Error("Document store needs to be opened with canOpen option when the document type is a Program");
128
119
  }
129
- for (const signature of signatures) {
130
- if (signature.publicKey.equals(this.node.identity.publicKey)) {
131
- this.keepCache?.add(e.hash);
120
+ }
121
+ this._optionCanPerform = options.canPerform;
122
+ const idProperty = options.index?.idProperty ||
123
+ indexerTypes.getIdProperty(this._clazz) ||
124
+ "id";
125
+ const idResolver = options.id ||
126
+ (typeof idProperty === "string"
127
+ ? (obj) => obj[idProperty]
128
+ : (obj) => indexerTypes.extractFieldValue(obj, idProperty));
129
+ this.idResolver = idResolver;
130
+ this.compatibility = options.compatibility;
131
+ this.strictHistory = options.strictHistory ?? false;
132
+ await this._index.open({
133
+ documentEvents: this.events,
134
+ log: this.log,
135
+ canRead: options?.index?.canRead,
136
+ canSearch: options.index?.canSearch,
137
+ documentType: this._clazz,
138
+ transform: options.index,
139
+ indexBy: idProperty,
140
+ compatibility: options.compatibility,
141
+ cache: options?.index?.cache,
142
+ replicate: async (query, results) => {
143
+ // here we arrive for all the results we want to persist.
144
+ let mergeSegments = this.domain?.canProjectToOneSegment(query);
145
+ await this.log.join(results.results
146
+ .flat()
147
+ .map((x) => x instanceof ResultIndexedValue && x.entries.length > 0
148
+ ? x.entries[0]
149
+ : x.context.head), { replicate: { assumeSynced: true, mergeSegments } });
150
+ },
151
+ dbType: this.constructor,
152
+ maybeOpen: this.maybeSubprogramOpen.bind(this),
153
+ prefetch: options.index?.prefetch,
154
+ includeIndexed: options.index?.includeIndexed,
155
+ });
156
+ // document v6 and below need log compatibility of v8 or below
157
+ // document v7 needs log compatibility of v9
158
+ let logCompatiblity = undefined;
159
+ if (options.compatibility === 6) {
160
+ logCompatiblity = 8;
161
+ }
162
+ else if (options.compatibility === 7) {
163
+ logCompatiblity = 9;
164
+ }
165
+ this.domain = options.domain?.(this);
166
+ let keepFunction;
167
+ if (options?.keep === "self") {
168
+ this.keepCache = new Set();
169
+ keepFunction = async (e) => {
170
+ if (this.keepCache?.has(e.hash)) {
132
171
  return true;
133
172
  }
134
- }
135
- return false; // TODO also cache this?
136
- };
137
- }
138
- else {
139
- keepFunction = options?.keep;
140
- }
141
- await this.log.open({
142
- encoding: BORSH_ENCODING_OPERATION,
143
- canReplicate: options?.canReplicate,
144
- canAppend: this.canAppend.bind(this),
145
- onChange: this.handleChanges.bind(this),
146
- trim: options?.log?.trim,
147
- replicate: options?.replicate,
148
- replicas: options?.replicas,
149
- domain: (options?.domain
150
- ? (log) => options.domain(this)
151
- : undefined), /// TODO types,
152
- compatibility: logCompatiblity,
153
- keep: keepFunction,
154
- });
155
- }
156
- async recover() {
157
- return this.log.recover();
158
- }
159
- async _resolveEntry(history, options) {
160
- return typeof history === "string"
161
- ? (await this.log.log.get(history, options)) ||
162
- (await Entry.fromMultihash(this.log.log.blocks, history, options))
163
- : history;
164
- }
165
- async canAppend(entry, reference) {
166
- const l0 = await this._canAppend(entry, reference);
167
- if (!l0) {
168
- return false;
169
- }
170
- try {
171
- let operation = l0;
172
- let document = reference?.document;
173
- if (!document) {
174
- if (isPutOperation(l0)) {
175
- document = this._index.valueEncoding.decoder(l0.data);
176
- if (!document) {
173
+ let signatures = undefined;
174
+ if (e instanceof Entry) {
175
+ signatures = e.signatures;
176
+ }
177
+ else {
178
+ const entry = await this.log.log.get(e.hash);
179
+ signatures = entry?.signatures;
180
+ }
181
+ if (!signatures) {
177
182
  return false;
178
183
  }
179
- }
180
- else if (isDeleteOperation(l0)) {
181
- // Nothing to do here by default
182
- // checking if the document exists is not necessary
183
- // since it might already be deleted
184
- }
185
- else {
186
- throw new Error("Unsupported operation");
187
- }
188
- }
189
- if (this._optionCanPerform) {
190
- if (!(await this._optionCanPerform(isPutOperation(operation)
191
- ? {
192
- type: "put",
193
- value: document,
194
- operation,
195
- entry: entry,
184
+ for (const signature of signatures) {
185
+ if (signature.publicKey.equals(this.node.identity.publicKey)) {
186
+ this.keepCache?.add(e.hash);
187
+ return true;
188
+ }
196
189
  }
197
- : {
198
- type: "delete",
199
- operation,
200
- entry: entry,
201
- }))) {
202
- return false;
203
- }
190
+ return false; // TODO also cache this?
191
+ };
204
192
  }
205
- }
206
- catch (error) {
207
- if (error instanceof BorshError) {
208
- logger.warn("Received payload that could not be decoded, skipping");
209
- return false;
193
+ else {
194
+ keepFunction = options?.keep;
210
195
  }
211
- throw error;
196
+ await this.log.open({
197
+ encoding: BORSH_ENCODING_OPERATION,
198
+ canReplicate: options?.canReplicate,
199
+ canAppend: this.canAppend.bind(this),
200
+ onChange: this.handleChanges.bind(this),
201
+ trim: options?.log?.trim,
202
+ replicate: options?.replicate,
203
+ replicas: options?.replicas,
204
+ domain: (options?.domain
205
+ ? (log) => options.domain(this)
206
+ : undefined), /// TODO types,
207
+ compatibility: logCompatiblity,
208
+ keep: keepFunction,
209
+ });
212
210
  }
213
- return true;
214
- }
215
- async _canAppend(entry, reference) {
216
- const resolve = async (history) => {
211
+ async recover() {
212
+ return this.log.recover();
213
+ }
214
+ async _resolveEntry(history, options) {
217
215
  return typeof history === "string"
218
- ? this.log.log.get(history) ||
219
- (await Entry.fromMultihash(this.log.log.blocks, history))
216
+ ? (await this.log.log.get(history, options)) ||
217
+ (await Entry.fromMultihash(this.log.log.blocks, history, options))
220
218
  : history;
221
- };
222
- const pointsToHistory = async (history) => {
223
- // make sure nexts only points to this document at some point in history
224
- let current = await resolve(history);
225
- const next = entry.meta.next[0];
226
- while (current?.hash &&
227
- next !== current?.hash &&
228
- current.meta.next.length > 0) {
229
- current = await this.log.log.get(current.meta.next[0]);
230
- }
231
- if (current?.hash === next) {
232
- return true; // Ok, we are pointing this new edit to some exising point in time of the old document
219
+ }
220
+ async canAppend(entry, reference) {
221
+ const l0 = await this._canAppend(entry, reference);
222
+ if (!l0) {
223
+ return false;
233
224
  }
234
- return false;
235
- };
236
- try {
237
- entry.init({
238
- encoding: this.log.log.encoding,
239
- keychain: this.node.services.keychain,
240
- });
241
- const operation = reference?.operation ||
242
- /* entry._payload instanceof DecryptedThing
243
- ? entry.payload.getValue(entry.encoding)
244
- : */ (await entry.getPayloadValue()); // TODO implement sync api for resolving entries that does not deep decryption
245
- if (isPutOperation(operation)) {
246
- // check nexts
247
- const putOperation = operation;
248
- let value = reference?.document ??
249
- this.index.valueEncoding.decoder(putOperation.data);
250
- const keyValue = this.idResolver(value);
251
- const key = indexerTypes.toId(keyValue);
252
- const existingDocument = (await this.index.getDetailed(key, {
253
- resolve: false,
254
- local: true,
255
- remote: this.immutable ? { strategy: "fallback" } : false,
256
- }))?.[0]?.results[0];
257
- if (existingDocument && existingDocument.context.head !== entry.hash) {
258
- // econd condition can false if we reset the operation log, while not resetting the index. For example when doing .recover
259
- if (this.immutable) {
260
- // key already exist but pick the oldest entry
261
- // this is because we can not overwrite same id if immutable
262
- if (existingDocument.context.created <
263
- entry.meta.clock.timestamp.wallTime) {
225
+ try {
226
+ let operation = l0;
227
+ let document = reference?.document;
228
+ if (!document) {
229
+ if (isPutOperation(l0)) {
230
+ document = this._index.valueEncoding.decoder(l0.data);
231
+ if (!document) {
264
232
  return false;
265
233
  }
266
- if (entry.meta.next.length > 0) {
267
- return false; // can not append to immutable document
268
- }
269
- return putOperation;
234
+ }
235
+ else if (isDeleteOperation(l0)) {
236
+ // Nothing to do here by default
237
+ // checking if the document exists is not necessary
238
+ // since it might already be deleted
270
239
  }
271
240
  else {
272
- if (this.strictHistory) {
273
- // make sure that the next pointer exist and points to the existing documents
274
- if (entry.meta.next.length !== 1) {
241
+ throw new Error("Unsupported operation");
242
+ }
243
+ }
244
+ if (this._optionCanPerform) {
245
+ if (!(await this._optionCanPerform(isPutOperation(operation)
246
+ ? {
247
+ type: "put",
248
+ value: document,
249
+ operation,
250
+ entry: entry,
251
+ }
252
+ : {
253
+ type: "delete",
254
+ operation,
255
+ entry: entry,
256
+ }))) {
257
+ return false;
258
+ }
259
+ }
260
+ }
261
+ catch (error) {
262
+ if (error instanceof BorshError) {
263
+ warn("Received payload that could not be decoded, skipping");
264
+ return false;
265
+ }
266
+ throw error;
267
+ }
268
+ return true;
269
+ }
270
+ async _canAppend(entry, reference) {
271
+ const resolve = async (history) => {
272
+ return typeof history === "string"
273
+ ? this.log.log.get(history) ||
274
+ (await Entry.fromMultihash(this.log.log.blocks, history))
275
+ : history;
276
+ };
277
+ const pointsToHistory = async (history) => {
278
+ // make sure nexts only points to this document at some point in history
279
+ let current = await resolve(history);
280
+ const next = entry.meta.next[0];
281
+ while (current?.hash &&
282
+ next !== current?.hash &&
283
+ current.meta.next.length > 0) {
284
+ current = await this.log.log.get(current.meta.next[0]);
285
+ }
286
+ if (current?.hash === next) {
287
+ return true; // Ok, we are pointing this new edit to some exising point in time of the old document
288
+ }
289
+ return false;
290
+ };
291
+ try {
292
+ entry.init({
293
+ encoding: this.log.log.encoding,
294
+ keychain: this.node.services.keychain,
295
+ });
296
+ const operation = reference?.operation ||
297
+ /* entry._payload instanceof DecryptedThing
298
+ ? entry.payload.getValue(entry.encoding)
299
+ : */ (await entry.getPayloadValue()); // TODO implement sync api for resolving entries that does not deep decryption
300
+ if (isPutOperation(operation)) {
301
+ // check nexts
302
+ const putOperation = operation;
303
+ let value = reference?.document ??
304
+ this.index.valueEncoding.decoder(putOperation.data);
305
+ const keyValue = this.idResolver(value);
306
+ const key = indexerTypes.toId(keyValue);
307
+ const existingDocument = (await this.index.getDetailed(key, {
308
+ resolve: false,
309
+ local: true,
310
+ remote: this.immutable ? { strategy: "fallback" } : false,
311
+ }))?.[0]?.results[0];
312
+ if (existingDocument && existingDocument.context.head !== entry.hash) {
313
+ // econd condition can false if we reset the operation log, while not resetting the index. For example when doing .recover
314
+ if (this.immutable) {
315
+ // key already exist but pick the oldest entry
316
+ // this is because we can not overwrite same id if immutable
317
+ if (existingDocument.context.created <
318
+ entry.meta.clock.timestamp.wallTime) {
275
319
  return false;
276
320
  }
277
- const prevEntry = await this.log.log.entryIndex.get(existingDocument.context.head);
278
- if (!prevEntry) {
279
- logger.error("Failed to find previous entry for document edit: " +
280
- entry.hash);
281
- return false;
321
+ if (entry.meta.next.length > 0) {
322
+ return false; // can not append to immutable document
282
323
  }
283
- const referenceHistoryCorrectly = await pointsToHistory(prevEntry);
284
- return referenceHistoryCorrectly ? putOperation : false;
324
+ return putOperation;
285
325
  }
286
326
  else {
287
- return putOperation;
327
+ if (this.strictHistory) {
328
+ // make sure that the next pointer exist and points to the existing documents
329
+ if (entry.meta.next.length !== 1) {
330
+ return false;
331
+ }
332
+ const prevEntry = await this.log.log.entryIndex.get(existingDocument.context.head);
333
+ if (!prevEntry) {
334
+ logger.error("Failed to find previous entry for document edit: " +
335
+ entry.hash);
336
+ return false;
337
+ }
338
+ const referenceHistoryCorrectly = await pointsToHistory(prevEntry);
339
+ return referenceHistoryCorrectly ? putOperation : false;
340
+ }
341
+ else {
342
+ return putOperation;
343
+ }
288
344
  }
289
345
  }
346
+ else {
347
+ // TODO should re reject next pointers to other documents?
348
+ // like if (entry.meta.next.length > 0) { return false; }
349
+ // for now the default behaviour will allow us to build document dependencies
350
+ }
351
+ }
352
+ else if (isDeleteOperation(operation)) {
353
+ if (entry.meta.next.length !== 1) {
354
+ return false;
355
+ }
356
+ const existingDocument = (await this.index.getDetailed(operation.key, {
357
+ resolve: false,
358
+ local: true,
359
+ remote: this.immutable,
360
+ }))?.[0]?.results[0];
361
+ if (!existingDocument) {
362
+ // already deleted
363
+ return coerceDeleteOperation(operation); // assume ok
364
+ }
365
+ let doc = await this.log.log.get(existingDocument.context.head);
366
+ if (!doc) {
367
+ logger.error("Failed to find Document from head");
368
+ return false;
369
+ }
370
+ if (await pointsToHistory(doc)) {
371
+ // references the existing document
372
+ return coerceDeleteOperation(operation);
373
+ }
374
+ return false;
290
375
  }
291
376
  else {
292
- // TODO should re reject next pointers to other documents?
293
- // like if (entry.meta.next.length > 0) { return false; }
294
- // for now the default behaviour will allow us to build document dependencies
377
+ throw new Error("Unsupported operation");
295
378
  }
379
+ return operation;
296
380
  }
297
- else if (isDeleteOperation(operation)) {
298
- if (entry.meta.next.length !== 1) {
381
+ catch (error) {
382
+ if (error instanceof AccessError) {
383
+ return false; // we cant index because we can not decrypt
384
+ }
385
+ else if (error instanceof BorshError) {
386
+ warn("Received payload that could not be decoded, skipping");
299
387
  return false;
300
388
  }
301
- const existingDocument = (await this.index.getDetailed(operation.key, {
389
+ throw error;
390
+ }
391
+ }
392
+ async put(doc, options) {
393
+ const keyValue = this.idResolver(doc);
394
+ // type check the key
395
+ indexerTypes.checkId(keyValue);
396
+ const ser = serialize(doc);
397
+ if (ser.length > MAX_BATCH_SIZE) {
398
+ throw new Error(`Document is too large (${ser.length * 1e-6}) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`);
399
+ }
400
+ const existingDocument = options?.unique
401
+ ? undefined
402
+ : (await this._index.getDetailed(keyValue, {
302
403
  resolve: false,
303
404
  local: true,
304
- remote: this.immutable,
405
+ remote: options?.checkRemote
406
+ ? { replicate: options?.replicate }
407
+ : false, // only query remote if we know they exist
305
408
  }))?.[0]?.results[0];
306
- if (!existingDocument) {
307
- // already deleted
308
- return coerceDeleteOperation(operation); // assume ok
309
- }
310
- let doc = await this.log.log.get(existingDocument.context.head);
311
- if (!doc) {
312
- logger.error("Failed to find Document from head");
313
- return false;
409
+ let operation;
410
+ if (this.compatibility === 6) {
411
+ if (typeof keyValue === "string") {
412
+ operation = new PutWithKeyOperation({
413
+ key: keyValue,
414
+ data: ser,
415
+ });
314
416
  }
315
- if (await pointsToHistory(doc)) {
316
- // references the existing document
317
- return coerceDeleteOperation(operation);
417
+ else {
418
+ throw new Error("Key must be a string in compatibility mode v6");
318
419
  }
319
- return false;
320
420
  }
321
421
  else {
322
- throw new Error("Unsupported operation");
323
- }
324
- return operation;
325
- }
326
- catch (error) {
327
- if (error instanceof AccessError) {
328
- return false; // we cant index because we can not decrypt
329
- }
330
- else if (error instanceof BorshError) {
331
- logger.warn("Received payload that could not be decoded, skipping");
332
- return false;
422
+ operation = new PutOperation({
423
+ data: ser,
424
+ });
333
425
  }
334
- throw error;
335
- }
336
- }
337
- async put(doc, options) {
338
- const keyValue = this.idResolver(doc);
339
- // type check the key
340
- indexerTypes.checkId(keyValue);
341
- const ser = serialize(doc);
342
- if (ser.length > MAX_BATCH_SIZE) {
343
- throw new Error(`Document is too large (${ser.length * 1e-6}) mb). Needs to be less than ${MAX_BATCH_SIZE * 1e-6} mb`);
426
+ const appended = await this.log.append(operation, {
427
+ ...options,
428
+ meta: {
429
+ next: existingDocument
430
+ ? [await this._resolveEntry(existingDocument.context.head)]
431
+ : [],
432
+ ...options?.meta,
433
+ },
434
+ canAppend: (entry) => {
435
+ return this.canAppend(entry, { document: doc, operation });
436
+ },
437
+ onChange: (change) => {
438
+ return this.handleChanges(change, { document: doc, operation });
439
+ },
440
+ replicate: options?.replicate,
441
+ });
442
+ this.keepCache?.add(appended.entry.hash);
443
+ return appended;
344
444
  }
345
- const existingDocument = options?.unique
346
- ? undefined
347
- : (await this._index.getDetailed(keyValue, {
445
+ async del(id, options) {
446
+ const key = indexerTypes.toId(id);
447
+ const existing = (await this._index.getDetailed(key, {
348
448
  resolve: false,
349
449
  local: true,
350
- remote: options?.checkRemote
351
- ? { replicate: options?.replicate }
352
- : false, // only query remote if we know they exist
450
+ remote: { replicate: options?.replicate },
353
451
  }))?.[0]?.results[0];
354
- let operation;
355
- if (this.compatibility === 6) {
356
- if (typeof keyValue === "string") {
357
- operation = new PutWithKeyOperation({
358
- key: keyValue,
359
- data: ser,
360
- });
361
- }
362
- else {
363
- throw new Error("Key must be a string in compatibility mode v6");
452
+ if (!existing) {
453
+ throw new NotFoundError(`No entry with key '${key.primitive}' in the database`);
364
454
  }
365
- }
366
- else {
367
- operation = new PutOperation({
368
- data: ser,
455
+ this.keepCache?.delete(existing.value.__context.head);
456
+ const entry = await this._resolveEntry(existing.context.head, {
457
+ remote: true,
458
+ });
459
+ return this.log.append(new DeleteOperation({
460
+ key,
461
+ }), {
462
+ ...options,
463
+ meta: {
464
+ next: [entry],
465
+ type: EntryType.CUT,
466
+ ...options?.meta,
467
+ },
369
468
  });
370
469
  }
371
- const appended = await this.log.append(operation, {
372
- ...options,
373
- meta: {
374
- next: existingDocument
375
- ? [await this._resolveEntry(existingDocument.context.head)]
376
- : [],
377
- ...options?.meta,
378
- },
379
- canAppend: (entry) => {
380
- return this.canAppend(entry, { document: doc, operation });
381
- },
382
- onChange: (change) => {
383
- return this.handleChanges(change, { document: doc, operation });
384
- },
385
- replicate: options?.replicate,
386
- });
387
- this.keepCache?.add(appended.entry.hash);
388
- return appended;
389
- }
390
- async del(id, options) {
391
- const key = indexerTypes.toId(id);
392
- const existing = (await this._index.getDetailed(key, {
393
- resolve: false,
394
- local: true,
395
- remote: { replicate: options?.replicate },
396
- }))?.[0]?.results[0];
397
- if (!existing) {
398
- throw new NotFoundError(`No entry with key '${key.primitive}' in the database`);
399
- }
400
- this.keepCache?.delete(existing.value.__context.head);
401
- const entry = await this._resolveEntry(existing.context.head, {
402
- remote: true,
403
- });
404
- return this.log.append(new DeleteOperation({
405
- key,
406
- }), {
407
- ...options,
408
- meta: {
409
- next: [entry],
410
- type: EntryType.CUT,
411
- ...options?.meta,
412
- },
413
- });
414
- }
415
- async handleChanges(change, reference) {
416
- const isAppendOperation = change?.added.length === 1 ? !!change.added[0] : false;
417
- const removedSet = new Map();
418
- for (const r of change.removed) {
419
- removedSet.set(r.hash, r);
420
- }
421
- const sortedEntries = [
422
- ...change.added.map((x) => x.entry),
423
- ...((await Promise.all(change.removed.map((x) => x instanceof Entry ? x : this.log.log.entryIndex.get(x.hash)))) || []),
424
- ]; // TODO assert sorting
425
- /* const sortedEntries = [...change.added, ...(removed || [])]
426
- .sort(this.log.log.sortFn)
427
- .reverse(); // sort so we get newest to oldest */
428
- // There might be a case where change.added and change.removed contains the same document id. Usaully because you use the "trim" option
429
- // in combinatpion with inserting the same document. To mitigate this, we loop through the changes and modify the behaviour for this
430
- let documentsChanged = {
431
- added: [],
432
- removed: [],
433
- };
434
- let modified = new Set();
435
- for (const item of sortedEntries) {
436
- if (!item) {
437
- continue;
470
+ async handleChanges(change, reference) {
471
+ logger.trace("handleChanges called", change);
472
+ const isAppendOperation = change?.added.length === 1 ? !!change.added[0] : false;
473
+ const removedSet = new Map();
474
+ for (const r of change.removed) {
475
+ removedSet.set(r.hash, r);
438
476
  }
439
- try {
440
- const payload =
441
- /* item._payload instanceof DecryptedThing
442
- ? item.payload.getValue(item.encoding)
443
- : */ await item.getPayloadValue(); // TODO implement sync api for resolving entries that does not deep decryption
444
- if (isPutOperation(payload) && !removedSet.has(item.hash)) {
445
- let value = (isAppendOperation &&
446
- reference?.operation === payload &&
447
- reference?.document) ||
448
- this.index.valueEncoding.decoder(payload.data);
449
- // get index key from value
450
- const keyObject = this.idResolver(value);
451
- const key = indexerTypes.toId(keyObject);
452
- // document is already updated with more recent entry
453
- if (modified.has(key.primitive)) {
454
- continue;
455
- }
456
- // if no casual ordering is used, use timestamps to order docs
457
- let existing = reference?.unique
458
- ? null
459
- : (await this._index.index.get(key)) || null;
460
- if (!this.strictHistory && existing) {
461
- // if immutable use oldest, else use newest
462
- let shouldIgnoreChange = this.immutable
463
- ? existing.value.__context.modified <
464
- item.meta.clock.timestamp.wallTime
465
- : existing.value.__context.modified >
466
- item.meta.clock.timestamp.wallTime;
467
- if (shouldIgnoreChange) {
468
- continue;
469
- }
470
- }
471
- // Program specific
472
- if (value instanceof Program) {
473
- // if replicator, then open
474
- value = await this.maybeSubprogramOpen(value);
475
- }
476
- const { context, indexable } = await this._index.put(value, key, item, existing);
477
- documentsChanged.added.push(coerceWithIndexed(coerceWithContext(value, context), indexable));
478
- modified.add(key.primitive);
477
+ const sortedEntries = [
478
+ ...change.added.map((x) => x.entry),
479
+ ...((await Promise.all(change.removed.map((x) => x instanceof Entry ? x : this.log.log.entryIndex.get(x.hash)))) || []),
480
+ ]; // TODO assert sorting
481
+ /* const sortedEntries = [...change.added, ...(removed || [])]
482
+ .sort(this.log.log.sortFn)
483
+ .reverse(); // sort so we get newest to oldest */
484
+ // There might be a case where change.added and change.removed contains the same document id. Usaully because you use the "trim" option
485
+ // in combinatpion with inserting the same document. To mitigate this, we loop through the changes and modify the behaviour for this
486
+ let documentsChanged = {
487
+ added: [],
488
+ removed: [],
489
+ };
490
+ let modified = new Set();
491
+ for (const item of sortedEntries) {
492
+ if (!item) {
493
+ continue;
479
494
  }
480
- else if ((isDeleteOperation(payload) && !removedSet.has(item.hash)) ||
481
- isPutOperation(payload) ||
482
- removedSet.has(item.hash)) {
483
- let value;
484
- let key;
485
- if (isPutOperation(payload)) {
486
- const valueWithoutContext = this.index.valueEncoding.decoder(payload.data);
487
- key = indexerTypes.toId(this.idResolver(valueWithoutContext));
495
+ try {
496
+ const payload =
497
+ /* item._payload instanceof DecryptedThing
498
+ ? item.payload.getValue(item.encoding)
499
+ : */ await item.getPayloadValue(); // TODO implement sync api for resolving entries that does not deep decryption
500
+ if (isPutOperation(payload) && !removedSet.has(item.hash)) {
501
+ let value = (isAppendOperation &&
502
+ reference?.operation === payload &&
503
+ reference?.document) ||
504
+ this.index.valueEncoding.decoder(payload.data);
505
+ // get index key from value
506
+ const keyObject = this.idResolver(value);
507
+ const key = indexerTypes.toId(keyObject);
488
508
  // document is already updated with more recent entry
489
509
  if (modified.has(key.primitive)) {
490
510
  continue;
491
511
  }
492
- // we try to fetch it anyway, because we need the context for the events
493
- const document = await this._index.get(key, {
494
- local: true,
495
- remote: false,
496
- });
497
- if (!document) {
498
- continue;
512
+ // if no casual ordering is used, use timestamps to order docs
513
+ let existing = reference?.unique
514
+ ? null
515
+ : (await this._index.index.get(key)) || null;
516
+ if (!this.strictHistory && existing) {
517
+ // if immutable use oldest, else use newest
518
+ let shouldIgnoreChange = this.immutable
519
+ ? existing.value.__context.modified <
520
+ item.meta.clock.timestamp.wallTime
521
+ : existing.value.__context.modified >
522
+ item.meta.clock.timestamp.wallTime;
523
+ if (shouldIgnoreChange) {
524
+ continue;
525
+ }
499
526
  }
500
- value = document;
527
+ // Program specific
528
+ if (value instanceof Program) {
529
+ // if replicator, then open
530
+ value = await this.maybeSubprogramOpen(value);
531
+ }
532
+ const { context, indexable } = await this._index.put(value, key, item, existing);
533
+ documentsChanged.added.push(coerceWithIndexed(coerceWithContext(value, context), indexable));
534
+ modified.add(key.primitive);
501
535
  }
502
- else if (isDeleteOperation(payload)) {
503
- key = coerceDeleteOperation(payload).key;
504
- // document is already updated with more recent entry
505
- if (modified.has(key.primitive)) {
506
- continue;
536
+ else if ((isDeleteOperation(payload) && !removedSet.has(item.hash)) ||
537
+ isPutOperation(payload) ||
538
+ removedSet.has(item.hash)) {
539
+ let value;
540
+ let key;
541
+ if (isPutOperation(payload)) {
542
+ const valueWithoutContext = this.index.valueEncoding.decoder(payload.data);
543
+ key = indexerTypes.toId(this.idResolver(valueWithoutContext));
544
+ // document is already updated with more recent entry
545
+ if (modified.has(key.primitive)) {
546
+ continue;
547
+ }
548
+ // we try to fetch it anyway, because we need the context for the events
549
+ const document = await this._index.get(key, {
550
+ local: true,
551
+ remote: false,
552
+ });
553
+ if (!document) {
554
+ continue;
555
+ }
556
+ value = document;
507
557
  }
508
- const document = await this._index.get(key, {
509
- local: true,
510
- remote: false,
511
- });
512
- if (!document) {
513
- continue;
558
+ else if (isDeleteOperation(payload)) {
559
+ key = coerceDeleteOperation(payload).key;
560
+ // document is already updated with more recent entry
561
+ if (modified.has(key.primitive)) {
562
+ continue;
563
+ }
564
+ const document = await this._index.get(key, {
565
+ local: true,
566
+ remote: false,
567
+ });
568
+ if (!document) {
569
+ continue;
570
+ }
571
+ value = document;
572
+ }
573
+ else {
574
+ throw new Error("Unexpected");
575
+ }
576
+ documentsChanged.removed.push(value);
577
+ if (value instanceof Program &&
578
+ value.closed !== true &&
579
+ value.parents.includes(this)) {
580
+ await value.drop(this);
514
581
  }
515
- value = document;
582
+ // update index
583
+ await this._index.del(key);
584
+ modified.add(key.primitive);
516
585
  }
517
586
  else {
518
- throw new Error("Unexpected");
587
+ // Unknown operation
588
+ throw new OperationError("Unknown operation");
519
589
  }
520
- documentsChanged.removed.push(value);
521
- if (value instanceof Program &&
522
- value.closed !== true &&
523
- value.parents.includes(this)) {
524
- await value.drop(this);
525
- }
526
- // update index
527
- await this._index.del(key);
528
- modified.add(key.primitive);
529
- }
530
- else {
531
- // Unknown operation
532
- throw new OperationError("Unknown operation");
533
590
  }
534
- }
535
- catch (error) {
536
- if (error instanceof AccessError) {
537
- continue;
591
+ catch (error) {
592
+ if (error instanceof AccessError) {
593
+ continue;
594
+ }
595
+ throw error;
538
596
  }
539
- throw error;
540
597
  }
598
+ this.events.dispatchEvent(new CustomEvent("change", { detail: documentsChanged }));
541
599
  }
542
- this.events.dispatchEvent(new CustomEvent("change", { detail: documentsChanged }));
543
- }
544
- // approximate the amount of documents that exists globally
545
- async count(options) {
546
- let isReplicating = await this.log.isReplicating();
547
- if (!isReplicating) {
548
- // fetch a subset of posts
549
- const iterator = this.index.iterate({ query: options?.query }, {
550
- remote: {
551
- reach: typeof options?.approximate === "object"
552
- ? options?.approximate.scope
553
- : undefined,
554
- },
555
- resolve: false,
600
+ // approximate the amount of documents that exists globally
601
+ async count(options) {
602
+ let isReplicating = await this.log.isReplicating();
603
+ if (!isReplicating) {
604
+ // fetch a subset of posts
605
+ const iterator = this.index.iterate({ query: options?.query }, {
606
+ remote: {
607
+ reach: typeof options?.approximate === "object"
608
+ ? options?.approximate.scope
609
+ : undefined,
610
+ },
611
+ resolve: false,
612
+ });
613
+ const one = await iterator.next(1);
614
+ const left = (await iterator.pending()) ?? 0;
615
+ await iterator.close();
616
+ return one.length + left;
617
+ }
618
+ let totalHeadCount = await this.log.countHeads({ approximate: true }); /* -
619
+ (this.keepCache?.size || 0); TODO adjust for keep fn */
620
+ let totalAssignedHeads = await this.log.countAssignedHeads({
621
+ strict: false,
556
622
  });
557
- const one = await iterator.next(1);
558
- const left = (await iterator.pending()) ?? 0;
559
- await iterator.close();
560
- return one.length + left;
561
- }
562
- let totalHeadCount = await this.log.countHeads({ approximate: true }); /* -
563
- (this.keepCache?.size || 0); TODO adjust for keep fn */
564
- let totalAssignedHeads = await this.log.countAssignedHeads({
565
- strict: false,
566
- });
567
- let indexedDocumentsCount = await this.index.index.count({
568
- query: options?.query,
569
- });
570
- if (totalAssignedHeads == 0) {
571
- return indexedDocumentsCount; // TODO is this really expected?
623
+ let indexedDocumentsCount = await this.index.index.count({
624
+ query: options?.query,
625
+ });
626
+ if (totalAssignedHeads == 0) {
627
+ return indexedDocumentsCount; // TODO is this really expected?
628
+ }
629
+ const nonDeletedDocumentsRatio = indexedDocumentsCount / totalAssignedHeads; // [0, 1]
630
+ let expectedAmountOfDocuments = totalHeadCount * nonDeletedDocumentsRatio; // if total heads count is 100 and 80% is actual documents, then 80 pieces of non-deleted documents should exist
631
+ return Math.round(expectedAmountOfDocuments);
572
632
  }
573
- const nonDeletedDocumentsRatio = indexedDocumentsCount / totalAssignedHeads; // [0, 1]
574
- let expectedAmountOfDocuments = totalHeadCount * nonDeletedDocumentsRatio; // if total heads count is 100 and 80% is actual documents, then 80 pieces of non-deleted documents should exist
575
- return Math.round(expectedAmountOfDocuments);
576
- }
577
- };
578
- __decorate([
579
- field({ type: SharedLog }),
580
- __metadata("design:type", SharedLog)
581
- ], Documents.prototype, "log", void 0);
582
- __decorate([
583
- field({ type: "bool" }),
584
- __metadata("design:type", Boolean)
585
- ], Documents.prototype, "immutable", void 0);
586
- __decorate([
587
- field({ type: DocumentIndex }),
588
- __metadata("design:type", DocumentIndex)
589
- ], Documents.prototype, "_index", void 0);
590
- Documents = __decorate([
591
- variant("documents"),
592
- __metadata("design:paramtypes", [Object])
593
- ], Documents);
633
+ };
634
+ return Documents = _classThis;
635
+ })();
594
636
  export { Documents };
595
637
  //# sourceMappingURL=program.js.map