@bodil/bdb 0.1.2 → 0.2.0

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/index.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import type { OrderFn } from "@bodil/core/order";
2
2
  import BTree from "sorted-btree";
3
3
 
4
- import { ArrayIndex, CompoundIndex, CustomIndex, PrimitiveIndex, type UnitIndex } from "./indices";
5
- import { Table } from "./table";
4
+ import { ArrayIndex, CompoundIndex, CustomIndex, PrimitiveIndex } from "./indices";
6
5
  import type { ArrayIndexablesOf, CustomIndexablesOf, PrimitiveIndexablesOf } from "./types";
7
6
 
8
7
  export type {
@@ -19,58 +18,126 @@ export type {
19
18
  CustomIndexablesOf,
20
19
  PrimitiveIndexablesOf,
21
20
  } from "./types";
22
- export type { Table, TableEvent } from "./table";
21
+ export { Table, type TableEvent } from "./table";
23
22
  export type { IndexQuery, ArrayQuery, ChainQuery } from "./query";
24
23
  export type { Broadcaster } from "./broadcast";
25
24
  export type { IndexedDBBackendConfig, DatabaseBroadcast } from "./backend";
26
25
 
27
26
  export { StorageBackend, IndexedDBBackend } from "./backend";
28
27
 
29
- export function createTable<A extends object>(): <PI extends UnitIndex<A>>(
30
- primaryIndex: PI,
31
- ) => Table<A, PI, PI["record"]> {
32
- return (primaryIndex) => new Table(primaryIndex);
33
- }
28
+ /**
29
+ * Constructor functions for creating an {@link index}.
30
+ * @interface
31
+ */
32
+ export type IndexConstructor<Document extends object> = {
33
+ /**
34
+ * Create an index for a single property on a document.
35
+ *
36
+ * The type of the property needs to match {@link IndexablePrimitive}, ie.
37
+ * it needs to be a string, a number or a bigint. If you need an index for a
38
+ * different value type, use {@link IndexConstructor.custom}.
39
+ *
40
+ * @example
41
+ * type Document = { id: string; value: number };
42
+ * const index = index<Document>().key("id");
43
+ */
44
+ key: <I extends PrimitiveIndexablesOf<Document>>(key: I) => PrimitiveIndex<Document, I>;
34
45
 
35
- export function index<A extends object>(): <I extends PrimitiveIndexablesOf<A>>(
36
- key: I,
37
- ) => PrimitiveIndex<A, I> {
38
- return (key) => new PrimitiveIndex(key);
39
- }
46
+ /**
47
+ * Create a compound index for a pair of properties on a document.
48
+ *
49
+ * The types of the properties need to match {@link IndexablePrimitive}, ie.
50
+ * they need to be strings, numbers or bigints.
51
+ *
52
+ * This index matches documents where both properties match the provided
53
+ * pair of values. Partial matches do not count.
54
+ *
55
+ * Whemn a compound index is used as a primary index, the unique key is the
56
+ * value pair, not either of the individual values, so you can have multiple
57
+ * documents with either one of the properties having identical values, but
58
+ * only one document matching any one given value pair.
59
+ *
60
+ * @example
61
+ * type Document = { id: string; value: number };
62
+ * const compoundIndex = index<Document>().keys("id", "value");
63
+ */
64
+ keys: <
65
+ I extends PrimitiveIndexablesOf<Document>,
66
+ J extends Exclude<PrimitiveIndexablesOf<Document>, I>,
67
+ >(
68
+ leftKey: I,
69
+ rightKey: J,
70
+ ) => CompoundIndex<Document, I, J>;
40
71
 
41
- export function timeIndex<A extends object>(): <I extends CustomIndexablesOf<A, Temporal.Instant>>(
42
- key: I,
43
- ) => CustomIndex<A, Temporal.Instant, I> {
44
- return (key) =>
45
- new CustomIndex(
46
- key,
47
- () => new BTree(undefined, Temporal.Instant.compare as OrderFn<A[typeof key]>),
48
- );
49
- }
72
+ /**
73
+ * Create an array index for a property on a document.
74
+ *
75
+ * This index works on a property with an array of
76
+ * {@link IndexablePrimitive}s. It will match documents where the lookup
77
+ * value is a member of the array.
78
+ *
79
+ * @example
80
+ * type Document = { id: string; flags: Array<string> };
81
+ * const arrayIndex = index<Document>().array("flags");
82
+ */
83
+ array: <I extends ArrayIndexablesOf<Document>, L extends Document[I] & Array<unknown>>(
84
+ key: I,
85
+ ) => ArrayIndex<Document, I, L>;
50
86
 
51
- export function arrayIndex<A extends object>(): <
52
- I extends ArrayIndexablesOf<A>,
53
- L extends A[I] & Array<unknown>,
54
- >(
55
- key: I,
56
- ) => ArrayIndex<A, I, L> {
57
- return (key) => new ArrayIndex(key);
58
- }
87
+ /**
88
+ * Create an index for a {@link Temporal.Instant} property on a document.
89
+ *
90
+ * This works exactly like {@link IndexConstructor.key}, except that it
91
+ * takes a {@link Temporal.Instant} instead of a primitive value.
92
+ */
93
+ time: <I extends CustomIndexablesOf<Document, Temporal.Instant>>(
94
+ key: I,
95
+ ) => CustomIndex<Document, Temporal.Instant, I>;
59
96
 
60
- export function compoundIndex<A extends object>(): <
61
- I extends PrimitiveIndexablesOf<A>,
62
- J extends Exclude<PrimitiveIndexablesOf<A>, I>,
63
- >(
64
- leftKey: I,
65
- rightKey: J,
66
- ) => CompoundIndex<A, I, J> {
67
- return (leftKey, rightKey) => new CompoundIndex(leftKey, rightKey);
68
- }
97
+ /**
98
+ * Create an index with a custom ordering function for a property on a
99
+ * document.
100
+ *
101
+ * This works like {@link IndexConstructor.key}, except that it takes any
102
+ * value rather than just a primitive, as long as you provide an
103
+ * {@link OrderFn | ordering function} for it.
104
+ */
105
+ custom: <T, I extends CustomIndexablesOf<Document, T>>(
106
+ key: I,
107
+ orderFn: OrderFn<T>,
108
+ ) => CustomIndex<Document, T, I>;
109
+ };
110
+
111
+ const indexConstructor = Symbol("indexConstructor");
69
112
 
70
- export function customIndex<A extends object, T>(): <I extends CustomIndexablesOf<A, T>>(
71
- key: I,
72
- orderFn: OrderFn<T>,
73
- ) => CustomIndex<A, T, I> {
74
- return (key, orderFn: OrderFn<T>) =>
75
- new CustomIndex(key, () => new BTree(undefined, orderFn as OrderFn<A[typeof key]>));
113
+ /**
114
+ * Create an index.
115
+ *
116
+ * This function takes a document type as its type argument, and returns an
117
+ * object with a selection of {@link IndexConstructor}s which allow you to
118
+ * create an index for the given document type.
119
+ *
120
+ * @example
121
+ * type Document = { id: string; timestamp: Temporal.Instant };
122
+ * const idIndex = index<Document>().key("id");
123
+ * const timeIndex = index<Document>().time("timestamp");
124
+ */
125
+ export function index<Document extends object>(): IndexConstructor<Document> {
126
+ (index as any)[indexConstructor] ??= {
127
+ key: (key) => new PrimitiveIndex(key),
128
+ keys: (leftKey, rightKey) => new CompoundIndex(leftKey, rightKey),
129
+ array: (key) => new ArrayIndex(key),
130
+ time: (key) =>
131
+ new CustomIndex(
132
+ key,
133
+ () =>
134
+ new BTree(undefined, Temporal.Instant.compare as OrderFn<Document[typeof key]>),
135
+ ),
136
+ custom: (key, orderFn) =>
137
+ new CustomIndex(
138
+ key,
139
+ () => new BTree(undefined, orderFn as OrderFn<Document[typeof key]>),
140
+ ),
141
+ } as IndexConstructor<Document>;
142
+ return (index as any)[indexConstructor] as IndexConstructor<Document>;
76
143
  }
package/src/indices.ts CHANGED
@@ -2,6 +2,7 @@ import type BTree from "sorted-btree";
2
2
 
3
3
  import type { ArrayIndexablesOf, CustomIndexablesOf, PrimitiveIndexablesOf } from "./types";
4
4
 
5
+ /** @internal */
5
6
  export abstract class Index<A extends object> {
6
7
  readonly name!: string;
7
8
  readonly keyType!: unknown;
@@ -9,10 +10,12 @@ export abstract class Index<A extends object> {
9
10
  abstract extractKeys(value: A): Array<typeof this.keyType>;
10
11
  }
11
12
 
13
+ /** @internal */
12
14
  export abstract class UnitIndex<A extends object> extends Index<A> {
13
15
  abstract extractKey(value: A): typeof this.keyType;
14
16
  }
15
17
 
18
+ /** @internal */
16
19
  export class CustomIndex<A extends object, T, I extends CustomIndexablesOf<A, T>>
17
20
  implements Index<A>, UnitIndex<A>
18
21
  {
@@ -37,6 +40,7 @@ export class CustomIndex<A extends object, T, I extends CustomIndexablesOf<A, T>
37
40
  }
38
41
  }
39
42
 
43
+ /** @internal */
40
44
  export class PrimitiveIndex<A extends object, I extends PrimitiveIndexablesOf<A>>
41
45
  implements Index<A>, UnitIndex<A>
42
46
  {
@@ -58,12 +62,12 @@ export class PrimitiveIndex<A extends object, I extends PrimitiveIndexablesOf<A>
58
62
  }
59
63
  }
60
64
 
65
+ /** @internal */
61
66
  export class ArrayIndex<
62
67
  A extends object,
63
68
  I extends ArrayIndexablesOf<A>,
64
69
  L extends A[I] & Array<unknown>,
65
- > implements Index<A>
66
- {
70
+ > implements Index<A> {
67
71
  readonly index: I;
68
72
  readonly name: `*${I}`;
69
73
  readonly keyType!: L[number];
@@ -79,11 +83,12 @@ export class ArrayIndex<
79
83
  }
80
84
  }
81
85
 
86
+ /** @internal */
82
87
  export class CompoundIndex<
83
- A extends object,
84
- I extends PrimitiveIndexablesOf<A>,
85
- J extends Exclude<PrimitiveIndexablesOf<A>, I>,
86
- >
88
+ A extends object,
89
+ I extends PrimitiveIndexablesOf<A>,
90
+ J extends Exclude<PrimitiveIndexablesOf<A>, I>,
91
+ >
87
92
  implements Index<A>, UnitIndex<A>
88
93
  {
89
94
  readonly leftIndex: I;
package/src/query.ts CHANGED
@@ -9,29 +9,25 @@ import type { Table } from "./table";
9
9
  export enum IteratorDirection {
10
10
  Ascending = 0,
11
11
  Descending = 1,
12
- DescendingExclusive = 2,
13
12
  }
14
13
 
15
- function* indexIterator<A extends object, I extends Index<A>>(
16
- table: BTree<I["keyType"], Array<A>>,
14
+ function* indexIterator<Document extends object, I extends Index<Document>>(
15
+ table: BTree<I["keyType"], Array<Document>>,
17
16
  direction: IteratorDirection,
18
17
  start?: I["keyType"],
19
- ): Generator<Readonly<A>> {
18
+ skipStart = false,
19
+ ): Generator<Readonly<Document>> {
20
20
  if (table.isEmpty) {
21
21
  return;
22
22
  }
23
- if (direction === IteratorDirection.Ascending) {
24
- for (const items of table.values(start)) {
25
- for (const item of items) {
26
- yield item;
27
- }
28
- }
29
- } else {
30
- for (const entry of table.entriesReversed(
31
- start,
32
- undefined,
33
- direction === IteratorDirection.DescendingExclusive,
34
- )) {
23
+ const entries =
24
+ direction === IteratorDirection.Ascending
25
+ ? table.entries(start)
26
+ : table.entriesReversed(start);
27
+ for (const entry of entries) {
28
+ if (skipStart && table._compare(entry[0], start) === 0) {
29
+ skipStart = false;
30
+ } else {
35
31
  for (const item of entry[1]) {
36
32
  yield item;
37
33
  }
@@ -39,94 +35,100 @@ function* indexIterator<A extends object, I extends Index<A>>(
39
35
  }
40
36
  }
41
37
 
42
- function* primaryIndexIterator<A extends object, I extends UnitIndex<A>>(
43
- table: BTree<I["keyType"], Readonly<A>>,
38
+ function* primaryIndexIterator<Document extends object, I extends UnitIndex<Document>>(
39
+ table: BTree<I["keyType"], Readonly<Document>>,
44
40
  direction: IteratorDirection,
45
41
  start?: I["keyType"],
46
- ): Generator<Readonly<A>> {
42
+ skipStart = false,
43
+ ): Generator<Readonly<Document>> {
47
44
  if (table.isEmpty) {
48
45
  return;
49
46
  }
50
- if (direction === IteratorDirection.Ascending) {
51
- for (const item of table.values(start)) {
52
- yield item;
53
- }
54
- } else {
55
- for (const entry of table.entriesReversed(
56
- start,
57
- undefined,
58
- direction === IteratorDirection.DescendingExclusive,
59
- )) {
47
+ const entries =
48
+ direction === IteratorDirection.Ascending
49
+ ? table.entries(start)
50
+ : table.entriesReversed(start);
51
+ for (const entry of entries) {
52
+ if (skipStart && table._compare(entry[0], start) === 0) {
53
+ skipStart = false;
54
+ } else {
60
55
  yield entry[1];
61
56
  }
62
57
  }
63
58
  }
64
59
 
65
- export abstract class Query<A extends object> implements Iterable<Readonly<A>> {
66
- abstract [Symbol.iterator](): IterableIterator<Readonly<A>>;
67
- abstract signal(): Signal.Computed<Array<Readonly<A>>>;
60
+ export abstract class Query<Document extends object> implements Iterable<Readonly<Document>> {
61
+ abstract [Symbol.iterator](): IterableIterator<Readonly<Document>>;
62
+ abstract signal(): Signal.Computed<Array<Readonly<Document>>>;
68
63
 
69
- map<B>(mapFn: (item: Readonly<A>) => B): IterableIterator<B> {
64
+ map<B>(mapFn: (item: Readonly<Document>) => B): IterableIterator<B> {
70
65
  return Iterator.from(this).map(mapFn);
71
66
  }
72
67
 
73
- forEach(action: (item: A) => void): void {
68
+ forEach(action: (item: Document) => void): void {
74
69
  for (const item of this) {
75
70
  action(item);
76
71
  }
77
72
  }
78
73
 
79
- toArray(): Array<Readonly<A>> {
74
+ toArray(): Array<Readonly<Document>> {
80
75
  return Array.from(this);
81
76
  }
82
77
  }
83
78
 
84
79
  export abstract class TableQuery<
85
- A extends object,
86
- PI extends UnitIndex<A>,
80
+ Document extends object,
81
+ PI extends UnitIndex<Document>,
87
82
  Ix extends object,
88
- > extends Query<A> {
89
- table: Table<A, PI, Ix>;
83
+ > extends Query<Document> {
84
+ /** @ignore */
85
+ protected table: Table<Document, PI, Ix>;
90
86
 
91
- constructor(table: Table<A, PI, Ix>) {
87
+ constructor(table: Table<Document, PI, Ix>) {
92
88
  super();
93
89
  this.table = table;
94
90
  }
95
91
 
96
- signal(): Signal.Computed<Array<Readonly<A>>> {
92
+ signal(): Signal.Computed<Array<Readonly<Document>>> {
97
93
  return Signal.computed(() => {
98
94
  this.table.changed.get();
99
95
  return Array.from(this);
100
96
  });
101
97
  }
102
98
 
99
+ /**
100
+ * Delete all documents from the table matching this query.
101
+ */
103
102
  delete(): number {
104
- return this.table.delete(...Array.from(this));
103
+ return this.table.delete(
104
+ ...Array.from(this).map((doc) => this.table.primaryIndex.extractKey(doc)),
105
+ );
105
106
  }
106
107
 
107
- filter(predicate: (item: Readonly<A>) => boolean): ChainQuery<A, PI, Ix> {
108
+ filter(predicate: (item: Readonly<Document>) => boolean): ChainQuery<Document, PI, Ix> {
108
109
  return new ChainQuery(this.table, this, (iter) => Iterator.from(iter).filter(predicate));
109
110
  }
110
111
 
111
- limit(count: number): ChainQuery<A, PI, Ix> {
112
+ limit(count: number): ChainQuery<Document, PI, Ix> {
112
113
  return new ChainQuery(this.table, this, (iter) => Iterator.from(iter).take(count));
113
114
  }
114
115
  }
115
116
 
116
117
  export class IndexQuery<
117
- A extends object,
118
- PI extends UnitIndex<A>,
119
- I extends Index<A>,
118
+ Document extends object,
119
+ PI extends UnitIndex<Document>,
120
+ I extends Index<Document>,
120
121
  Ix extends object,
121
- > extends TableQuery<A, PI, Ix> {
122
+ > extends TableQuery<Document, PI, Ix> {
122
123
  private readonly index: I;
123
- private readonly indexTable?: BTree<I["keyType"], Array<A>>;
124
+ private readonly indexTable?: BTree<I["keyType"], Array<Document>>;
124
125
  private readonly isPrimary: boolean;
125
126
  private start?: I["keyType"];
127
+ private skipStart = false;
126
128
  private direction: IteratorDirection;
127
129
 
128
130
  constructor(
129
- table: Table<A, PI, Ix>,
131
+ table: Table<Document, PI, Ix>,
130
132
  indexKey: keyof Ix & string,
131
133
  direction: IteratorDirection,
132
134
  start?: I["keyType"],
@@ -144,10 +146,10 @@ export class IndexQuery<
144
146
  this.start = start;
145
147
  }
146
148
 
147
- [Symbol.iterator](): IterableIterator<Readonly<A>> {
149
+ [Symbol.iterator](): IterableIterator<Readonly<Document>> {
148
150
  return this.isPrimary
149
- ? primaryIndexIterator(this.table.primary, this.direction, this.start)
150
- : indexIterator(this.indexTable!, this.direction, this.start);
151
+ ? primaryIndexIterator(this.table.primary, this.direction, this.start, this.skipStart)
152
+ : indexIterator(this.indexTable!, this.direction, this.start, this.skipStart);
151
153
  }
152
154
 
153
155
  reverse(): this {
@@ -158,12 +160,10 @@ export class IndexQuery<
158
160
  case IteratorDirection.Descending:
159
161
  this.direction = IteratorDirection.Ascending;
160
162
  return this;
161
- case IteratorDirection.DescendingExclusive:
162
- throw new Error("not sure how to reverse IteratorDirection.DescendingExclusive");
163
163
  }
164
164
  }
165
165
 
166
- equals(value: I["keyType"]): ArrayQuery<A, PI, Ix> {
166
+ equals(value: I["keyType"]): ArrayQuery<Document, PI, Ix> {
167
167
  if (this.isPrimary) {
168
168
  const item = this.table.get(value);
169
169
  return new ArrayQuery(this.table, item === undefined ? [] : [item]);
@@ -173,7 +173,20 @@ export class IndexQuery<
173
173
 
174
174
  below(value: I["keyType"]): this {
175
175
  this.start = value;
176
- this.direction = IteratorDirection.DescendingExclusive;
176
+ this.direction = IteratorDirection.Descending;
177
+ this.skipStart = true;
178
+ return this;
179
+ }
180
+
181
+ above(value: I["keyType"]): this {
182
+ this.start = value;
183
+ this.direction = IteratorDirection.Ascending;
184
+ this.skipStart = true;
185
+ return this;
186
+ }
187
+
188
+ inclusive(): this {
189
+ this.skipStart = false;
177
190
  return this;
178
191
  }
179
192
 
@@ -191,43 +204,45 @@ export class IndexQuery<
191
204
  }
192
205
 
193
206
  export class ChainQuery<
194
- A extends object,
195
- PI extends UnitIndex<A>,
207
+ Document extends object,
208
+ PI extends UnitIndex<Document>,
196
209
  Ix extends object,
197
- > extends TableQuery<A, PI, Ix> {
198
- private readonly parent: Query<A>;
210
+ > extends TableQuery<Document, PI, Ix> {
211
+ private readonly parent: Query<Document>;
199
212
  private readonly iterator: (
200
- parentIterator: IterableIterator<Readonly<A>>,
201
- ) => IterableIterator<Readonly<A>>;
213
+ parentIterator: IterableIterator<Readonly<Document>>,
214
+ ) => IterableIterator<Readonly<Document>>;
202
215
 
203
216
  constructor(
204
- table: Table<A, PI, Ix>,
205
- parent: Query<A>,
206
- iterator: (parentIterator: IterableIterator<Readonly<A>>) => IterableIterator<Readonly<A>>,
217
+ table: Table<Document, PI, Ix>,
218
+ parent: Query<Document>,
219
+ iterator: (
220
+ parentIterator: IterableIterator<Readonly<Document>>,
221
+ ) => IterableIterator<Readonly<Document>>,
207
222
  ) {
208
223
  super(table);
209
224
  this.parent = parent;
210
225
  this.iterator = iterator;
211
226
  }
212
227
 
213
- [Symbol.iterator](): IterableIterator<Readonly<A>> {
228
+ [Symbol.iterator](): IterableIterator<Readonly<Document>> {
214
229
  return this.iterator(this.parent[Symbol.iterator]());
215
230
  }
216
231
  }
217
232
 
218
233
  export class ArrayQuery<
219
- A extends object,
220
- PI extends UnitIndex<A>,
234
+ Document extends object,
235
+ PI extends UnitIndex<Document>,
221
236
  Ix extends object,
222
- > extends TableQuery<A, PI, Ix> {
223
- private readonly values: Array<Readonly<A>>;
237
+ > extends TableQuery<Document, PI, Ix> {
238
+ private readonly values: Array<Readonly<Document>>;
224
239
 
225
- constructor(table: Table<A, PI, Ix>, values: Array<Readonly<A>>) {
240
+ constructor(table: Table<Document, PI, Ix>, values: Array<Readonly<Document>>) {
226
241
  super(table);
227
242
  this.values = values;
228
243
  }
229
244
 
230
- [Symbol.iterator](): IterableIterator<Readonly<A>> {
245
+ [Symbol.iterator](): IterableIterator<Readonly<Document>> {
231
246
  return this.values[Symbol.iterator]();
232
247
  }
233
248
  }