@peerbit/document 10.0.3 → 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.
- package/dist/benchmark/index.js +114 -59
- package/dist/benchmark/index.js.map +1 -1
- package/dist/benchmark/iterate-replicate-2.js +117 -63
- package/dist/benchmark/iterate-replicate-2.js.map +1 -1
- package/dist/benchmark/iterate-replicate.js +106 -56
- package/dist/benchmark/iterate-replicate.js.map +1 -1
- package/dist/benchmark/memory/child.js +114 -59
- package/dist/benchmark/memory/child.js.map +1 -1
- package/dist/benchmark/replication.js +106 -52
- package/dist/benchmark/replication.js.map +1 -1
- package/dist/src/domain.d.ts.map +1 -1
- package/dist/src/domain.js +1 -3
- package/dist/src/domain.js.map +1 -1
- package/dist/src/events.d.ts +1 -1
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/most-common-query-predictor.d.ts +3 -3
- package/dist/src/most-common-query-predictor.d.ts.map +1 -1
- package/dist/src/most-common-query-predictor.js.map +1 -1
- package/dist/src/operation.js +175 -81
- package/dist/src/operation.js.map +1 -1
- package/dist/src/prefetch.d.ts +2 -2
- package/dist/src/prefetch.d.ts.map +1 -1
- package/dist/src/prefetch.js.map +1 -1
- package/dist/src/program.d.ts +2 -2
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +550 -508
- package/dist/src/program.js.map +1 -1
- package/dist/src/resumable-iterator.d.ts.map +1 -1
- package/dist/src/resumable-iterator.js +44 -0
- package/dist/src/resumable-iterator.js.map +1 -1
- package/dist/src/search.d.ts +14 -10
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +2477 -2119
- package/dist/src/search.js.map +1 -1
- package/package.json +21 -19
- package/src/domain.ts +1 -3
- package/src/events.ts +1 -1
- package/src/index.ts +1 -0
- package/src/most-common-query-predictor.ts +19 -5
- package/src/prefetch.ts +12 -3
- package/src/program.ts +7 -5
- package/src/resumable-iterator.ts +44 -0
- package/src/search.ts +567 -198
package/dist/src/program.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
|
-
var
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
8
|
-
|
|
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(
|
|
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 =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
199
|
-
operation,
|
|
200
|
-
entry: entry,
|
|
201
|
-
}))) {
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
190
|
+
return false; // TODO also cache this?
|
|
191
|
+
};
|
|
204
192
|
}
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
284
|
-
return referenceHistoryCorrectly ? putOperation : false;
|
|
324
|
+
return putOperation;
|
|
285
325
|
}
|
|
286
326
|
else {
|
|
287
|
-
|
|
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
|
-
|
|
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
|
-
|
|
298
|
-
if (
|
|
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
|
-
|
|
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:
|
|
405
|
+
remote: options?.checkRemote
|
|
406
|
+
? { replicate: options?.replicate }
|
|
407
|
+
: false, // only query remote if we know they exist
|
|
305
408
|
}))?.[0]?.results[0];
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
316
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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?.
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if (isPutOperation(payload)) {
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
//
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
582
|
+
// update index
|
|
583
|
+
await this._index.del(key);
|
|
584
|
+
modified.add(key.primitive);
|
|
516
585
|
}
|
|
517
586
|
else {
|
|
518
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|