@peerbit/document 15.0.12 → 15.0.14
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/src/callback-detachment.d.ts +14 -0
- package/dist/src/callback-detachment.d.ts.map +1 -0
- package/dist/src/callback-detachment.js +234 -0
- package/dist/src/callback-detachment.js.map +1 -0
- package/dist/src/domain.d.ts.map +1 -1
- package/dist/src/domain.js +5 -3
- package/dist/src/domain.js.map +1 -1
- package/dist/src/program.d.ts +5 -0
- package/dist/src/program.d.ts.map +1 -1
- package/dist/src/program.js +170 -43
- package/dist/src/program.js.map +1 -1
- package/dist/src/search.d.ts +5 -2
- package/dist/src/search.d.ts.map +1 -1
- package/dist/src/search.js +20 -6
- package/dist/src/search.js.map +1 -1
- package/package.json +15 -13
- package/src/callback-detachment.ts +308 -0
- package/src/domain.ts +11 -3
- package/src/program.ts +265 -52
- package/src/search.ts +44 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/document",
|
|
3
|
-
"version": "15.0.
|
|
3
|
+
"version": "15.0.14",
|
|
4
4
|
"description": "Document store implementation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -62,19 +62,19 @@
|
|
|
62
62
|
"uint8arrays": "^5.1.0",
|
|
63
63
|
"@peerbit/cache": "3.1.1",
|
|
64
64
|
"@peerbit/crypto": "3.1.6",
|
|
65
|
-
"@peerbit/
|
|
66
|
-
"@peerbit/indexer-
|
|
67
|
-
"@peerbit/
|
|
68
|
-
"@peerbit/indexer-
|
|
69
|
-
"@peerbit/
|
|
70
|
-
"@peerbit/
|
|
71
|
-
"@peerbit/program": "6.0.53",
|
|
72
|
-
"@peerbit/logger": "2.0.2",
|
|
65
|
+
"@peerbit/indexer-cache": "0.2.15",
|
|
66
|
+
"@peerbit/indexer-simple": "1.2.16",
|
|
67
|
+
"@peerbit/document-interface": "3.2.67",
|
|
68
|
+
"@peerbit/indexer-interface": "3.0.12",
|
|
69
|
+
"@peerbit/indexer-sqlite3": "3.0.18",
|
|
70
|
+
"@peerbit/log": "6.2.24",
|
|
73
71
|
"@peerbit/pubsub": "5.4.2",
|
|
74
|
-
"@peerbit/
|
|
72
|
+
"@peerbit/program": "6.0.55",
|
|
73
|
+
"@peerbit/logger": "2.0.2",
|
|
74
|
+
"@peerbit/rpc": "6.1.23",
|
|
75
75
|
"@peerbit/stream-interface": "6.0.16",
|
|
76
76
|
"@peerbit/time": "3.0.1",
|
|
77
|
-
"@peerbit/shared-log": "16.0.
|
|
77
|
+
"@peerbit/shared-log": "16.0.14"
|
|
78
78
|
},
|
|
79
79
|
"optionalDependencies": {
|
|
80
80
|
"@peerbit/document-rust": "0.1.1"
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
"uuid": "^11.1.1",
|
|
88
88
|
"@peerbit/log-rust": "1.1.4",
|
|
89
89
|
"@peerbit/native-backbone": "0.2.10",
|
|
90
|
-
"
|
|
91
|
-
"peerbit": "
|
|
90
|
+
"peerbit": "5.3.34",
|
|
91
|
+
"@peerbit/test-utils": "3.1.34"
|
|
92
92
|
},
|
|
93
93
|
"repository": {
|
|
94
94
|
"type": "git",
|
|
@@ -102,6 +102,8 @@
|
|
|
102
102
|
"scripts": {
|
|
103
103
|
"clean": "aegir clean",
|
|
104
104
|
"build": "aegir build --no-bundle",
|
|
105
|
+
"bench:ordered-remote-projection": "node --loader ts-node/esm ./benchmark/ordered-remote-projection.ts",
|
|
106
|
+
"bench:shared-fs-cold-join": "node --loader ts-node/esm ./benchmark/shared-fs-cold-join.ts",
|
|
105
107
|
"test": "aegir test --target node",
|
|
106
108
|
"test:document-rust-core": "npm run build && PEERBIT_SHARED_LOG_RUST_CORE=1 aegir test -t node --grep '^(?!.*(uses the validated local append path|uses the independent native prepared batch path|uses native shared-log planning for replicated target-none puts|uses commit-only local puts when coordinate persistence is deferred)).*(native conformance guard |operations basic |operations get |operations index |operations search |iterate sort |count approximate |query distribution |returnIndexed |caching |custom index |prefetch )' -- --forbid-pending",
|
|
107
109
|
"lint": "aegir lint",
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import * as indexerTypes from "@peerbit/indexer-interface";
|
|
2
|
+
import { Entry } from "@peerbit/log";
|
|
3
|
+
import {
|
|
4
|
+
DeleteByStringKeyOperation,
|
|
5
|
+
DeleteOperation,
|
|
6
|
+
type Operation,
|
|
7
|
+
PutOperation,
|
|
8
|
+
PutWithKeyOperation,
|
|
9
|
+
} from "./operation.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Borsh byte fields are input-buffer views. Clone only fields that can expose
|
|
13
|
+
* verified operation bytes while retaining the operation's exact prototype,
|
|
14
|
+
* subclass fields, symbols, and property descriptors.
|
|
15
|
+
*/
|
|
16
|
+
export const detachOperationBytes = <T extends Operation>(operation: T): T => {
|
|
17
|
+
const cloneWithField = <V extends object>(
|
|
18
|
+
value: V,
|
|
19
|
+
key: PropertyKey,
|
|
20
|
+
replacement: unknown,
|
|
21
|
+
): V => {
|
|
22
|
+
const descriptors = Object.getOwnPropertyDescriptors(value) as Record<
|
|
23
|
+
PropertyKey,
|
|
24
|
+
PropertyDescriptor
|
|
25
|
+
>;
|
|
26
|
+
descriptors[key] = {
|
|
27
|
+
...descriptors[key],
|
|
28
|
+
value: replacement,
|
|
29
|
+
};
|
|
30
|
+
const clone = Object.create(Object.getPrototypeOf(value), descriptors) as V;
|
|
31
|
+
if (!Object.isExtensible(value)) {
|
|
32
|
+
Object.preventExtensions(clone);
|
|
33
|
+
}
|
|
34
|
+
return clone;
|
|
35
|
+
};
|
|
36
|
+
if (operation instanceof PutWithKeyOperation) {
|
|
37
|
+
return cloneWithField(
|
|
38
|
+
operation,
|
|
39
|
+
"data",
|
|
40
|
+
new Uint8Array(operation.data),
|
|
41
|
+
) as T;
|
|
42
|
+
}
|
|
43
|
+
if (operation instanceof PutOperation) {
|
|
44
|
+
return cloneWithField(
|
|
45
|
+
operation,
|
|
46
|
+
"data",
|
|
47
|
+
new Uint8Array(operation.data),
|
|
48
|
+
) as T;
|
|
49
|
+
}
|
|
50
|
+
if (operation instanceof DeleteOperation) {
|
|
51
|
+
const key = cloneWithField(
|
|
52
|
+
operation.key,
|
|
53
|
+
"key",
|
|
54
|
+
operation.key instanceof indexerTypes.Uint8ArrayKey
|
|
55
|
+
? new Uint8Array(operation.key.key)
|
|
56
|
+
: operation.key.key,
|
|
57
|
+
);
|
|
58
|
+
return cloneWithField(operation, "key", key) as T;
|
|
59
|
+
}
|
|
60
|
+
if (operation instanceof DeleteByStringKeyOperation) {
|
|
61
|
+
return cloneWithField(operation, "key", operation.key) as T;
|
|
62
|
+
}
|
|
63
|
+
return operation;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type LazyCallbackProperty = "operation" | "entry";
|
|
67
|
+
type LazyCallbackState = {
|
|
68
|
+
operation?: Operation;
|
|
69
|
+
entry?: Entry<any>;
|
|
70
|
+
operationPending: boolean;
|
|
71
|
+
entryPending: boolean;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const lazyCallbackStates = new WeakMap<object, LazyCallbackState>();
|
|
75
|
+
|
|
76
|
+
const isLazyCallbackProperty = (
|
|
77
|
+
property: PropertyKey,
|
|
78
|
+
): property is LazyCallbackProperty =>
|
|
79
|
+
property === "operation" || property === "entry";
|
|
80
|
+
|
|
81
|
+
const settleCallbackProperty = (
|
|
82
|
+
target: object,
|
|
83
|
+
state: LazyCallbackState,
|
|
84
|
+
property: LazyCallbackProperty,
|
|
85
|
+
): void => {
|
|
86
|
+
state[`${property}Pending`] = false;
|
|
87
|
+
state[property] = undefined;
|
|
88
|
+
if (!state.operationPending && !state.entryPending) {
|
|
89
|
+
lazyCallbackStates.delete(target);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const materializeCallbackProperty = (
|
|
94
|
+
target: object,
|
|
95
|
+
property: LazyCallbackProperty,
|
|
96
|
+
): void => {
|
|
97
|
+
const state = lazyCallbackStates.get(target);
|
|
98
|
+
if (!state || !state[`${property}Pending`]) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const value =
|
|
102
|
+
property === "operation"
|
|
103
|
+
? detachOperationBytes(state.operation!)
|
|
104
|
+
: detachEntryPayloadForCallback(state.entry!);
|
|
105
|
+
Reflect.set(target, property, value, target);
|
|
106
|
+
settleCallbackProperty(target, state, property);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const lazyCallbackHandler: ProxyHandler<any> = {
|
|
110
|
+
get(target, property, receiver) {
|
|
111
|
+
if (isLazyCallbackProperty(property)) {
|
|
112
|
+
materializeCallbackProperty(target, property);
|
|
113
|
+
}
|
|
114
|
+
return Reflect.get(target, property, receiver);
|
|
115
|
+
},
|
|
116
|
+
set(target, property, value) {
|
|
117
|
+
const updated = Reflect.set(target, property, value, target);
|
|
118
|
+
if (updated && isLazyCallbackProperty(property)) {
|
|
119
|
+
const state = lazyCallbackStates.get(target);
|
|
120
|
+
if (state) {
|
|
121
|
+
settleCallbackProperty(target, state, property);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return updated;
|
|
125
|
+
},
|
|
126
|
+
deleteProperty(target, property) {
|
|
127
|
+
const deleted = Reflect.deleteProperty(target, property);
|
|
128
|
+
if (deleted && isLazyCallbackProperty(property)) {
|
|
129
|
+
const state = lazyCallbackStates.get(target);
|
|
130
|
+
if (state) {
|
|
131
|
+
settleCallbackProperty(target, state, property);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return deleted;
|
|
135
|
+
},
|
|
136
|
+
defineProperty(target, property, descriptor) {
|
|
137
|
+
if (isLazyCallbackProperty(property)) {
|
|
138
|
+
const replacesValue =
|
|
139
|
+
"value" in descriptor || "get" in descriptor || "set" in descriptor;
|
|
140
|
+
if (!replacesValue) {
|
|
141
|
+
materializeCallbackProperty(target, property);
|
|
142
|
+
}
|
|
143
|
+
const defined = Reflect.defineProperty(target, property, descriptor);
|
|
144
|
+
if (defined && replacesValue) {
|
|
145
|
+
const state = lazyCallbackStates.get(target);
|
|
146
|
+
if (state) {
|
|
147
|
+
settleCallbackProperty(target, state, property);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return defined;
|
|
151
|
+
}
|
|
152
|
+
return Reflect.defineProperty(target, property, descriptor);
|
|
153
|
+
},
|
|
154
|
+
getOwnPropertyDescriptor(target, property) {
|
|
155
|
+
if (isLazyCallbackProperty(property)) {
|
|
156
|
+
materializeCallbackProperty(target, property);
|
|
157
|
+
}
|
|
158
|
+
return Reflect.getOwnPropertyDescriptor(target, property);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const detachCanPerformCallbackProperties = <
|
|
163
|
+
T extends { operation: Operation; entry: Entry<any> },
|
|
164
|
+
>(
|
|
165
|
+
properties: T,
|
|
166
|
+
): T => {
|
|
167
|
+
lazyCallbackStates.set(properties, {
|
|
168
|
+
operation: properties.operation,
|
|
169
|
+
entry: properties.entry,
|
|
170
|
+
operationPending: true,
|
|
171
|
+
entryPending: true,
|
|
172
|
+
});
|
|
173
|
+
Reflect.set(properties, "operation", undefined, properties);
|
|
174
|
+
Reflect.set(properties, "entry", undefined, properties);
|
|
175
|
+
return new Proxy(properties, lazyCallbackHandler);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Return an Entry view whose public payload accessors cannot expose the
|
|
180
|
+
* verified payload buffer to application callbacks. The byte copy is lazy:
|
|
181
|
+
* callbacks that only inspect hashes, clocks, or signers allocate nothing.
|
|
182
|
+
*/
|
|
183
|
+
const detachedCallbackEntries = new WeakSet<object>();
|
|
184
|
+
|
|
185
|
+
export const detachEntryPayloadForCallback = <T>(entry: Entry<T>): Entry<T> => {
|
|
186
|
+
if (detachedCallbackEntries.has(entry)) {
|
|
187
|
+
return entry;
|
|
188
|
+
}
|
|
189
|
+
let detachedPayload: any;
|
|
190
|
+
let detachedMaterialized: Entry<T> | undefined;
|
|
191
|
+
let callbackEntry: Entry<T>;
|
|
192
|
+
const boundMethods = new Map<
|
|
193
|
+
PropertyKey,
|
|
194
|
+
{ source: Function; bound: Function }
|
|
195
|
+
>();
|
|
196
|
+
const copyPayload = (payload: any) => {
|
|
197
|
+
if (detachedPayload) {
|
|
198
|
+
return detachedPayload;
|
|
199
|
+
}
|
|
200
|
+
const data = new Uint8Array(payload.data);
|
|
201
|
+
const descriptors = Object.getOwnPropertyDescriptors(payload) as Record<
|
|
202
|
+
PropertyKey,
|
|
203
|
+
PropertyDescriptor
|
|
204
|
+
>;
|
|
205
|
+
descriptors.data = {
|
|
206
|
+
...descriptors.data,
|
|
207
|
+
value: data,
|
|
208
|
+
};
|
|
209
|
+
descriptors._value = {
|
|
210
|
+
configurable: true,
|
|
211
|
+
enumerable: false,
|
|
212
|
+
writable: true,
|
|
213
|
+
...descriptors._value,
|
|
214
|
+
value: payload.isDecoded ? payload.encoding.decoder(data) : undefined,
|
|
215
|
+
};
|
|
216
|
+
detachedPayload = Object.create(
|
|
217
|
+
Object.getPrototypeOf(payload),
|
|
218
|
+
descriptors,
|
|
219
|
+
);
|
|
220
|
+
if (!Object.isExtensible(payload)) {
|
|
221
|
+
Object.preventExtensions(detachedPayload);
|
|
222
|
+
}
|
|
223
|
+
return detachedPayload;
|
|
224
|
+
};
|
|
225
|
+
const getPayload = async () => {
|
|
226
|
+
const entryWithPayloadMethod = entry as Entry<T> & {
|
|
227
|
+
getPayload?: () => unknown;
|
|
228
|
+
};
|
|
229
|
+
if (typeof entryWithPayloadMethod.getPayload === "function") {
|
|
230
|
+
return copyPayload(await entryWithPayloadMethod.getPayload.call(entry));
|
|
231
|
+
}
|
|
232
|
+
const materialized = entry.toMaterialized();
|
|
233
|
+
if (materialized !== entry) {
|
|
234
|
+
const materializedWithPayloadMethod = materialized as Entry<T> & {
|
|
235
|
+
getPayload?: () => unknown;
|
|
236
|
+
};
|
|
237
|
+
if (typeof materializedWithPayloadMethod.getPayload === "function") {
|
|
238
|
+
return copyPayload(
|
|
239
|
+
await materializedWithPayloadMethod.getPayload.call(materialized),
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return copyPayload(materialized.payload);
|
|
243
|
+
}
|
|
244
|
+
return copyPayload(entry.payload);
|
|
245
|
+
};
|
|
246
|
+
const getPayloadValue = async () => {
|
|
247
|
+
const payload = await getPayload();
|
|
248
|
+
return payload.isDecoded ? payload.value : payload.getValue();
|
|
249
|
+
};
|
|
250
|
+
callbackEntry = new Proxy(entry, {
|
|
251
|
+
get(target, property) {
|
|
252
|
+
if (property === "payload") {
|
|
253
|
+
return copyPayload(entry.payload);
|
|
254
|
+
}
|
|
255
|
+
if (
|
|
256
|
+
property === "getPayload" &&
|
|
257
|
+
typeof (entry as Entry<T> & { getPayload?: unknown }).getPayload ===
|
|
258
|
+
"function"
|
|
259
|
+
) {
|
|
260
|
+
return getPayload;
|
|
261
|
+
}
|
|
262
|
+
if (property === "getPayloadValue") {
|
|
263
|
+
return getPayloadValue;
|
|
264
|
+
}
|
|
265
|
+
if (property === "getStorageBytes") {
|
|
266
|
+
return () => new Uint8Array(entry.getStorageBytes());
|
|
267
|
+
}
|
|
268
|
+
if (property === "valueOf") {
|
|
269
|
+
return () => callbackEntry;
|
|
270
|
+
}
|
|
271
|
+
if (property === "toMaterialized") {
|
|
272
|
+
return () => {
|
|
273
|
+
const materialized = entry.toMaterialized();
|
|
274
|
+
if (materialized === entry) {
|
|
275
|
+
return callbackEntry;
|
|
276
|
+
}
|
|
277
|
+
return (detachedMaterialized ??=
|
|
278
|
+
detachEntryPayloadForCallback(materialized));
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (property === "toSignable") {
|
|
282
|
+
return () => detachEntryPayloadForCallback(entry.toSignable());
|
|
283
|
+
}
|
|
284
|
+
if (property === "init") {
|
|
285
|
+
return (properties: Parameters<Entry<T>["init"]>[0]) => {
|
|
286
|
+
entry.init(properties);
|
|
287
|
+
return callbackEntry;
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const value = Reflect.get(target, property, target);
|
|
291
|
+
if (property === "constructor" || typeof value !== "function") {
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
const existing = boundMethods.get(property);
|
|
295
|
+
if (existing && existing.source === value) {
|
|
296
|
+
return existing.bound;
|
|
297
|
+
}
|
|
298
|
+
const bound = value.bind(target);
|
|
299
|
+
boundMethods.set(property, { source: value, bound });
|
|
300
|
+
return bound;
|
|
301
|
+
},
|
|
302
|
+
set(target, property, value) {
|
|
303
|
+
return Reflect.set(target, property, value, target);
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
detachedCallbackEntries.add(callbackEntry);
|
|
307
|
+
return callbackEntry;
|
|
308
|
+
};
|
package/src/domain.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ReplicationDomain,
|
|
10
10
|
type SharedLog,
|
|
11
11
|
} from "@peerbit/shared-log";
|
|
12
|
+
import { detachEntryPayloadForCallback } from "./callback-detachment.js";
|
|
12
13
|
import { type Operation, isPutOperation } from "./operation.js";
|
|
13
14
|
import type { DocumentIndex } from "./search.js";
|
|
14
15
|
|
|
@@ -93,8 +94,13 @@ export const createDocumentDomain =
|
|
|
93
94
|
): ((db: DB) => CustomDocumentDomain<InferR<DB>>) =>
|
|
94
95
|
(db: DB) => {
|
|
95
96
|
let maxValue = args.resolution === "u32" ? MAX_U32 : MAX_U64;
|
|
97
|
+
const detachEntry = (
|
|
98
|
+
entry: ShallowEntry | Entry<Operation> | EntryReplicated<any>,
|
|
99
|
+
) =>
|
|
100
|
+
entry instanceof Entry ? detachEntryPayloadForCallback(entry) : entry;
|
|
96
101
|
let fromEntry = (args as FromEntry<InferR<DB>>).fromEntry
|
|
97
|
-
? (
|
|
102
|
+
? (entry: ShallowEntry | Entry<Operation> | EntryReplicated<any>) =>
|
|
103
|
+
(args as FromEntry<InferR<DB>>).fromEntry!(detachEntry(entry))
|
|
98
104
|
: async (
|
|
99
105
|
entry: ShallowEntry | Entry<Operation> | EntryReplicated<any>,
|
|
100
106
|
) => {
|
|
@@ -106,11 +112,13 @@ export const createDocumentDomain =
|
|
|
106
112
|
if (!item) {
|
|
107
113
|
logger.error("Item not found");
|
|
108
114
|
} else if (isPutOperation(item)) {
|
|
109
|
-
document = db.index.valueEncoding.decoder(
|
|
115
|
+
document = db.index.valueEncoding.decoder(
|
|
116
|
+
new Uint8Array(item.data),
|
|
117
|
+
);
|
|
110
118
|
}
|
|
111
119
|
return (args as FromValue<any, any>).fromValue!(
|
|
112
120
|
document,
|
|
113
|
-
entry,
|
|
121
|
+
detachEntry(entry),
|
|
114
122
|
) as NumberFromType<InferR<DB>>;
|
|
115
123
|
};
|
|
116
124
|
return {
|