@peerbit/document 8.0.5 → 8.0.6-27dca31

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/src/domain.ts CHANGED
@@ -1,56 +1,146 @@
1
+ import type * as types from "@peerbit/document-interface";
1
2
  import { Entry, type ShallowEntry } from "@peerbit/log";
3
+ import { logger as loggerFn } from "@peerbit/logger";
2
4
  import {
3
5
  type EntryReplicated,
6
+ MAX_U32,
7
+ MAX_U64,
8
+ type NumberFromType,
4
9
  type ReplicationDomain,
10
+ type SharedLog,
5
11
  } from "@peerbit/shared-log";
6
- import {
7
- type Documents,
8
- type Operation,
9
- isPutOperation,
10
- } from "../src/index.js";
11
-
12
- type RangeArgs = { from: number; to: number };
13
- export type CustomDomain = ReplicationDomain<RangeArgs, Operation, "u32">;
14
-
15
- export const createDocumentDomain = <T extends object>(
16
- db: Documents<T, any, CustomDomain>,
17
- options: {
18
- fromValue: (value: T) => number;
19
- fromMissing?: (
20
- entry: EntryReplicated<"u32"> | ShallowEntry | Entry<Operation>,
21
- ) => number;
22
- },
23
- ): CustomDomain => {
24
- const fromValue = options.fromValue;
25
- const fromMissing = options.fromMissing || (() => 0xffffffff);
26
- return {
27
- type: "custom",
28
- resolution: "u32",
29
- fromArgs(args, log) {
30
- if (!args) {
31
- return { offset: log.node.identity.publicKey };
32
- }
33
- return {
34
- offset: args.from,
35
- length: args.to - args.from,
36
- };
37
- },
38
- fromEntry: async (entry) => {
39
- const item = await (
40
- entry instanceof Entry ? entry : await db.log.log.get(entry.hash)
41
- )?.getPayloadValue();
42
- if (!item) {
43
- // eslint-disable-next-line no-console
44
- console.error("Item not found");
45
- return fromMissing(entry);
46
- }
47
-
48
- if (isPutOperation(item)) {
49
- const document = db.index.valueEncoding.decoder(item.data);
50
- return fromValue(document);
51
- }
52
-
53
- return fromMissing(entry);
54
- },
55
- };
12
+ import { type Operation, isPutOperation } from "./operation.js";
13
+ import type { DocumentIndex } from "./search.js";
14
+
15
+ const logger = loggerFn({ module: "document-domain" });
16
+
17
+ type InferT<D> = D extends Documents<infer T, any, any> ? T : never;
18
+ type InferR<D> =
19
+ D extends Documents<any, ReplicationDomain<any, any, infer R>> ? R : never;
20
+
21
+ type Documents<
22
+ T,
23
+ D extends ReplicationDomain<any, Operation, R>,
24
+ R extends "u32" | "u64" = D extends ReplicationDomain<any, T, infer I>
25
+ ? I
26
+ : "u32",
27
+ > = { log: SharedLog<Operation, D, R>; index: DocumentIndex<T, any, D> };
28
+
29
+ type RangeArgs<R extends "u32" | "u64"> = {
30
+ from: NumberFromType<R>;
31
+ to: NumberFromType<R>;
32
+ };
33
+ export type CustomDocumentDomain<R extends "u32" | "u64"> = ReplicationDomain<
34
+ RangeArgs<R>,
35
+ Operation,
36
+ R
37
+ > & { canProjectToOneSegment: (request: types.SearchRequest) => boolean };
38
+
39
+ type FromEntry<R extends "u32" | "u64"> = {
40
+ fromEntry?: (
41
+ entry: ShallowEntry | Entry<Operation> | EntryReplicated<R>,
42
+ ) => NumberFromType<R>;
56
43
  };
44
+ type FromValue<T, R extends "u32" | "u64"> = {
45
+ fromValue?: (
46
+ value: T | undefined,
47
+ entry: ShallowEntry | Entry<Operation> | EntryReplicated<R>,
48
+ ) => NumberFromType<R>;
49
+ };
50
+
51
+ type CreateArgs<
52
+ R extends "u32" | "u64",
53
+ DB extends Documents<any, any, any>,
54
+ > = {
55
+ resolution: R;
56
+ canProjectToOneSegment: (request: types.SearchRequest) => boolean;
57
+ mergeSegmentMaxDelta?: number;
58
+ } & (FromEntry<R> | FromValue<InferT<DB>, R>);
59
+
60
+ export const createDocumentDomainFromProperty = <
61
+ R extends "u32" | "u64",
62
+ DB extends Documents<any, any, any>,
63
+ >(properties: {
64
+ property: keyof InferT<DB>;
65
+ resolution: R;
66
+ mergeSegmentMaxDelta?: number;
67
+ }): ((db: DB) => CustomDocumentDomain<InferR<DB>>) => {
68
+ const coerceNumber = (number: number | bigint): NumberFromType<R> =>
69
+ (properties.resolution === "u32"
70
+ ? number
71
+ : BigInt(number)) as NumberFromType<R>;
72
+ return createDocumentDomain({
73
+ resolution: properties.resolution,
74
+ canProjectToOneSegment: (request) =>
75
+ request.sort[0]?.key[0] === properties.property,
76
+ fromValue: (value) => coerceNumber(value![properties.property]),
77
+ mergeSegmentMaxDelta: properties.mergeSegmentMaxDelta,
78
+ });
79
+ };
80
+
81
+ export const createDocumentDomain =
82
+ <R extends "u32" | "u64", DB extends Documents<any, any, any>>(
83
+ args: CreateArgs<R, DB>,
84
+ ): ((db: DB) => CustomDocumentDomain<InferR<DB>>) =>
85
+ (db: DB) => {
86
+ let maxValue = args.resolution === "u32" ? MAX_U32 : MAX_U64;
87
+ let fromEntry = (args as FromEntry<InferR<DB>>).fromEntry
88
+ ? (args as FromEntry<InferR<DB>>).fromEntry!
89
+ : async (
90
+ entry: ShallowEntry | Entry<Operation> | EntryReplicated<any>,
91
+ ) => {
92
+ const item = await (
93
+ entry instanceof Entry ? entry : await db.log.log.get(entry.hash)
94
+ )?.getPayloadValue();
95
+
96
+ let document: InferT<DB> | undefined = undefined;
97
+ if (!item) {
98
+ logger.error("Item not found");
99
+ } else if (isPutOperation(item)) {
100
+ document = db.index.valueEncoding.decoder(item.data);
101
+ }
102
+ return (args as FromValue<any, any>).fromValue!(
103
+ document,
104
+ entry,
105
+ ) as NumberFromType<InferR<DB>>;
106
+ };
107
+ return {
108
+ type: "custom",
109
+ resolution: args.resolution as InferR<DB>,
110
+ canProjectToOneSegment: args.canProjectToOneSegment,
111
+ fromArgs(args) {
112
+ if (!args) {
113
+ return {
114
+ offset: db.log.node.identity.publicKey,
115
+ length: maxValue as NumberFromType<InferR<DB>>,
116
+ };
117
+ }
118
+ return {
119
+ offset: args.from,
120
+ length: (args.to - args.from) as NumberFromType<InferR<DB>>,
121
+ };
122
+ },
123
+ fromEntry,
124
+ canMerge:
125
+ args.mergeSegmentMaxDelta == null
126
+ ? undefined
127
+ : (from, into) => {
128
+ if (
129
+ Math.abs(Number(from.end2 - into.start1)) <=
130
+ args.mergeSegmentMaxDelta!
131
+ ) {
132
+ return true;
133
+ }
134
+ if (
135
+ Math.abs(Number(from.start1 - into.end2)) <=
136
+ args.mergeSegmentMaxDelta!
137
+ ) {
138
+ return true;
139
+ }
140
+ if (from.overlaps(into)) {
141
+ return true;
142
+ }
143
+ return false;
144
+ },
145
+ };
146
+ };
package/src/program.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  type SharedLogOptions,
25
25
  } from "@peerbit/shared-log";
