@fireproof/core-base 0.22.0-keybag → 0.23.1-dev-issue-1057

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/database.ts DELETED
@@ -1,200 +0,0 @@
1
- import { Logger } from "@adviser/cement";
2
- import { index } from "./indexer.js";
3
- import {
4
- ClockHead,
5
- MapFn,
6
- QueryOpts,
7
- ChangesOptions,
8
- DocSet,
9
- DocWithId,
10
- IndexKeyType,
11
- ListenerFn,
12
- DocResponse,
13
- BulkResponse,
14
- ChangesResponse,
15
- DocTypes,
16
- IndexRows,
17
- DocFragment,
18
- ChangesResponseRow,
19
- CRDTMeta,
20
- AllDocsQueryOpts,
21
- AllDocsResponse,
22
- SuperThis,
23
- Database,
24
- Ledger,
25
- Attachable,
26
- Attached,
27
- NotFoundError,
28
- } from "@fireproof/core-types-base";
29
- import { ensureLogger, makeName } from "@fireproof/core-runtime";
30
-
31
- export function isDatabase(db: unknown): db is Database {
32
- return db instanceof DatabaseImpl;
33
- }
34
-
35
- export class DatabaseImpl implements Database {
36
- onClosed(fn: () => void) {
37
- this.ledger.onClosed(fn);
38
- }
39
- close() {
40
- return this.ledger.close();
41
- }
42
-
43
- destroy() {
44
- return this.ledger.destroy();
45
- }
46
-
47
- ready(): Promise<void> {
48
- return this.ledger.ready();
49
- }
50
-
51
- readonly ledger: Ledger;
52
- readonly logger: Logger;
53
- readonly sthis: SuperThis;
54
- // readonly id: string;
55
-
56
- constructor(ledger: Ledger) {
57
- this.sthis = ledger.sthis;
58
- this.ledger = ledger;
59
- // this.id = ledger.id;
60
- this.logger = ensureLogger(this.sthis, "Database");
61
- }
62
-
63
- attach(a: Attachable): Promise<Attached> {
64
- return this.ledger.attach(a);
65
- }
66
-
67
- get name() {
68
- return this.ledger.name;
69
- }
70
- async get<T extends DocTypes>(id: string): Promise<DocWithId<T>> {
71
- if (!id) throw this.logger.Error().Str("db", this.name).Msg(`Doc id is required`).AsError();
72
-
73
- await this.ready();
74
- this.logger.Debug().Str("id", id).Msg("get");
75
- try {
76
- const got = await this.ledger.crdt.get(id);
77
- if (!got) throw new NotFoundError(`Not found: ${id}`);
78
- const { doc } = got;
79
- return { ...(doc as unknown as DocWithId<T>), _id: id };
80
- } catch (e) {
81
- throw new NotFoundError(`Not found: ${id} - ${e instanceof Error ? e.message : String(e)}`);
82
- }
83
- }
84
-
85
- async put<T extends DocTypes>(doc: DocSet<T>): Promise<DocResponse> {
86
- await this.ready();
87
- this.logger.Debug().Str("id", doc._id).Msg("put");
88
- const { _id, ...value } = doc;
89
- const docId = _id || this.sthis.timeOrderedNextId().str;
90
- const result = (await this.ledger.writeQueue.push({
91
- id: docId,
92
- value: {
93
- ...(value as unknown as DocSet<T>),
94
- _id: docId,
95
- },
96
- })) as CRDTMeta;
97
- return { id: docId, clock: result?.head, name: this.name } as DocResponse;
98
- }
99
-
100
- async bulk<T extends DocTypes>(docs: DocSet<T>[]): Promise<BulkResponse> {
101
- await this.ready();
102
-
103
- const updates = docs.map((doc) => {
104
- const id = doc._id || this.sthis.timeOrderedNextId().str;
105
- return {
106
- id,
107
- value: {
108
- ...(doc as unknown as DocSet<T>),
109
- _id: id,
110
- },
111
- };
112
- });
113
- const result = (await this.ledger.writeQueue.bulk(updates)) as CRDTMeta;
114
- return { ids: updates.map((u) => u.id), clock: result.head, name: this.name } as BulkResponse;
115
- }
116
-
117
- async del(id: string): Promise<DocResponse> {
118
- await this.ready();
119
- this.logger.Debug().Str("id", id).Msg("del");
120
- const result = (await this.ledger.writeQueue.push({ id: id, del: true })) as CRDTMeta;
121
- return { id, clock: result?.head, name: this.name } as DocResponse;
122
- }
123
-
124
- async remove(id: string): Promise<DocResponse> {
125
- return this.del(id);
126
- }
127
-
128
- async changes<T extends DocTypes>(since: ClockHead = [], opts: ChangesOptions = {}): Promise<ChangesResponse<T>> {
129
- await this.ready();
130
- this.logger.Debug().Any("since", since).Any("opts", opts).Msg("changes");
131
- const { result, head } = await this.ledger.crdt.changes(since, opts);
132
- const rows: ChangesResponseRow<T>[] = result.map(({ id: key, value, del, clock }) => ({
133
- key,
134
- value: (del ? { _id: key, _deleted: true } : { _id: key, ...value }) as DocWithId<T>,
135
- clock,
136
- }));
137
- return { rows, clock: head, name: this.name };
138
- }
139
-
140
- async allDocs<T extends DocTypes>(opts: Partial<AllDocsQueryOpts> = {}): Promise<AllDocsResponse<T>> {
141
- await this.ready();
142
- this.logger.Debug().Msg("allDocs");
143
- const { result, head } = await this.ledger.crdt.allDocs();
144
-
145
- // Map all docs to the expected format
146
- let rows = result.map(({ id: key, value, del }) => ({
147
- key,
148
- value: (del ? { _id: key, _deleted: true } : { _id: key, ...value }) as DocWithId<T>,
149
- }));
150
-
151
- // Filter out deleted documents unless includeDeleted is true
152
- if (!opts.includeDeleted) {
153
- rows = rows.filter((row) => !(row.value as DocWithId<T> & { _deleted?: boolean })._deleted);
154
- }
155
-
156
- // Apply limit if specified
157
- if (typeof opts.limit === "number" && opts.limit >= 0) {
158
- rows = rows.slice(0, opts.limit);
159
- }
160
-
161
- return { rows, clock: head, name: this.name };
162
- }
163
-
164
- async allDocuments<T extends DocTypes>(): Promise<{
165
- rows: {
166
- key: string;
167
- value: DocWithId<T>;
168
- }[];
169
- clock: ClockHead;
170
- }> {
171
- return this.allDocs<T>();
172
- }
173
-
174
- subscribe<T extends DocTypes>(listener: ListenerFn<T>, updates?: boolean): () => void {
175
- return this.ledger.subscribe(listener, updates);
176
- }
177
-
178
- // todo if we add this onto dbs in fireproof.ts then we can make index.ts a separate package
179
- async query<T extends DocTypes, K extends IndexKeyType = string, R extends DocFragment = T>(
180
- field: string | MapFn<T>,
181
- opts: QueryOpts<K> = {},
182
- ): Promise<IndexRows<T, K, R>> {
183
- await this.ready();
184
- this.logger.Debug().Any("field", field).Any("opts", opts).Msg("query");
185
- // const _crdt = this.ledger.crdt as unknown as CRDT<T>;
186
- const idx = typeof field === "string" ? index<T, K, R>(this, field) : index<T, K, R>(this, makeName(field.toString()), field);
187
- const result = await idx.query(opts);
188
-
189
- // Add docs property to match useLiveQuery behavior
190
- return {
191
- rows: result.rows,
192
- docs: result.rows.map((r) => r.doc).filter((r): r is DocWithId<T> => !!r),
193
- };
194
- }
195
-
196
- async compact() {
197
- await this.ready();
198
- await this.ledger.crdt.compact();
199
- }
200
- }
package/index.ts DELETED
@@ -1,15 +0,0 @@
1
- export * from "./ledger.js";
2
- export * from "./database.js";
3
-
4
- export * from "./crdt.js";
5
-
6
- export * from "./indexer.js";
7
- export * from "./indexer-helpers.js";
8
-
9
- export * from "./version.js";
10
-
11
- import "./compact-strategies.js";
12
-
13
- export { getCompactStrategy, registerCompactStrategy, getCompactStrategyThrow } from "@fireproof/core-runtime";
14
-
15
- export { registerStoreProtocol } from "@fireproof/core-blockstore";
@@ -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
- // }