@fireproof/core-base 0.23.1 → 0.23.3
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/apply-head-queue.js.map +1 -1
- package/bundle-not-impl.js.map +1 -1
- package/compact-strategies.js.map +1 -1
- package/crdt-clock.js.map +1 -1
- package/crdt-helpers.js.map +1 -1
- package/crdt.js.map +1 -1
- package/database.js.map +1 -1
- package/index.js.map +1 -1
- package/indexer-helpers.js.map +1 -1
- package/indexer.js.map +1 -1
- package/ledger.js.map +1 -1
- package/package.json +9 -9
- package/version.js.map +1 -1
- package/write-queue.js.map +1 -1
- package/apply-head-queue.ts +0 -72
- package/bundle-not-impl.ts +0 -4
- package/compact-strategies.ts +0 -95
- package/crdt-clock.ts +0 -192
- package/crdt-helpers.ts +0 -408
- package/crdt.ts +0 -275
- package/database.ts +0 -200
- package/index.ts +0 -15
- package/indexer-helpers.ts +0 -263
- package/indexer.ts +0 -360
- package/ledger.ts +0 -345
- package/tsconfig.json +0 -18
- package/version.ts +0 -3
- package/write-queue.ts +0 -93
package/indexer-helpers.ts
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
/// <reference types="@fireproof/core-types-base/prolly-trees.d.ts" />
|
|
2
|
-
import type { Block } from "multiformats";
|
|
3
|
-
import { sha256 as hasher } from "multiformats/hashes/sha2";
|
|
4
|
-
import * as codec from "@ipld/dag-cbor";
|
|
5
|
-
|
|
6
|
-
// @ts-expect-error "charwise" has no types
|
|
7
|
-
import charwise from "charwise";
|
|
8
|
-
import * as DbIndex from "prolly-trees/db-index";
|
|
9
|
-
import { bf, simpleCompare } from "prolly-trees/utils";
|
|
10
|
-
import { nocache as cache } from "prolly-trees/cache";
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
DocUpdate,
|
|
14
|
-
MapFn,
|
|
15
|
-
DocFragment,
|
|
16
|
-
IndexUpdate,
|
|
17
|
-
QueryOpts,
|
|
18
|
-
IndexRows,
|
|
19
|
-
DocWithId,
|
|
20
|
-
IndexKeyType,
|
|
21
|
-
IndexKey,
|
|
22
|
-
DocTypes,
|
|
23
|
-
DocObject,
|
|
24
|
-
IndexUpdateString,
|
|
25
|
-
CarTransaction,
|
|
26
|
-
CRDT,
|
|
27
|
-
IndexTree,
|
|
28
|
-
FPIndexRow,
|
|
29
|
-
} from "@fireproof/core-types-base";
|
|
30
|
-
import { BlockFetcher, AnyLink, AnyBlock } from "@fireproof/core-types-blockstore";
|
|
31
|
-
import { Logger } from "@adviser/cement";
|
|
32
|
-
import { anyBlock2FPBlock } from "@fireproof/core-blockstore";
|
|
33
|
-
import { StaticProllyOptions, BaseNode as ProllyNode, IndexRow } from "prolly-trees/base";
|
|
34
|
-
import { asyncBlockCreate } from "@fireproof/core-runtime";
|
|
35
|
-
|
|
36
|
-
type CompareRef = string | number;
|
|
37
|
-
export type CompareKey = [string | number, CompareRef];
|
|
38
|
-
|
|
39
|
-
function refCompare(aRef: CompareRef, bRef: CompareRef) {
|
|
40
|
-
if (Number.isNaN(aRef)) return -1;
|
|
41
|
-
if (Number.isNaN(bRef)) throw new Error("ref may not be Infinity or NaN");
|
|
42
|
-
if (aRef === Infinity) return 1;
|
|
43
|
-
// if (!Number.isFinite(bRef)) throw new Error('ref may not be Infinity or NaN')
|
|
44
|
-
|
|
45
|
-
return simpleCompare(aRef, bRef) as number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function compare(a: CompareKey, b: CompareKey) {
|
|
49
|
-
const [aKey, aRef] = a;
|
|
50
|
-
const [bKey, bRef] = b;
|
|
51
|
-
|
|
52
|
-
const comp: number = simpleCompare(aKey, bKey);
|
|
53
|
-
if (comp !== 0) return comp;
|
|
54
|
-
return refCompare(aRef, bRef);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// declare function bf<T>(factor: number): (entry: T, dist: number) => Promise<boolean>;
|
|
58
|
-
// chunker: (entry: T, distance: number) => Promise<boolean>;
|
|
59
|
-
export const byKeyOpts: StaticProllyOptions<CompareKey> = { cache, chunker: bf(30), codec, hasher, compare };
|
|
60
|
-
|
|
61
|
-
export const byIdOpts: StaticProllyOptions<string | number> = { cache, chunker: bf(30), codec, hasher, compare: simpleCompare };
|
|
62
|
-
|
|
63
|
-
export interface IndexDoc<K extends IndexKeyType> {
|
|
64
|
-
readonly key: IndexKey<K>;
|
|
65
|
-
readonly value: DocFragment;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface IndexDocString {
|
|
69
|
-
readonly key: string;
|
|
70
|
-
readonly value: DocFragment;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function indexEntriesForChanges<T extends DocTypes, K extends IndexKeyType>(
|
|
74
|
-
changes: DocUpdate<T>[],
|
|
75
|
-
mapFn: MapFn<T>,
|
|
76
|
-
): IndexDoc<K>[] {
|
|
77
|
-
const indexEntries: IndexDoc<K>[] = [];
|
|
78
|
-
changes.forEach(({ id: key, value, del }) => {
|
|
79
|
-
if (del || !value) return;
|
|
80
|
-
let mapCalled = false;
|
|
81
|
-
const mapReturn = mapFn({ ...(value as DocWithId<T>), _id: key }, (k: IndexKeyType, v?: DocFragment) => {
|
|
82
|
-
mapCalled = true;
|
|
83
|
-
if (typeof k === "undefined") return;
|
|
84
|
-
indexEntries.push({
|
|
85
|
-
key: [charwise.encode(k) as K, key],
|
|
86
|
-
value: v || null,
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
if (!mapCalled && typeof mapReturn !== "undefined") {
|
|
90
|
-
indexEntries.push({
|
|
91
|
-
key: [charwise.encode(mapReturn) as K, key],
|
|
92
|
-
value: null,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
return indexEntries;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function makeProllyGetBlock(blocks: BlockFetcher): (address: AnyLink) => Promise<AnyBlock> {
|
|
100
|
-
return async (address: AnyLink) => {
|
|
101
|
-
const block = await blocks.get(address);
|
|
102
|
-
if (!block) throw new Error(`Missing block ${address.toString()}`);
|
|
103
|
-
const { cid, bytes } = block;
|
|
104
|
-
return asyncBlockCreate({ cid, bytes, hasher, codec }) as Promise<AnyBlock>;
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function bulkIndex<K extends IndexKeyType, T extends DocFragment, CT>(
|
|
109
|
-
logger: Logger,
|
|
110
|
-
tblocks: CarTransaction,
|
|
111
|
-
inIndex: IndexTree<K, T>,
|
|
112
|
-
indexEntries: (IndexUpdate<K> | IndexUpdateString)[],
|
|
113
|
-
opts: StaticProllyOptions<CT>,
|
|
114
|
-
): Promise<IndexTree<K, T>> {
|
|
115
|
-
logger.Debug().Msg("enter bulkIndex");
|
|
116
|
-
if (!indexEntries.length) return inIndex;
|
|
117
|
-
if (!inIndex.root) {
|
|
118
|
-
if (!inIndex.cid) {
|
|
119
|
-
let returnRootBlock: Block | undefined = undefined;
|
|
120
|
-
let returnNode: ProllyNode<K, T> | undefined = undefined;
|
|
121
|
-
|
|
122
|
-
for await (const node of (await DbIndex.create({
|
|
123
|
-
get: makeProllyGetBlock(tblocks),
|
|
124
|
-
list: indexEntries,
|
|
125
|
-
...opts,
|
|
126
|
-
})) as ProllyNode<K, T>[]) {
|
|
127
|
-
const block = await node.block;
|
|
128
|
-
await tblocks.put(await anyBlock2FPBlock(block));
|
|
129
|
-
returnRootBlock = block;
|
|
130
|
-
returnNode = node;
|
|
131
|
-
}
|
|
132
|
-
if (!returnNode || !returnRootBlock) throw new Error("failed to create index");
|
|
133
|
-
logger.Debug().Msg("exit !root bulkIndex");
|
|
134
|
-
return { root: returnNode, cid: returnRootBlock.cid };
|
|
135
|
-
} else {
|
|
136
|
-
inIndex.root = (await DbIndex.load({ cid: inIndex.cid, get: makeProllyGetBlock(tblocks), ...opts })) as ProllyNode<K, T>;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
logger.Debug().Msg("pre bulk bulkIndex");
|
|
140
|
-
const { root, blocks: newBlocks } = await inIndex.root.bulk(indexEntries);
|
|
141
|
-
if (root) {
|
|
142
|
-
logger.Debug().Msg("pre root put bulkIndex");
|
|
143
|
-
for await (const block of newBlocks) {
|
|
144
|
-
await tblocks.put(await anyBlock2FPBlock(block));
|
|
145
|
-
}
|
|
146
|
-
return { root, cid: (await root.block).cid };
|
|
147
|
-
} else {
|
|
148
|
-
logger.Debug().Msg("pre !root bulkIndex");
|
|
149
|
-
return { root: undefined, cid: undefined };
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export async function loadIndex<K extends IndexKeyType, T extends DocFragment, CT>(
|
|
154
|
-
tblocks: BlockFetcher,
|
|
155
|
-
cid: AnyLink,
|
|
156
|
-
opts: StaticProllyOptions<CT>,
|
|
157
|
-
): Promise<ProllyNode<K, T>> {
|
|
158
|
-
return (await DbIndex.load({ cid, get: makeProllyGetBlock(tblocks), ...opts })) as ProllyNode<K, T>;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export async function applyQuery<T extends DocObject, K extends IndexKeyType, R extends DocFragment>(
|
|
162
|
-
crdt: CRDT,
|
|
163
|
-
resp: { result: IndexRow<K, R>[] },
|
|
164
|
-
query: QueryOpts<K>,
|
|
165
|
-
): Promise<IndexRows<T, K, R>> {
|
|
166
|
-
if (query.descending) {
|
|
167
|
-
resp.result = resp.result.reverse();
|
|
168
|
-
}
|
|
169
|
-
if (query.limit) {
|
|
170
|
-
resp.result = resp.result.slice(0, query.limit);
|
|
171
|
-
}
|
|
172
|
-
if (query.includeDocs) {
|
|
173
|
-
resp.result = await Promise.all(
|
|
174
|
-
resp.result.map(async (row) => {
|
|
175
|
-
const val = await crdt.get(row.id);
|
|
176
|
-
const doc = val ? ({ ...val.doc, _id: row.id } as DocWithId<T>) : undefined;
|
|
177
|
-
return { ...row, doc };
|
|
178
|
-
}),
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
const rows = resp.result.map(({ key, ...row }) => {
|
|
182
|
-
// First decode the key
|
|
183
|
-
const decodedKey = charwise.decode(key);
|
|
184
|
-
|
|
185
|
-
// Use a type-safe approach with Record to check for potentially missing properties
|
|
186
|
-
// This handles the case where some query results use 'row' instead of 'value'
|
|
187
|
-
const dynamicRow = row as Record<string, unknown>;
|
|
188
|
-
if ("row" in dynamicRow && !("value" in dynamicRow)) {
|
|
189
|
-
// We found a result with 'row' property but no 'value' property
|
|
190
|
-
// Create a new normalized object with the 'value' property
|
|
191
|
-
const normalizedRow: Record<string, unknown> = {};
|
|
192
|
-
Object.keys(dynamicRow).forEach((k) => {
|
|
193
|
-
if (k === "row") {
|
|
194
|
-
normalizedRow.value = dynamicRow[k];
|
|
195
|
-
} else {
|
|
196
|
-
normalizedRow[k] = dynamicRow[k];
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
key: decodedKey,
|
|
202
|
-
...normalizedRow,
|
|
203
|
-
} as IndexRow<K, R>;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Standard case - use the properties as they are
|
|
207
|
-
return {
|
|
208
|
-
key: decodedKey,
|
|
209
|
-
...row,
|
|
210
|
-
} as IndexRow<K, R>;
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// We need to be explicit about the document types here
|
|
214
|
-
const typedRows = rows as FPIndexRow<K, T, R>[];
|
|
215
|
-
|
|
216
|
-
// Simply filter out null/undefined docs and cast the result
|
|
217
|
-
const docs = typedRows.filter((r) => !!r.doc) as unknown as DocWithId<T>[];
|
|
218
|
-
|
|
219
|
-
return {
|
|
220
|
-
rows: typedRows,
|
|
221
|
-
docs,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function encodeRange(range: [IndexKeyType, IndexKeyType]): [string, string] {
|
|
226
|
-
return [charwise.encode(range[0]), charwise.encode(range[1])];
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
export function encodeKey(key: DocFragment): string {
|
|
230
|
-
return charwise.encode(key) as string;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// export interface ProllyIndexRow<K extends IndexKeyType, T extends DocFragment> {
|
|
234
|
-
// readonly id: string;
|
|
235
|
-
// readonly key: IndexKey<K>;
|
|
236
|
-
// readonly value: T;
|
|
237
|
-
// readonly doc?: DocWithId<DocObject>;
|
|
238
|
-
// }
|
|
239
|
-
|
|
240
|
-
// // ProllyNode type based on the ProllyNode from 'prolly-trees/base'
|
|
241
|
-
// interface ProllyNode<K extends IndexKeyType, T extends DocFragment> extends BaseNode<K, T> {
|
|
242
|
-
// getAllEntries(): PromiseLike<{ [x: string]: unknown; result: ProllyIndexRow<K, T>[] }>;
|
|
243
|
-
// getMany<KI extends IndexKeyType>(removeIds: KI[]): Promise<{ /* [x: K]: unknown; */ result: IndexKey<K>[] }>;
|
|
244
|
-
// range(a: string, b: string): Promise<{ result: ProllyIndexRow<K, T>[] }>;
|
|
245
|
-
// get(key: string): Promise<{ result: ProllyIndexRow<K, T>[] }>;
|
|
246
|
-
// bulk(bulk: (IndexUpdate<K> | IndexUpdateString)[]): PromiseLike<{
|
|
247
|
-
// readonly root?: ProllyNode<K, T>;
|
|
248
|
-
// readonly blocks: Block[];
|
|
249
|
-
// }>;
|
|
250
|
-
// readonly address: Promise<Link>;
|
|
251
|
-
// readonly distance: number;
|
|
252
|
-
// compare: (a: unknown, b: unknown) => number;
|
|
253
|
-
// readonly cache: unknown;
|
|
254
|
-
// readonly block: Promise<Block>;
|
|
255
|
-
// }
|
|
256
|
-
|
|
257
|
-
// interface StaticProllyOptions<T> {
|
|
258
|
-
// readonly cache: unknown;
|
|
259
|
-
// chunker: (entry: T, distance: number) => boolean;
|
|
260
|
-
// readonly codec: unknown;
|
|
261
|
-
// readonly hasher: unknown;
|
|
262
|
-
// compare: (a: T, b: T) => number;
|
|
263
|
-
// }
|
package/indexer.ts
DELETED
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
/// <reference types="@fireproof/core-types-base/prolly-trees.d.ts" />
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type ClockHead,
|
|
5
|
-
type DocUpdate,
|
|
6
|
-
type MapFn,
|
|
7
|
-
type IndexUpdate,
|
|
8
|
-
type QueryOpts,
|
|
9
|
-
type IdxMeta,
|
|
10
|
-
type DocFragment,
|
|
11
|
-
type IdxMetaMap,
|
|
12
|
-
type IndexKeyType,
|
|
13
|
-
type IndexRows,
|
|
14
|
-
type DocTypes,
|
|
15
|
-
type IndexUpdateString,
|
|
16
|
-
throwFalsy,
|
|
17
|
-
type IndexTransactionMeta,
|
|
18
|
-
type SuperThis,
|
|
19
|
-
type BaseBlockstore,
|
|
20
|
-
type CRDT,
|
|
21
|
-
type HasCRDT,
|
|
22
|
-
type HasLogger,
|
|
23
|
-
type HasSuperThis,
|
|
24
|
-
type RefLedger,
|
|
25
|
-
type DocWithId,
|
|
26
|
-
IndexIf,
|
|
27
|
-
IndexTree,
|
|
28
|
-
} from "@fireproof/core-types-base";
|
|
29
|
-
// import { BaseBlockstore } from "./blockstore/index.js";
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
bulkIndex,
|
|
33
|
-
indexEntriesForChanges,
|
|
34
|
-
byIdOpts,
|
|
35
|
-
byKeyOpts,
|
|
36
|
-
applyQuery,
|
|
37
|
-
encodeRange,
|
|
38
|
-
encodeKey,
|
|
39
|
-
loadIndex,
|
|
40
|
-
IndexDocString,
|
|
41
|
-
CompareKey,
|
|
42
|
-
} from "./indexer-helpers.js";
|
|
43
|
-
import { ensureLogger } from "@fireproof/core-runtime";
|
|
44
|
-
import { Logger } from "@adviser/cement";
|
|
45
|
-
|
|
46
|
-
// import { ProllyNode } from "prolly-trees/base";
|
|
47
|
-
|
|
48
|
-
function refLedger(u: HasCRDT | RefLedger): u is RefLedger {
|
|
49
|
-
return !!(u as RefLedger).ledger;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function index<T extends DocTypes = DocTypes, K extends IndexKeyType = string, R extends DocFragment = T>(
|
|
53
|
-
refDb: HasLogger & HasSuperThis & (HasCRDT | RefLedger),
|
|
54
|
-
name: string,
|
|
55
|
-
mapFn?: MapFn<T>,
|
|
56
|
-
meta?: IdxMeta,
|
|
57
|
-
): Index<T, K, R> {
|
|
58
|
-
const crdt = refLedger(refDb) ? refDb.ledger.crdt : refDb.crdt;
|
|
59
|
-
|
|
60
|
-
if (mapFn && meta) throw refDb.logger.Error().Msg("cannot provide both mapFn and meta").AsError();
|
|
61
|
-
if (mapFn && mapFn.constructor.name !== "Function") throw refDb.logger.Error().Msg("mapFn must be a function").AsError();
|
|
62
|
-
if (crdt.indexers.has(name)) {
|
|
63
|
-
const idx = crdt.indexers.get(name) as unknown as Index<T, K>;
|
|
64
|
-
idx.applyMapFn(name, mapFn, meta);
|
|
65
|
-
} else {
|
|
66
|
-
const idx = new Index<T, K>(refDb.sthis, crdt, name, mapFn, meta);
|
|
67
|
-
crdt.indexers.set(name, idx as unknown as Index<DocTypes, K, DocTypes>);
|
|
68
|
-
}
|
|
69
|
-
return crdt.indexers.get(name) as unknown as Index<T, K, R>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// interface ByIdIndexIten<K extends IndexKeyType> {
|
|
73
|
-
// readonly key: K;
|
|
74
|
-
// readonly value: [K, K];
|
|
75
|
-
// }
|
|
76
|
-
|
|
77
|
-
export class Index<T extends DocTypes, K extends IndexKeyType = string, R extends DocFragment = T> implements IndexIf<T, K, R> {
|
|
78
|
-
readonly blockstore: BaseBlockstore;
|
|
79
|
-
readonly crdt: CRDT;
|
|
80
|
-
readonly name: string;
|
|
81
|
-
mapFn?: MapFn<T>;
|
|
82
|
-
mapFnString = "";
|
|
83
|
-
byKey: IndexTree<K, R> = {};
|
|
84
|
-
byId: IndexTree<K, R> = {};
|
|
85
|
-
indexHead?: ClockHead;
|
|
86
|
-
|
|
87
|
-
initError?: Error;
|
|
88
|
-
|
|
89
|
-
ready(): Promise<void> {
|
|
90
|
-
return Promise.all([this.blockstore.ready(), this.crdt.ready()]).then(() => {
|
|
91
|
-
/* noop */
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// close(): Promise<void> {
|
|
96
|
-
// return Promise.all([this.blockstore.close(), this.crdt.close()]).then(() => {
|
|
97
|
-
// /* noop */
|
|
98
|
-
// });
|
|
99
|
-
// }
|
|
100
|
-
// destroy(): Promise<void> {
|
|
101
|
-
// return Promise.all([this.blockstore.destroy(), this.crdt.destroy()]).then(() => {
|
|
102
|
-
// /* noop */
|
|
103
|
-
// });
|
|
104
|
-
// }
|
|
105
|
-
|
|
106
|
-
readonly logger: Logger;
|
|
107
|
-
|
|
108
|
-
constructor(sthis: SuperThis, crdt: CRDT, name: string, mapFn?: MapFn<T>, meta?: IdxMeta) {
|
|
109
|
-
this.logger = ensureLogger(sthis, "Index");
|
|
110
|
-
if (!crdt.indexBlockstore) {
|
|
111
|
-
throw sthis.logger.Error().Msg("indexBlockstore not set").AsError();
|
|
112
|
-
}
|
|
113
|
-
this.blockstore = crdt.indexBlockstore;
|
|
114
|
-
this.crdt = crdt as CRDT;
|
|
115
|
-
this.applyMapFn(name, mapFn, meta);
|
|
116
|
-
this.name = name;
|
|
117
|
-
if (!(this.mapFnString || this.initError)) throw this.logger.Error().Msg("missing mapFnString").AsError();
|
|
118
|
-
// this.ready = this.blockstore.ready.then(() => {
|
|
119
|
-
// return;
|
|
120
|
-
// });
|
|
121
|
-
// .then((header: IdxCarHeader) => {
|
|
122
|
-
// // @ts-ignore
|
|
123
|
-
// if (header.head) throw new Error('cannot have head in idx header')
|
|
124
|
-
// if (header.indexes === undefined) throw new Error('missing indexes in idx header')
|
|
125
|
-
// // for (const [name, idx] of Object.entries(header.indexes)) {
|
|
126
|
-
// // index({ _crdt: crdt }, name, undefined, idx as IdxMeta)
|
|
127
|
-
// // }
|
|
128
|
-
// })
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
applyMapFn(name: string, mapFn?: MapFn<T>, meta?: IdxMeta) {
|
|
132
|
-
if (mapFn && meta) throw this.logger.Error().Msg("cannot provide both mapFn and meta").AsError();
|
|
133
|
-
if (this.name && this.name !== name) throw this.logger.Error().Msg("cannot change name").AsError();
|
|
134
|
-
// this.name = name;
|
|
135
|
-
try {
|
|
136
|
-
let mapFnChanged = false;
|
|
137
|
-
|
|
138
|
-
if (meta) {
|
|
139
|
-
// hydrating from header
|
|
140
|
-
if (this.indexHead && this.indexHead.map((c) => c.toString()).join() !== meta.head.map((c) => c.toString()).join()) {
|
|
141
|
-
throw this.logger.Error().Msg("cannot apply different head meta").AsError();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (this.mapFnString) {
|
|
145
|
-
// we already initialized from application code
|
|
146
|
-
if (this.mapFnString !== meta.map) {
|
|
147
|
-
this.mapFnString = meta.map;
|
|
148
|
-
mapFnChanged = true;
|
|
149
|
-
}
|
|
150
|
-
// Always apply the metadata
|
|
151
|
-
this.byId.cid = meta.byId;
|
|
152
|
-
this.byKey.cid = meta.byKey;
|
|
153
|
-
this.indexHead = meta.head;
|
|
154
|
-
} else {
|
|
155
|
-
// we are first
|
|
156
|
-
this.mapFnString = meta.map;
|
|
157
|
-
this.byId.cid = meta.byId;
|
|
158
|
-
this.byKey.cid = meta.byKey;
|
|
159
|
-
this.indexHead = meta.head;
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
if (this.mapFn) {
|
|
163
|
-
// we already initialized from application code
|
|
164
|
-
if (mapFn) {
|
|
165
|
-
if (this.mapFn.toString() !== mapFn.toString()) {
|
|
166
|
-
this.mapFn = mapFn;
|
|
167
|
-
this.mapFnString = mapFn.toString();
|
|
168
|
-
mapFnChanged = true;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
// application code is creating an index
|
|
173
|
-
if (!mapFn) {
|
|
174
|
-
mapFn = ((doc) => (doc as unknown as Record<string, unknown>)[name] ?? undefined) as MapFn<T>;
|
|
175
|
-
}
|
|
176
|
-
if (this.mapFnString) {
|
|
177
|
-
// we already loaded from a header
|
|
178
|
-
if (this.mapFnString !== mapFn.toString()) {
|
|
179
|
-
mapFnChanged = true;
|
|
180
|
-
}
|
|
181
|
-
} else {
|
|
182
|
-
// we are first
|
|
183
|
-
this.mapFnString = mapFn.toString();
|
|
184
|
-
}
|
|
185
|
-
this.mapFn = mapFn;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// If the map function changed, reset the index for correctness
|
|
190
|
-
if (mapFnChanged) {
|
|
191
|
-
this._resetIndex();
|
|
192
|
-
}
|
|
193
|
-
} catch (e) {
|
|
194
|
-
this.initError = e as Error;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async query(opts: QueryOpts<K> = {}): Promise<IndexRows<T, K, R>> {
|
|
199
|
-
this.logger.Debug().Msg("enter query");
|
|
200
|
-
await this.ready();
|
|
201
|
-
// this._resetIndex();
|
|
202
|
-
this.logger.Debug().Msg("post ready query");
|
|
203
|
-
await this._updateIndex();
|
|
204
|
-
this.logger.Debug().Msg("post _updateIndex query");
|
|
205
|
-
await this._hydrateIndex();
|
|
206
|
-
this.logger.Debug().Msg("post _hydrateIndex query");
|
|
207
|
-
if (!this.byKey.root) {
|
|
208
|
-
return await applyQuery<T, K, R>(this.crdt, { result: [] }, opts);
|
|
209
|
-
}
|
|
210
|
-
if (opts.includeDocs === undefined) opts.includeDocs = true;
|
|
211
|
-
if (opts.range) {
|
|
212
|
-
const eRange = encodeRange(opts.range);
|
|
213
|
-
return await applyQuery<T, K, R>(this.crdt, await throwFalsy(this.byKey.root).range(eRange[0], eRange[1]), opts);
|
|
214
|
-
}
|
|
215
|
-
if (typeof opts.key === "boolean" || opts.key) {
|
|
216
|
-
const encodedKey = encodeKey(opts.key);
|
|
217
|
-
return await applyQuery<T, K, R>(this.crdt, await throwFalsy(this.byKey.root).get(encodedKey), opts);
|
|
218
|
-
}
|
|
219
|
-
if (Array.isArray(opts.keys)) {
|
|
220
|
-
// Create a new options object without the limit to avoid limiting individual key results
|
|
221
|
-
const optsWithoutLimit = { ...opts };
|
|
222
|
-
delete optsWithoutLimit.limit;
|
|
223
|
-
|
|
224
|
-
// Process each key separately but don't apply limit yet
|
|
225
|
-
const results = await Promise.all(
|
|
226
|
-
opts.keys.map(async (key: DocFragment) => {
|
|
227
|
-
const encodedKey = encodeKey(key);
|
|
228
|
-
return (await applyQuery<T, K, R>(this.crdt, await throwFalsy(this.byKey.root).get(encodedKey), optsWithoutLimit)).rows;
|
|
229
|
-
}),
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
// Flatten all results into a single array
|
|
233
|
-
let flattenedRows = results.flat();
|
|
234
|
-
|
|
235
|
-
// Apply the original limit to the combined results if it was specified
|
|
236
|
-
if (opts) {
|
|
237
|
-
flattenedRows = flattenedRows.slice(0, opts.limit);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
rows: flattenedRows,
|
|
242
|
-
docs: flattenedRows.map((r) => r.doc).filter((r): r is DocWithId<T> => !!r),
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
if (opts.prefix) {
|
|
246
|
-
if (!Array.isArray(opts.prefix)) opts.prefix = [opts.prefix];
|
|
247
|
-
// prefix should be always an array
|
|
248
|
-
const start = [...opts.prefix, NaN];
|
|
249
|
-
const end = [...opts.prefix, Infinity];
|
|
250
|
-
const encodedR = encodeRange([start, end]);
|
|
251
|
-
return await applyQuery<T, K, R>(this.crdt, await this.byKey.root.range(...encodedR), opts);
|
|
252
|
-
}
|
|
253
|
-
const all = await this.byKey.root.getAllEntries(); // funky return type
|
|
254
|
-
return await applyQuery<T, K, R>(
|
|
255
|
-
this.crdt,
|
|
256
|
-
{
|
|
257
|
-
// getAllEntries returns a different type than range
|
|
258
|
-
result: all.result.map(({ key: [k, id], value }) => ({
|
|
259
|
-
key: k as [K, string],
|
|
260
|
-
id,
|
|
261
|
-
value,
|
|
262
|
-
})),
|
|
263
|
-
},
|
|
264
|
-
opts,
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
_resetIndex() {
|
|
269
|
-
this.byId = {};
|
|
270
|
-
this.byKey = {};
|
|
271
|
-
this.indexHead = undefined;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async _hydrateIndex() {
|
|
275
|
-
if (this.byId.root && this.byKey.root) return;
|
|
276
|
-
if (!this.byId.cid || !this.byKey.cid) return;
|
|
277
|
-
this.byId.root = await loadIndex<K, R, string | number>(this.blockstore, this.byId.cid, byIdOpts);
|
|
278
|
-
this.byKey.root = await loadIndex<K, R, CompareKey>(this.blockstore, this.byKey.cid, byKeyOpts);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async _updateIndex(): Promise<IndexTransactionMeta> {
|
|
282
|
-
await this.ready();
|
|
283
|
-
this.logger.Debug().Msg("enter _updateIndex");
|
|
284
|
-
if (this.initError) throw this.initError;
|
|
285
|
-
if (!this.mapFn) throw this.logger.Error().Msg("No map function defined").AsError();
|
|
286
|
-
let result: DocUpdate<T>[], head: ClockHead;
|
|
287
|
-
if (!this.indexHead || this.indexHead.length === 0) {
|
|
288
|
-
({ result, head } = await this.crdt.allDocs<T>());
|
|
289
|
-
this.logger.Debug().Msg("enter crdt.allDocs");
|
|
290
|
-
} else {
|
|
291
|
-
({ result, head } = await this.crdt.changes<T>(this.indexHead));
|
|
292
|
-
this.logger.Debug().Msg("enter crdt.changes");
|
|
293
|
-
}
|
|
294
|
-
if (result.length === 0) {
|
|
295
|
-
this.indexHead = head;
|
|
296
|
-
// return { byId: this.byId, byKey: this.byKey } as IndexTransactionMeta;
|
|
297
|
-
}
|
|
298
|
-
let staleKeyIndexEntries: IndexUpdate<K>[] = [];
|
|
299
|
-
let removeIdIndexEntries: IndexUpdateString[] = [];
|
|
300
|
-
if (this.byId.root) {
|
|
301
|
-
const removeIds = result.map(({ id: key }) => key);
|
|
302
|
-
const { result: oldChangeEntries } = await this.byId.root.getMany(removeIds);
|
|
303
|
-
staleKeyIndexEntries = oldChangeEntries.map((key) => ({ key, del: true }));
|
|
304
|
-
removeIdIndexEntries = oldChangeEntries.map((key) => ({ key: key[1], del: true }));
|
|
305
|
-
}
|
|
306
|
-
const indexEntries = indexEntriesForChanges<T, K>(result, this.mapFn); // use a getter to translate from string
|
|
307
|
-
const byIdIndexEntries: IndexDocString[] = indexEntries.map(({ key }) => ({
|
|
308
|
-
key: key[1],
|
|
309
|
-
value: key,
|
|
310
|
-
}));
|
|
311
|
-
const indexerMeta: IdxMetaMap = { indexes: new Map() };
|
|
312
|
-
|
|
313
|
-
for (const [name, indexer] of this.crdt.indexers) {
|
|
314
|
-
if (indexer.indexHead) {
|
|
315
|
-
indexerMeta.indexes?.set(name, {
|
|
316
|
-
byId: indexer.byId.cid,
|
|
317
|
-
byKey: indexer.byKey.cid,
|
|
318
|
-
head: indexer.indexHead,
|
|
319
|
-
map: indexer.mapFnString,
|
|
320
|
-
name: indexer.name,
|
|
321
|
-
} as IdxMeta);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if (result.length === 0) {
|
|
325
|
-
return indexerMeta as unknown as IndexTransactionMeta;
|
|
326
|
-
}
|
|
327
|
-
this.logger.Debug().Msg("pre this.blockstore.transaction");
|
|
328
|
-
const { meta } = await this.blockstore.transaction<IndexTransactionMeta>(async (tblocks): Promise<IndexTransactionMeta> => {
|
|
329
|
-
this.byId = await bulkIndex<K, R, string | number>(
|
|
330
|
-
this.logger,
|
|
331
|
-
tblocks,
|
|
332
|
-
this.byId,
|
|
333
|
-
removeIdIndexEntries.concat(byIdIndexEntries),
|
|
334
|
-
byIdOpts,
|
|
335
|
-
);
|
|
336
|
-
this.byKey = await bulkIndex<K, R, CompareKey>(
|
|
337
|
-
this.logger,
|
|
338
|
-
tblocks,
|
|
339
|
-
this.byKey,
|
|
340
|
-
staleKeyIndexEntries.concat(indexEntries),
|
|
341
|
-
byKeyOpts,
|
|
342
|
-
);
|
|
343
|
-
this.indexHead = head;
|
|
344
|
-
if (this.byId.cid && this.byKey.cid) {
|
|
345
|
-
const idxMeta = {
|
|
346
|
-
byId: this.byId.cid,
|
|
347
|
-
byKey: this.byKey.cid,
|
|
348
|
-
head,
|
|
349
|
-
map: this.mapFnString,
|
|
350
|
-
name: this.name,
|
|
351
|
-
} as IdxMeta;
|
|
352
|
-
indexerMeta.indexes?.set(this.name, idxMeta);
|
|
353
|
-
}
|
|
354
|
-
this.logger.Debug().Any("indexerMeta", new Array(indexerMeta.indexes?.entries())).Msg("exit this.blockstore.transaction fn");
|
|
355
|
-
return indexerMeta as unknown as IndexTransactionMeta;
|
|
356
|
-
});
|
|
357
|
-
this.logger.Debug().Msg("post this.blockstore.transaction");
|
|
358
|
-
return meta;
|
|
359
|
-
}
|
|
360
|
-
}
|