26
26
  import { MAX_BATCH_SIZE } from "./constants.js";
27
+ import type { CustomDocumentDomain } from "./domain.js";
27
28
  import {
28
29
  BORSH_ENCODING_OPERATION,
29
30
  DeleteOperation,
@@ -76,9 +77,11 @@ export type CanPerform<T> = (
76
77
  properties: CanPerformOperations<T>,
77
78
  ) => MaybePromise<boolean>;
78
79
 
80
+ type InferR<D> = D extends ReplicationDomain<any, any, infer I> ? I : "u32";
81
+
79
82
  export type SetupOptions<
80
83
  T,
81
- I = T,
84
+ I extends Record<string, any> = T extends Record<string, any> ? T : any,
82
85
  D extends ReplicationDomain<any, Operation, any> = any,
83
86
  > = {
84
87
  type: AbstractType<T>;
@@ -94,7 +97,8 @@ export type SetupOptions<
94
97
  trim?: TrimOptions;
95
98
  };
96
99
  compatibility?: 6 | 7;
97
- } & Exclude<SharedLogOptions<Operation, D>, "compatibility">;
100
+ domain?: (db: Documents<T, I, D>) => CustomDocumentDomain<InferR<D>>;
101
+ } & Omit<SharedLogOptions<Operation, D, InferR<D>>, "compatibility" | "domain">;
98
102
 
99
103
  export type ExtractArgs<T> =
100
104
  T extends ReplicationDomain<infer Args, any, any> ? Args : never;
