@bodil/bdb 0.1.3 → 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/README.md +28 -7
- package/dist/backend.d.ts +40 -0
- package/dist/backend.js +31 -0
- package/dist/backend.js.map +1 -1
- package/dist/index.d.ts +80 -9
- package/dist/index.js +22 -17
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +37 -15
- package/dist/index.test.js.map +1 -1
- package/dist/indices.d.ts +6 -0
- package/dist/indices.js +6 -0
- package/dist/indices.js.map +1 -1
- package/dist/query.d.ts +26 -22
- package/dist/query.js +4 -1
- package/dist/query.js.map +1 -1
- package/dist/table.d.ts +152 -31
- package/dist/table.js +145 -13
- package/dist/table.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/backend.ts +40 -0
- package/src/index.test.ts +43 -17
- package/src/index.ts +112 -45
- package/src/indices.ts +11 -6
- package/src/query.ts +53 -45
- package/src/table.ts +224 -64
- package/src/types.ts +1 -1
package/src/backend.ts
CHANGED
|
@@ -4,9 +4,18 @@ import type { IndexablePrimitive } from "./types";
|
|
|
4
4
|
|
|
5
5
|
export type DatabaseBroadcast = { event: "update" | "delete"; table: string; key: unknown };
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for IndexedDB backends.
|
|
9
|
+
*/
|
|
7
10
|
export type IndexedDBBackendConfig = {
|
|
11
|
+
/** Name of the IndexedDB database to connect to. */
|
|
8
12
|
name: string;
|
|
13
|
+
/** Version number of the database. */
|
|
9
14
|
version: number;
|
|
15
|
+
/**
|
|
16
|
+
* Name of the IndexedDB database table to store our database in. Defaults
|
|
17
|
+
* to `"store"`.
|
|
18
|
+
*/
|
|
10
19
|
table?: string;
|
|
11
20
|
};
|
|
12
21
|
|
|
@@ -27,6 +36,9 @@ function unpackRecord<A = unknown>(record: StorageRecord): A {
|
|
|
27
36
|
return deserialise(record.value) as A;
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Base class for storage backends.
|
|
41
|
+
*/
|
|
30
42
|
export abstract class StorageBackend {
|
|
31
43
|
readonly name: string;
|
|
32
44
|
|
|
@@ -49,12 +61,36 @@ export abstract class StorageBackend {
|
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Storage backend using an IndexedDB database.
|
|
66
|
+
*
|
|
67
|
+
* Use {@link IndexedDBBackend.open} to obtain a storage backend connected to
|
|
68
|
+
* the specified IndexedDB database.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Connect to the IndexedDB database.
|
|
72
|
+
* const storage = await IndexedDBBackend.open("my-docs", 1);
|
|
73
|
+
* // Declare a document type.
|
|
74
|
+
* type Document = { id: string; value: number };
|
|
75
|
+
* // Create the table.
|
|
76
|
+
* const docs = Table.create<Document>()
|
|
77
|
+
* .withPrimaryKey(index<Document>().key("id"))
|
|
78
|
+
* // Connect the table to our storage.
|
|
79
|
+
* .withStorage(storage);
|
|
80
|
+
* // Wait until the table has finished syncing with the storage.
|
|
81
|
+
* await docs.ready;
|
|
82
|
+
* // Ready for use!
|
|
83
|
+
* docs.add({ id: "Robert", value: 9001 });
|
|
84
|
+
*/
|
|
52
85
|
export class IndexedDBBackend extends StorageBackend {
|
|
53
86
|
private db?: IDBDatabase;
|
|
54
87
|
|
|
55
88
|
readonly version: number;
|
|
56
89
|
readonly table: string;
|
|
57
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Connect to an IndexedDB database.
|
|
93
|
+
*/
|
|
58
94
|
static async open(config: IndexedDBBackendConfig): Promise<IndexedDBBackend> {
|
|
59
95
|
const c = { ...backendConfigDefaults, ...config };
|
|
60
96
|
const storage = new IndexedDBBackend(c.name, c.version, c.table);
|
|
@@ -107,6 +143,7 @@ export class IndexedDBBackend extends StorageBackend {
|
|
|
107
143
|
});
|
|
108
144
|
}
|
|
109
145
|
|
|
146
|
+
/** @internal */
|
|
110
147
|
async get<A = unknown>(table: string, key: unknown): Promise<A> {
|
|
111
148
|
const value: StorageRecord = await this.transaction("readonly", (store) => {
|
|
112
149
|
return request(store.get([table, key] as IDBValidKey));
|
|
@@ -114,6 +151,7 @@ export class IndexedDBBackend extends StorageBackend {
|
|
|
114
151
|
return unpackRecord(value);
|
|
115
152
|
}
|
|
116
153
|
|
|
154
|
+
/** @internal */
|
|
117
155
|
async getAll<A = unknown>(table: string): Promise<Array<A>> {
|
|
118
156
|
const values = await this.transaction("readonly", (store) => {
|
|
119
157
|
return request(store.index("table").getAll(table));
|
|
@@ -121,6 +159,7 @@ export class IndexedDBBackend extends StorageBackend {
|
|
|
121
159
|
return (values as Array<StorageRecord>).map(unpackRecord<A>);
|
|
122
160
|
}
|
|
123
161
|
|
|
162
|
+
/** @internal */
|
|
124
163
|
async update(table: string, key: unknown, value: unknown): Promise<void> {
|
|
125
164
|
await this.transaction("readwrite", (store) => {
|
|
126
165
|
return request(store.put({ table, key, value: serialise(value) }));
|
|
@@ -128,6 +167,7 @@ export class IndexedDBBackend extends StorageBackend {
|
|
|
128
167
|
this.broadcast?.send({ event: "update", table, key });
|
|
129
168
|
}
|
|
130
169
|
|
|
170
|
+
/** @internal */
|
|
131
171
|
async delete(table: string, key: unknown): Promise<void> {
|
|
132
172
|
await this.transaction("readwrite", (store) => {
|
|
133
173
|
return request(store.delete([table, key as IDBValidKey]));
|
package/src/index.test.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { sleep } from "@bodil/core/async";
|
|
|
6
6
|
import { Signal } from "@bodil/signal";
|
|
7
7
|
import { expect, expectTypeOf, test } from "vitest";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { index, Table } from ".";
|
|
10
10
|
import { IndexedDBBackend } from "./backend";
|
|
11
11
|
import type { IndexablesOf } from "./types";
|
|
12
12
|
|
|
@@ -34,11 +34,12 @@ test("memdb basics", () => {
|
|
|
34
34
|
tags: Array<string>;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
const table =
|
|
38
|
-
.
|
|
39
|
-
.withIndex(index<TestItem>()("
|
|
40
|
-
.withIndex(
|
|
41
|
-
.withIndex(
|
|
37
|
+
const table = Table.create<TestItem>()
|
|
38
|
+
.withPrimaryIndex(index<TestItem>().key("id"))
|
|
39
|
+
.withIndex(index<TestItem>().time("created"))
|
|
40
|
+
.withIndex(index<TestItem>().key("uri"))
|
|
41
|
+
.withIndex(index<TestItem>().array("tags"))
|
|
42
|
+
.withIndex(index<TestItem>().keys("id", "uri"));
|
|
42
43
|
|
|
43
44
|
const now = time.now();
|
|
44
45
|
const [welp, wolp, wulp, wilp] = [
|
|
@@ -115,9 +116,9 @@ test("memdb basics", () => {
|
|
|
115
116
|
|
|
116
117
|
test("memdb index dupes", () => {
|
|
117
118
|
type Account = { id: string; order: number };
|
|
118
|
-
const accounts =
|
|
119
|
-
index<Account>()("
|
|
120
|
-
|
|
119
|
+
const accounts = Table.create<Account>()
|
|
120
|
+
.withPrimaryIndex(index<Account>().key("id"))
|
|
121
|
+
.withIndex(index<Account>().key("order"));
|
|
121
122
|
|
|
122
123
|
const account = { id: "test@test.com", order: 1 };
|
|
123
124
|
accounts.add({ ...account });
|
|
@@ -128,10 +129,10 @@ test("memdb index dupes", () => {
|
|
|
128
129
|
|
|
129
130
|
test("memdb signals", () => {
|
|
130
131
|
type Thing = { name: string; counter: number };
|
|
131
|
-
const things =
|
|
132
|
+
const things = Table.create<Thing>().withPrimaryIndex(index<Thing>().key("name"));
|
|
132
133
|
|
|
133
134
|
type ThingMap = { id: number; name: string };
|
|
134
|
-
const thingMaps =
|
|
135
|
+
const thingMaps = Table.create<ThingMap>().withPrimaryIndex(index<ThingMap>().key("id"));
|
|
135
136
|
thingMaps.add({ id: 1, name: "Mike" }, { id: 2, name: "Robert" });
|
|
136
137
|
|
|
137
138
|
const joe = things.signal("Joe");
|
|
@@ -163,7 +164,7 @@ test("memdb signals", () => {
|
|
|
163
164
|
|
|
164
165
|
test("failed update shouldn't change anything", () => {
|
|
165
166
|
type Thing = { name: string; counter: number };
|
|
166
|
-
const things =
|
|
167
|
+
const things = Table.create<Thing>().withPrimaryIndex(index<Thing>().key("name"));
|
|
167
168
|
|
|
168
169
|
things.add({ name: "Joe", counter: 321 });
|
|
169
170
|
expect(() =>
|
|
@@ -181,8 +182,9 @@ test("IndexedDB", async () => {
|
|
|
181
182
|
const before = now.subtract(time.seconds(25));
|
|
182
183
|
{
|
|
183
184
|
const store = await IndexedDBBackend.open({ name: "test", version: 1 });
|
|
184
|
-
const things =
|
|
185
|
-
.
|
|
185
|
+
const things = Table.create<Thing>()
|
|
186
|
+
.withPrimaryIndex(index<Thing>().key("key"))
|
|
187
|
+
.withIndex(index<Thing>().time("time"))
|
|
186
188
|
.withStorage(store, "things");
|
|
187
189
|
await things.ready;
|
|
188
190
|
things.add(
|
|
@@ -199,8 +201,9 @@ test("IndexedDB", async () => {
|
|
|
199
201
|
}
|
|
200
202
|
{
|
|
201
203
|
const store = await IndexedDBBackend.open({ name: "test", version: 1 });
|
|
202
|
-
const things =
|
|
203
|
-
.
|
|
204
|
+
const things = Table.create<Thing>()
|
|
205
|
+
.withPrimaryIndex(index<Thing>().key("key"))
|
|
206
|
+
.withIndex(index<Thing>().time("time"))
|
|
204
207
|
.withStorage(store, "things");
|
|
205
208
|
await things.ready;
|
|
206
209
|
expect(things.get("Joe")).deep.equal({ key: "Joe", value: "Armstrong", time: now });
|
|
@@ -213,7 +216,9 @@ test("IndexedDB", async () => {
|
|
|
213
216
|
|
|
214
217
|
test("query.below/query.above", () => {
|
|
215
218
|
type Item = { id: string; value: number };
|
|
216
|
-
const table =
|
|
219
|
+
const table = Table.create<Item>()
|
|
220
|
+
.withPrimaryIndex(index<Item>().key("id"))
|
|
221
|
+
.withIndex(index<Item>().key("value"));
|
|
217
222
|
table.add(
|
|
218
223
|
{ id: "1", value: 1 },
|
|
219
224
|
{ id: "2", value: 2 },
|
|
@@ -252,3 +257,24 @@ test("query.below/query.above", () => {
|
|
|
252
257
|
.map((i) => i.value),
|
|
253
258
|
).toEqual([3, 4, 5]);
|
|
254
259
|
});
|
|
260
|
+
|
|
261
|
+
test("query.delete()", () => {
|
|
262
|
+
type Doc = { id: number };
|
|
263
|
+
const table = Table.create<Doc>().withPrimaryIndex(index<Doc>().key("id"));
|
|
264
|
+
for (let id = 0; id < 10; id++) {
|
|
265
|
+
table.add({ id });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const ids = table
|
|
269
|
+
.where("id")
|
|
270
|
+
.signal()
|
|
271
|
+
.map((docs) => docs.map((doc) => doc.id));
|
|
272
|
+
expect(ids.get()).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
273
|
+
|
|
274
|
+
expect(table.where("id").above(5).delete()).toEqual(4);
|
|
275
|
+
expect(
|
|
276
|
+
Iterator.from(table)
|
|
277
|
+
.map((doc) => doc.id)
|
|
278
|
+
.toArray(),
|
|
279
|
+
).toEqual([0, 1, 2, 3, 4, 5]);
|
|
280
|
+
});
|
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
|
|
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
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
@@ -11,12 +11,12 @@ export enum IteratorDirection {
|
|
|
11
11
|
Descending = 1,
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function* indexIterator<
|
|
15
|
-
table: BTree<I["keyType"], Array<
|
|
14
|
+
function* indexIterator<Document extends object, I extends Index<Document>>(
|
|
15
|
+
table: BTree<I["keyType"], Array<Document>>,
|
|
16
16
|
direction: IteratorDirection,
|
|
17
17
|
start?: I["keyType"],
|
|
18
18
|
skipStart = false,
|
|
19
|
-
): Generator<Readonly<
|
|
19
|
+
): Generator<Readonly<Document>> {
|
|
20
20
|
if (table.isEmpty) {
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
@@ -35,12 +35,12 @@ function* indexIterator<A extends object, I extends Index<A>>(
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function* primaryIndexIterator<
|
|
39
|
-
table: BTree<I["keyType"], Readonly<
|
|
38
|
+
function* primaryIndexIterator<Document extends object, I extends UnitIndex<Document>>(
|
|
39
|
+
table: BTree<I["keyType"], Readonly<Document>>,
|
|
40
40
|
direction: IteratorDirection,
|
|
41
41
|
start?: I["keyType"],
|
|
42
42
|
skipStart = false,
|
|
43
|
-
): Generator<Readonly<
|
|
43
|
+
): Generator<Readonly<Document>> {
|
|
44
44
|
if (table.isEmpty) {
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
@@ -57,72 +57,78 @@ function* primaryIndexIterator<A extends object, I extends UnitIndex<A>>(
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export abstract class Query<
|
|
61
|
-
abstract [Symbol.iterator](): IterableIterator<Readonly<
|
|
62
|
-
abstract signal(): Signal.Computed<Array<Readonly<
|
|
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>>>;
|
|
63
63
|
|
|
64
|
-
map<B>(mapFn: (item: Readonly<
|
|
64
|
+
map<B>(mapFn: (item: Readonly<Document>) => B): IterableIterator<B> {
|
|
65
65
|
return Iterator.from(this).map(mapFn);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
forEach(action: (item:
|
|
68
|
+
forEach(action: (item: Document) => void): void {
|
|
69
69
|
for (const item of this) {
|
|
70
70
|
action(item);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
toArray(): Array<Readonly<
|
|
74
|
+
toArray(): Array<Readonly<Document>> {
|
|
75
75
|
return Array.from(this);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export abstract class TableQuery<
|
|
80
|
-
|
|
81
|
-
PI extends UnitIndex<
|
|
80
|
+
Document extends object,
|
|
81
|
+
PI extends UnitIndex<Document>,
|
|
82
82
|
Ix extends object,
|
|
83
|
-
> extends Query<
|
|
84
|
-
|
|
83
|
+
> extends Query<Document> {
|
|
84
|
+
/** @ignore */
|
|
85
|
+
protected table: Table<Document, PI, Ix>;
|
|
85
86
|
|
|
86
|
-
constructor(table: Table<
|
|
87
|
+
constructor(table: Table<Document, PI, Ix>) {
|
|
87
88
|
super();
|
|
88
89
|
this.table = table;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
signal(): Signal.Computed<Array<Readonly<
|
|
92
|
+
signal(): Signal.Computed<Array<Readonly<Document>>> {
|
|
92
93
|
return Signal.computed(() => {
|
|
93
94
|
this.table.changed.get();
|
|
94
95
|
return Array.from(this);
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Delete all documents from the table matching this query.
|
|
101
|
+
*/
|
|
98
102
|
delete(): number {
|
|
99
|
-
return this.table.delete(
|
|
103
|
+
return this.table.delete(
|
|
104
|
+
...Array.from(this).map((doc) => this.table.primaryIndex.extractKey(doc)),
|
|
105
|
+
);
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
filter(predicate: (item: Readonly<
|
|
108
|
+
filter(predicate: (item: Readonly<Document>) => boolean): ChainQuery<Document, PI, Ix> {
|
|
103
109
|
return new ChainQuery(this.table, this, (iter) => Iterator.from(iter).filter(predicate));
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
limit(count: number): ChainQuery<
|
|
112
|
+
limit(count: number): ChainQuery<Document, PI, Ix> {
|
|
107
113
|
return new ChainQuery(this.table, this, (iter) => Iterator.from(iter).take(count));
|
|
108
114
|
}
|
|
109
115
|
}
|
|
110
116
|
|
|
111
117
|
export class IndexQuery<
|
|
112
|
-
|
|
113
|
-
PI extends UnitIndex<
|
|
114
|
-
I extends Index<
|
|
118
|
+
Document extends object,
|
|
119
|
+
PI extends UnitIndex<Document>,
|
|
120
|
+
I extends Index<Document>,
|
|
115
121
|
Ix extends object,
|
|
116
|
-
> extends TableQuery<
|
|
122
|
+
> extends TableQuery<Document, PI, Ix> {
|
|
117
123
|
private readonly index: I;
|
|
118
|
-
private readonly indexTable?: BTree<I["keyType"], Array<
|
|
124
|
+
private readonly indexTable?: BTree<I["keyType"], Array<Document>>;
|
|
119
125
|
private readonly isPrimary: boolean;
|
|
120
126
|
private start?: I["keyType"];
|
|
121
127
|
private skipStart = false;
|
|
122
128
|
private direction: IteratorDirection;
|
|
123
129
|
|
|
124
130
|
constructor(
|
|
125
|
-
table: Table<
|
|
131
|
+
table: Table<Document, PI, Ix>,
|
|
126
132
|
indexKey: keyof Ix & string,
|
|
127
133
|
direction: IteratorDirection,
|
|
128
134
|
start?: I["keyType"],
|
|
@@ -140,7 +146,7 @@ export class IndexQuery<
|
|
|
140
146
|
this.start = start;
|
|
141
147
|
}
|
|
142
148
|
|
|
143
|
-
[Symbol.iterator](): IterableIterator<Readonly<
|
|
149
|
+
[Symbol.iterator](): IterableIterator<Readonly<Document>> {
|
|
144
150
|
return this.isPrimary
|
|
145
151
|
? primaryIndexIterator(this.table.primary, this.direction, this.start, this.skipStart)
|
|
146
152
|
: indexIterator(this.indexTable!, this.direction, this.start, this.skipStart);
|
|
@@ -157,7 +163,7 @@ export class IndexQuery<
|
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
equals(value: I["keyType"]): ArrayQuery<
|
|
166
|
+
equals(value: I["keyType"]): ArrayQuery<Document, PI, Ix> {
|
|
161
167
|
if (this.isPrimary) {
|
|
162
168
|
const item = this.table.get(value);
|
|
163
169
|
return new ArrayQuery(this.table, item === undefined ? [] : [item]);
|
|
@@ -198,43 +204,45 @@ export class IndexQuery<
|
|
|
198
204
|
}
|
|
199
205
|
|
|
200
206
|
export class ChainQuery<
|
|
201
|
-
|
|
202
|
-
PI extends UnitIndex<
|
|
207
|
+
Document extends object,
|
|
208
|
+
PI extends UnitIndex<Document>,
|
|
203
209
|
Ix extends object,
|
|
204
|
-
> extends TableQuery<
|
|
205
|
-
private readonly parent: Query<
|
|
210
|
+
> extends TableQuery<Document, PI, Ix> {
|
|
211
|
+
private readonly parent: Query<Document>;
|
|
206
212
|
private readonly iterator: (
|
|
207
|
-
parentIterator: IterableIterator<Readonly<
|
|
208
|
-
) => IterableIterator<Readonly<
|
|
213
|
+
parentIterator: IterableIterator<Readonly<Document>>,
|
|
214
|
+
) => IterableIterator<Readonly<Document>>;
|
|
209
215
|
|
|
210
216
|
constructor(
|
|
211
|
-
table: Table<
|
|
212
|
-
parent: Query<
|
|
213
|
-
iterator: (
|
|
217
|
+
table: Table<Document, PI, Ix>,
|
|
218
|
+
parent: Query<Document>,
|
|
219
|
+
iterator: (
|
|
220
|
+
parentIterator: IterableIterator<Readonly<Document>>,
|
|
221
|
+
) => IterableIterator<Readonly<Document>>,
|
|
214
222
|
) {
|
|
215
223
|
super(table);
|
|
216
224
|
this.parent = parent;
|
|
217
225
|
this.iterator = iterator;
|
|
218
226
|
}
|
|
219
227
|
|
|
220
|
-
[Symbol.iterator](): IterableIterator<Readonly<
|
|
228
|
+
[Symbol.iterator](): IterableIterator<Readonly<Document>> {
|
|
221
229
|
return this.iterator(this.parent[Symbol.iterator]());
|
|
222
230
|
}
|
|
223
231
|
}
|
|
224
232
|
|
|
225
233
|
export class ArrayQuery<
|
|
226
|
-
|
|
227
|
-
PI extends UnitIndex<
|
|
234
|
+
Document extends object,
|
|
235
|
+
PI extends UnitIndex<Document>,
|
|
228
236
|
Ix extends object,
|
|
229
|
-
> extends TableQuery<
|
|
230
|
-
private readonly values: Array<Readonly<
|
|
237
|
+
> extends TableQuery<Document, PI, Ix> {
|
|
238
|
+
private readonly values: Array<Readonly<Document>>;
|
|
231
239
|
|
|
232
|
-
constructor(table: Table<
|
|
240
|
+
constructor(table: Table<Document, PI, Ix>, values: Array<Readonly<Document>>) {
|
|
233
241
|
super(table);
|
|
234
242
|
this.values = values;
|
|
235
243
|
}
|
|
236
244
|
|
|
237
|
-
[Symbol.iterator](): IterableIterator<Readonly<
|
|
245
|
+
[Symbol.iterator](): IterableIterator<Readonly<Document>> {
|
|
238
246
|
return this.values[Symbol.iterator]();
|
|
239
247
|
}
|
|
240
248
|
}
|