@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerbit/document",
3
- "version": "15.0.12",
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/document-interface": "3.2.65",
66
- "@peerbit/indexer-interface": "3.0.11",
67
- "@peerbit/indexer-simple": "1.2.15",
68
- "@peerbit/indexer-sqlite3": "3.0.17",
69
- "@peerbit/log": "6.2.22",
70
- "@peerbit/indexer-cache": "0.2.14",
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/rpc": "6.1.21",
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.12"
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
- "@peerbit/test-utils": "3.1.32",
91
- "peerbit": "5.3.32"
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
- ? (args as FromEntry<InferR<DB>>).fromEntry!
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(item.data);
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 {