@@ -106,7 +110,7 @@ export class Documents<
106
110
  D extends ReplicationDomain<any, Operation, any> = any,
107
111
  > extends Program<SetupOptions<T, I, D>, DocumentEvents<T> & ProgramEvents> {
108
112
  @field({ type: SharedLog })
109
- log: SharedLog<Operation, D>;
113
+ log: SharedLog<Operation, D, InferR<D>>;
110
114
 
111
115
  @field({ type: "bool" })
112
116
  immutable: boolean; // "Can I overwrite a document?"
@@ -118,6 +122,7 @@ export class Documents<
118
122
 
119
123
  private _optionCanPerform?: CanPerform<T>;
120
124
  private idResolver!: (any: any) => indexerTypes.IdPrimitive;
125
+ private domain?: CustomDocumentDomain<InferR<D>>;
121
126
 
122
127
  canOpen?: (program: T, entry: Entry<Operation>) => Promise<boolean> | boolean;
123
128
 
@@ -171,13 +176,16 @@ export class Documents<
171
176
  documentType: this._clazz,
172
177
  transform: options.index,
173
178
  indexBy: idProperty,
174
- sync: async (result: documentsTypes.Results<T>) => {
179
+ sync: async (
180
+ query: documentsTypes.SearchRequest,
181
+ result: documentsTypes.Results<T>,
182
+ ) => {
175
183
  // here we arrive for all the results we want to persist.
176
- // we we need to do here is
177
- // 1. add the entry to a list of entries that we should persist through prunes
184
+
185
+ let mergeSegments = this.domain?.canProjectToOneSegment(query);
178
186
  await this.log.join(
179
187
  result.results.map((x) => x.context.head),
180
- { replicate: true },
188
+ { replicate: { assumeSynced: true, mergeSegments } },
181
189
  );
182
190
  },
183
191
  dbType: this.constructor,
@@ -191,6 +199,8 @@ export class Documents<
191
199
  } else if (options.compatibility === 7) {
192
200
  logCompatiblity = 9;
193
201
  }
202
+
203
+ this.domain = options.domain?.(this);
194
204
  await this.log.open({
195
205
  encoding: BORSH_ENCODING_OPERATION,
196
206
  canReplicate: options?.canReplicate,
@@ -199,7 +209,9 @@ export class Documents<
199
209
  trim: options?.log?.trim,
200
210
  replicate: options?.replicate,
201
211
  replicas: options?.replicas,
202
- domain: options?.domain,
212
+ domain: (options?.domain
213
+ ? (log: any) => options.domain!(this)
214
+ : undefined) as any, /// TODO types,
203
215
  compatibility: logCompatiblity,
204
216
  });
205
217
  }
@@ -1,6 +1,5 @@
1
1
  import { Cache } from "@peerbit/cache";
2
2
  import {
3
- type CloseIteratorRequest,
4
3
  type CollectNextRequest,
5
4
  SearchRequest,
6
5
  } from "@peerbit/document-interface";
@@ -44,7 +43,7 @@ export class ResumableIterators<T extends Record<string, any>> {
44
43
  return next;
45
44
  }
46
45
 
47
- async close(close: CloseIteratorRequest) {
46
+ close(close: { idString: string }) {
48
47
  this.clear(close.idString);
49
48
  }
50
49
 
package/src/search.ts CHANGED
@@ -56,6 +56,7 @@ export type ResultsIterator<T> = {
56
56
  close: () => Promise<void>;
57
57
  next: (number: number) => Promise<T[]>;
58
58
  done: () => boolean;
59
+ all: () => Promise<T[]>;
59
60
  };
60
61
 
61
62
  type QueryDetailedOptions<T, D> = QueryOptions<T, D> & {
@@ -66,9 +67,13 @@ type QueryDetailedOptions<T, D> = QueryOptions<T, D> & {
66
67
  };
67
68
 
68
69
  const introduceEntries = async <T, D>(
70
+ queryRequest: types.SearchRequest,
69
71
  responses: RPCResponse<types.AbstractSearchResult<T>>[],
70
72
  type: AbstractType<T>,
71
- sync: (result: types.Results<T>) => Promise<void>,
73
+ sync: (
74
+ queryRequest: types.SearchRequest,
75
+ result: types.Results<T>,
76
+ ) => Promise<void>,
72
77
  options?: QueryDetailedOptions<T, D>,
73
78
  ): Promise<RPCResponse<types.Results<T>>[]> => {
74
79
  const results: RPCResponse<types.Results<T>>[] = [];
@@ -80,7 +85,7 @@ const introduceEntries = async <T, D>(
80
85
  if (response.response instanceof types.Results) {
81
86
  response.response.results.forEach((r) => r.init(type));
82
87
  if (typeof options?.remote !== "boolean" && options?.remote?.replicate) {
83
- await sync(response.response);
88
+ await sync(queryRequest, response.response);
84
89
  }
85
90
  options?.onResponse &&
86
91
  (await options.onResponse(response.response, response.from!)); // TODO fix types
@@ -199,10 +204,13 @@ export type OpenOptions<
199
204
  > = {
200
205
  documentType: AbstractType<T>;
201
206
  dbType: AbstractType<types.IDocumentStore<T>>;
202
- log: SharedLog<Operation, D>;
207
+ log: SharedLog<Operation, D, any>;
203
208
  canRead?: CanRead<T>;
204
209
  canSearch?: CanSearch;
205
- sync: (result: types.Results<T>) => Promise<void>;
210
+ sync: (
211
+ request: types.SearchRequest,
212
+ result: types.Results<T>,
213
+ ) => Promise<void>;
206
214
  indexBy?: string | string[];
207
215
  transform?: TransformOptions<T, I>;
208
216
  };
@@ -245,9 +253,12 @@ export class DocumentIndex<
245
253
 
246
254
  private _valueEncoding: Encoding<T>;
247
255
 
248
- private _sync: (result: types.Results<T>) => Promise<void>;
256
+ private _sync: (
257
+ reques: types.SearchRequest,
258
+ result: types.Results<T>,
259
+ ) => Promise<void>;
249
260
 
250
- private _log: SharedLog<Operation, D>;
261
+ private _log: SharedLog<Operation, D, any>;
251
262
 
252
263
  private _resolverProgramCache?: Map<string | number | bigint, T>;
253
264
  private _resolverCache: Cache<T>;
@@ -413,6 +424,7 @@ export class DocumentIndex<
413
424
  async close(from?: Program): Promise<boolean> {
414
425
  const closed = await super.close(from);
415
426
  if (closed) {
427
+ this.clearAllResultQueues();
416
428
  await this.index.stop?.();
417
429
  }
418
430
  return closed;
@@ -421,6 +433,7 @@ export class DocumentIndex<
421
433
  async drop(from?: Program): Promise<boolean> {
422
434
  const dropped = await super.drop(from);
423
435
  if (dropped) {
436
+ this.clearAllResultQueues();
424
437
  await this.index.drop?.();
425
438
  await this.index.stop?.();
426
439
  }
@@ -629,6 +642,7 @@ export class DocumentIndex<
629
642
 
630
643
  if (prevQueued) {
631
644
  this._resultQueue.delete(query.idString);
645
+ clearTimeout(prevQueued.timeout);
632
646
  prevQueued = undefined;
633
647
  }
634
648
 
@@ -684,7 +698,7 @@ export class DocumentIndex<
684
698
  return results;
685
699
  }
686
700
 
687
- clearResultsQueue(
701
+ private clearResultsQueue(
688
702
  query:
689
703
  | types.SearchRequest
690
704
  | types.CollectNextRequest
@@ -696,6 +710,15 @@ export class DocumentIndex<
696
710
  this._resultQueue.delete(query.idString);
697
711
  }
698
712
  }
713
+
714
+ private clearAllResultQueues() {
715
+ for (const [key, queue] of this._resultQueue) {
716
+ clearTimeout(queue.timeout);
717
+ this._resultQueue.delete(key);
718
+ this._resumableIterators.close({ idString: key });
719
+ }
720
+ }
721
+
699
722
  async processCloseIteratorRequest(
700
723
  query: types.CloseIteratorRequest,
701
724
  publicKey: PublicSignKey,
@@ -775,6 +798,7 @@ export class DocumentIndex<
775
798
  results: RPCResponse<types.AbstractSearchResult<T>>[],
776
799
  ) => {
777
800
  for (const r of await introduceEntries(
801
+ queryRequest,
778
802
  results,
779
803
  this.documentType,
780
804
  this._sync,
@@ -1062,6 +1086,7 @@ export class DocumentIndex<
1062
1086
  })
1063
1087
  .then((response) =>
1064
1088
  introduceEntries(
1089
+ queryRequest,
1065
1090
  response,
1066
1091
  this.documentType,
1067
1092
  this._sync,
@@ -1212,11 +1237,19 @@ export class DocumentIndex<
1212
1237
  }
1213
1238
  await Promise.all(promises);
1214
1239
  };
1215
-
1240
+ let doneFn = () => done;
1216
1241
  return {
1217
1242
  close,
1218
1243
  next,
1219
- done: () => done,
1244
+ done: doneFn,
1245
+ all: async () => {
1246
+ let result: T[] = [];
1247
+ while (doneFn() !== true) {
1248
+ let batch = await next(100);
1249
+ result.push(...batch);
1250
+ }
1251
+ return result;
1252
+ },
1220
1253
  };
1221
1254
  }
1222
1255
  }