xitdb 0.11.0 → 0.13.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 +117 -115
- package/dist/core-buffered-file.d.ts +21 -23
- package/dist/core-file.d.ts +7 -9
- package/dist/core-memory.d.ts +14 -14
- package/dist/core.d.ts +14 -14
- package/dist/database.d.ts +35 -36
- package/dist/hasher.d.ts +2 -1
- package/dist/index.js +728 -791
- package/dist/read-array-list.d.ts +5 -5
- package/dist/read-counted-hash-map.d.ts +1 -1
- package/dist/read-counted-hash-set.d.ts +1 -1
- package/dist/read-cursor.d.ts +18 -18
- package/dist/read-hash-map.d.ts +18 -18
- package/dist/read-hash-set.d.ts +9 -9
- package/dist/read-linked-array-list.d.ts +5 -5
- package/dist/write-array-list.d.ts +9 -10
- package/dist/write-counted-hash-map.d.ts +2 -3
- package/dist/write-counted-hash-set.d.ts +2 -3
- package/dist/write-cursor.d.ts +10 -10
- package/dist/write-hash-map.d.ts +18 -19
- package/dist/write-hash-set.d.ts +12 -13
- package/dist/write-linked-array-list.d.ts +12 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
<a href="https://github.com/xit-vcs/xitdb">Zig</a> |
|
|
7
7
|
<a href="https://github.com/xit-vcs/xitdb-java">Java</a> |
|
|
8
8
|
<a href="https://github.com/codeboost/xitdb-clj">Clojure</a> |
|
|
9
|
-
<a href="https://github.com/xit-vcs/xitdb-ts">TypeScript</a>
|
|
9
|
+
<a href="https://github.com/xit-vcs/xitdb-ts">TypeScript</a> |
|
|
10
|
+
<a href="https://github.com/xit-vcs/xitdb-go">Go</a>
|
|
10
11
|
</p>
|
|
11
12
|
|
|
12
13
|
* Each transaction efficiently creates a new "copy" of the database, and past copies can still be read from and reverted to.
|
|
@@ -16,9 +17,10 @@
|
|
|
16
17
|
* Reads never block writes, and a database can be read from multiple threads/processes without locks.
|
|
17
18
|
* No query engine of any kind. You just write data structures (primarily an `ArrayList` and `HashMap`) that can be nested arbitrarily.
|
|
18
19
|
* No dependencies besides the JavaScript standard library.
|
|
20
|
+
* Fully synchronous API — no async/await needed.
|
|
19
21
|
* Available [on npm](https://www.npmjs.com/package/xitdb).
|
|
20
22
|
|
|
21
|
-
This database was originally made for the [xit version control system](https://github.com/xit-vcs/xit), but I bet it has a lot of potential for other projects. The combination of being immutable and having an API similar to in-memory data structures is pretty powerful. Consider using it [instead of SQLite](https://gist.github.com/
|
|
23
|
+
This database was originally made for the [xit version control system](https://github.com/xit-vcs/xit), but I bet it has a lot of potential for other projects. The combination of being immutable and having an API similar to in-memory data structures is pretty powerful. Consider using it [instead of SQLite](https://gist.github.com/xeubie/03a0724484e1111ef4c05d72a935c42c) for your TypeScript projects: it's simpler, it's pure TypeScript, and it creates no impedance mismatch with your program the way SQL databases do.
|
|
22
24
|
|
|
23
25
|
* [Example](#example)
|
|
24
26
|
* [Initializing a Database](#initializing-a-database)
|
|
@@ -35,13 +37,13 @@ In this example, we create a new database, write some data in a transaction, and
|
|
|
35
37
|
|
|
36
38
|
```typescript
|
|
37
39
|
// init the db
|
|
38
|
-
using core =
|
|
40
|
+
using core = new CoreBufferedFile('main.db');
|
|
39
41
|
const hasher = new Hasher('SHA-1');
|
|
40
|
-
const db =
|
|
42
|
+
const db = new Database(core, hasher);
|
|
41
43
|
|
|
42
44
|
// to get the benefits of immutability, the top-level data structure
|
|
43
45
|
// must be an ArrayList, so each transaction is stored as an item in it
|
|
44
|
-
const history =
|
|
46
|
+
const history = new WriteArrayList(db.rootCursor());
|
|
45
47
|
|
|
46
48
|
// this is how a transaction is executed. we call history.appendContext,
|
|
47
49
|
// providing it with the most recent copy of the db and a context
|
|
@@ -60,52 +62,52 @@ const history = await WriteArrayList.create(db.rootCursor());
|
|
|
60
62
|
// {"name": "Alice", "age": 25},
|
|
61
63
|
// {"name": "Bob", "age": 42}
|
|
62
64
|
// ]}
|
|
63
|
-
|
|
64
|
-
const moment =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const fruitsCursor =
|
|
70
|
-
const fruits =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const peopleCursor =
|
|
76
|
-
const people =
|
|
77
|
-
|
|
78
|
-
const aliceCursor =
|
|
79
|
-
const alice =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const bobCursor =
|
|
84
|
-
const bob =
|
|
85
|
-
|
|
86
|
-
|
|
65
|
+
history.appendContext(history.getSlot(-1), (cursor) => {
|
|
66
|
+
const moment = new WriteHashMap(cursor);
|
|
67
|
+
|
|
68
|
+
moment.put('foo', new Bytes('foo'));
|
|
69
|
+
moment.put('bar', new Bytes('bar'));
|
|
70
|
+
|
|
71
|
+
const fruitsCursor = moment.putCursor('fruits');
|
|
72
|
+
const fruits = new WriteArrayList(fruitsCursor);
|
|
73
|
+
fruits.append(new Bytes('apple'));
|
|
74
|
+
fruits.append(new Bytes('pear'));
|
|
75
|
+
fruits.append(new Bytes('grape'));
|
|
76
|
+
|
|
77
|
+
const peopleCursor = moment.putCursor('people');
|
|
78
|
+
const people = new WriteArrayList(peopleCursor);
|
|
79
|
+
|
|
80
|
+
const aliceCursor = people.appendCursor();
|
|
81
|
+
const alice = new WriteHashMap(aliceCursor);
|
|
82
|
+
alice.put('name', new Bytes('Alice'));
|
|
83
|
+
alice.put('age', new Uint(25));
|
|
84
|
+
|
|
85
|
+
const bobCursor = people.appendCursor();
|
|
86
|
+
const bob = new WriteHashMap(bobCursor);
|
|
87
|
+
bob.put('name', new Bytes('Bob'));
|
|
88
|
+
bob.put('age', new Uint(42));
|
|
87
89
|
});
|
|
88
90
|
|
|
89
91
|
// get the most recent copy of the database, like a moment
|
|
90
92
|
// in time. the -1 index will return the last index in the list.
|
|
91
|
-
const momentCursor =
|
|
93
|
+
const momentCursor = history.getCursor(-1);
|
|
92
94
|
const moment = new ReadHashMap(momentCursor!);
|
|
93
95
|
|
|
94
96
|
// we can read the value of "foo" from the map by getting
|
|
95
97
|
// the cursor to "foo" and then calling readBytes on it
|
|
96
|
-
const fooCursor =
|
|
97
|
-
const fooValue =
|
|
98
|
+
const fooCursor = moment.getCursor('foo');
|
|
99
|
+
const fooValue = fooCursor!.readBytes(MAX_READ_BYTES);
|
|
98
100
|
expect(new TextDecoder().decode(fooValue)).toBe('foo');
|
|
99
101
|
|
|
100
102
|
// to get the "fruits" list, we get the cursor to it and
|
|
101
103
|
// then pass it to the ReadArrayList constructor
|
|
102
|
-
const fruitsCursor =
|
|
104
|
+
const fruitsCursor = moment.getCursor('fruits');
|
|
103
105
|
const fruits = new ReadArrayList(fruitsCursor!);
|
|
104
|
-
expect(
|
|
106
|
+
expect(fruits.count()).toBe(3);
|
|
105
107
|
|
|
106
108
|
// now we can get the first item from the fruits list and read it
|
|
107
|
-
const appleCursor =
|
|
108
|
-
const appleValue =
|
|
109
|
+
const appleCursor = fruits.getCursor(0);
|
|
110
|
+
const appleValue = appleCursor!.readBytes(MAX_READ_BYTES);
|
|
109
111
|
expect(new TextDecoder().decode(appleValue)).toBe('apple');
|
|
110
112
|
```
|
|
111
113
|
|
|
@@ -147,14 +149,14 @@ In xitdb, you can optionally store a format tag with a byte array. A format tag
|
|
|
147
149
|
```typescript
|
|
148
150
|
const randomBytes = new Uint8Array(32);
|
|
149
151
|
crypto.getRandomValues(randomBytes);
|
|
150
|
-
|
|
152
|
+
moment.put('random-number', new Bytes(randomBytes, new TextEncoder().encode('bi')));
|
|
151
153
|
```
|
|
152
154
|
|
|
153
155
|
Then, you can read it like this:
|
|
154
156
|
|
|
155
157
|
```typescript
|
|
156
|
-
const randomNumberCursor =
|
|
157
|
-
const randomNumber =
|
|
158
|
+
const randomNumberCursor = moment.getCursor('random-number');
|
|
159
|
+
const randomNumber = randomNumberCursor!.readBytesObject(MAX_READ_BYTES);
|
|
158
160
|
expect(new TextDecoder().decode(randomNumber.formatTag!)).toBe('bi');
|
|
159
161
|
const randomBigInt = randomNumber.value;
|
|
160
162
|
```
|
|
@@ -166,76 +168,76 @@ There are many types you may want to store this way. Maybe an ISO-8601 date like
|
|
|
166
168
|
A powerful feature of immutable data is fast cloning. Any data structure can be instantly cloned and changed without affecting the original. Starting with the example code above, we can make a new transaction that creates a "food" list based on the existing "fruits" list:
|
|
167
169
|
|
|
168
170
|
```typescript
|
|
169
|
-
|
|
170
|
-
const moment =
|
|
171
|
+
history.appendContext(history.getSlot(-1), (cursor) => {
|
|
172
|
+
const moment = new WriteHashMap(cursor);
|
|
171
173
|
|
|
172
|
-
const fruitsCursor =
|
|
174
|
+
const fruitsCursor = moment.getCursor('fruits');
|
|
173
175
|
const fruits = new ReadArrayList(fruitsCursor!);
|
|
174
176
|
|
|
175
177
|
// create a new key called "food" whose initial value is
|
|
176
178
|
// based on the "fruits" list
|
|
177
|
-
const foodCursor =
|
|
178
|
-
|
|
179
|
+
const foodCursor = moment.putCursor('food');
|
|
180
|
+
foodCursor.write(fruits.slot());
|
|
179
181
|
|
|
180
|
-
const food =
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
const food = new WriteArrayList(foodCursor);
|
|
183
|
+
food.append(new Bytes('eggs'));
|
|
184
|
+
food.append(new Bytes('rice'));
|
|
185
|
+
food.append(new Bytes('fish'));
|
|
184
186
|
});
|
|
185
187
|
|
|
186
|
-
const momentCursor =
|
|
188
|
+
const momentCursor = history.getCursor(-1);
|
|
187
189
|
const moment = new ReadHashMap(momentCursor!);
|
|
188
190
|
|
|
189
191
|
// the food list includes the fruits
|
|
190
|
-
const foodCursor =
|
|
192
|
+
const foodCursor = moment.getCursor('food');
|
|
191
193
|
const food = new ReadArrayList(foodCursor!);
|
|
192
|
-
expect(
|
|
194
|
+
expect(food.count()).toBe(6);
|
|
193
195
|
|
|
194
196
|
// ...but the fruits list hasn't been changed
|
|
195
|
-
const fruitsCursor =
|
|
197
|
+
const fruitsCursor = moment.getCursor('fruits');
|
|
196
198
|
const fruits = new ReadArrayList(fruitsCursor!);
|
|
197
|
-
expect(
|
|
199
|
+
expect(fruits.count()).toBe(3);
|
|
198
200
|
```
|
|
199
201
|
|
|
200
202
|
Before we continue, let's save the latest history index, so we can revert back to this moment of the database later:
|
|
201
203
|
|
|
202
204
|
```typescript
|
|
203
|
-
const historyIndex =
|
|
205
|
+
const historyIndex = history.count() - 1;
|
|
204
206
|
```
|
|
205
207
|
|
|
206
208
|
There's one catch you'll run into when cloning. If we try cloning a data structure that was created in the same transaction, it doesn't seem to work:
|
|
207
209
|
|
|
208
210
|
```typescript
|
|
209
|
-
|
|
210
|
-
const moment =
|
|
211
|
+
history.appendContext(history.getSlot(-1), (cursor) => {
|
|
212
|
+
const moment = new WriteHashMap(cursor);
|
|
211
213
|
|
|
212
|
-
const bigCitiesCursor =
|
|
213
|
-
const bigCities =
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
const bigCitiesCursor = moment.putCursor('big-cities');
|
|
215
|
+
const bigCities = new WriteArrayList(bigCitiesCursor);
|
|
216
|
+
bigCities.append(new Bytes('New York, NY'));
|
|
217
|
+
bigCities.append(new Bytes('Los Angeles, CA'));
|
|
216
218
|
|
|
217
219
|
// create a new key called "cities" whose initial value is
|
|
218
220
|
// based on the "big-cities" list
|
|
219
|
-
const citiesCursor =
|
|
220
|
-
|
|
221
|
+
const citiesCursor = moment.putCursor('cities');
|
|
222
|
+
citiesCursor.write(bigCities.slot());
|
|
221
223
|
|
|
222
|
-
const cities =
|
|
223
|
-
|
|
224
|
-
|
|
224
|
+
const cities = new WriteArrayList(citiesCursor);
|
|
225
|
+
cities.append(new Bytes('Charleston, SC'));
|
|
226
|
+
cities.append(new Bytes('Louisville, KY'));
|
|
225
227
|
});
|
|
226
228
|
|
|
227
|
-
const momentCursor =
|
|
229
|
+
const momentCursor = history.getCursor(-1);
|
|
228
230
|
const moment = new ReadHashMap(momentCursor!);
|
|
229
231
|
|
|
230
232
|
// the cities list contains all four
|
|
231
|
-
const citiesCursor =
|
|
233
|
+
const citiesCursor = moment.getCursor('cities');
|
|
232
234
|
const cities = new ReadArrayList(citiesCursor!);
|
|
233
|
-
expect(
|
|
235
|
+
expect(cities.count()).toBe(4);
|
|
234
236
|
|
|
235
237
|
// ..but so does big-cities! we did not intend to mutate this
|
|
236
|
-
const bigCitiesCursor =
|
|
238
|
+
const bigCitiesCursor = moment.getCursor('big-cities');
|
|
237
239
|
const bigCities = new ReadArrayList(bigCitiesCursor!);
|
|
238
|
-
expect(
|
|
240
|
+
expect(bigCities.count()).toBe(4);
|
|
239
241
|
```
|
|
240
242
|
|
|
241
243
|
The reason that `big-cities` was mutated is because all data in a given transaction is temporarily mutable. This is a very important optimization, but in this case, it's not what we want.
|
|
@@ -243,45 +245,45 @@ The reason that `big-cities` was mutated is because all data in a given transact
|
|
|
243
245
|
To show how to fix this, let's first undo the transaction we just made. Here we use the `historyIndex` we saved before to revert back to the older database moment:
|
|
244
246
|
|
|
245
247
|
```typescript
|
|
246
|
-
|
|
248
|
+
history.append(history.getSlot(historyIndex)!);
|
|
247
249
|
```
|
|
248
250
|
|
|
249
251
|
This time, after making the "big cities" list, we call `freeze`, which tells xitdb to consider all data made so far in the transaction to be immutable. After that, we can clone it into the "cities" list and it will work the way we wanted:
|
|
250
252
|
|
|
251
253
|
```typescript
|
|
252
|
-
|
|
253
|
-
const moment =
|
|
254
|
+
history.appendContext(history.getSlot(-1), (cursor) => {
|
|
255
|
+
const moment = new WriteHashMap(cursor);
|
|
254
256
|
|
|
255
|
-
const bigCitiesCursor =
|
|
256
|
-
const bigCities =
|
|
257
|
-
|
|
258
|
-
|
|
257
|
+
const bigCitiesCursor = moment.putCursor('big-cities');
|
|
258
|
+
const bigCities = new WriteArrayList(bigCitiesCursor);
|
|
259
|
+
bigCities.append(new Bytes('New York, NY'));
|
|
260
|
+
bigCities.append(new Bytes('Los Angeles, CA'));
|
|
259
261
|
|
|
260
262
|
// freeze here, so big-cities won't be mutated
|
|
261
263
|
cursor.db.freeze();
|
|
262
264
|
|
|
263
265
|
// create a new key called "cities" whose initial value is
|
|
264
266
|
// based on the "big-cities" list
|
|
265
|
-
const citiesCursor =
|
|
266
|
-
|
|
267
|
+
const citiesCursor = moment.putCursor('cities');
|
|
268
|
+
citiesCursor.write(bigCities.slot());
|
|
267
269
|
|
|
268
|
-
const cities =
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
const cities = new WriteArrayList(citiesCursor);
|
|
271
|
+
cities.append(new Bytes('Charleston, SC'));
|
|
272
|
+
cities.append(new Bytes('Louisville, KY'));
|
|
271
273
|
});
|
|
272
274
|
|
|
273
|
-
const momentCursor =
|
|
275
|
+
const momentCursor = history.getCursor(-1);
|
|
274
276
|
const moment = new ReadHashMap(momentCursor!);
|
|
275
277
|
|
|
276
278
|
// the cities list contains all four
|
|
277
|
-
const citiesCursor =
|
|
279
|
+
const citiesCursor = moment.getCursor('cities');
|
|
278
280
|
const cities = new ReadArrayList(citiesCursor!);
|
|
279
|
-
expect(
|
|
281
|
+
expect(cities.count()).toBe(4);
|
|
280
282
|
|
|
281
283
|
// and big-cities only contains the original two
|
|
282
|
-
const bigCitiesCursor =
|
|
284
|
+
const bigCitiesCursor = moment.getCursor('big-cities');
|
|
283
285
|
const bigCities = new ReadArrayList(bigCitiesCursor!);
|
|
284
|
-
expect(
|
|
286
|
+
expect(bigCities.count()).toBe(2);
|
|
285
287
|
```
|
|
286
288
|
|
|
287
289
|
## Large Byte Arrays
|
|
@@ -289,12 +291,12 @@ expect(await bigCities.count()).toBe(2);
|
|
|
289
291
|
When reading and writing large byte arrays, you probably don't want to have all of their contents in memory at once. To incrementally write to a byte array, just get a writer from a cursor:
|
|
290
292
|
|
|
291
293
|
```typescript
|
|
292
|
-
const longTextCursor =
|
|
293
|
-
const cursorWriter =
|
|
294
|
+
const longTextCursor = moment.putCursor('long-text');
|
|
295
|
+
const cursorWriter = longTextCursor.writer();
|
|
294
296
|
for (let i = 0; i < 50; i++) {
|
|
295
|
-
|
|
297
|
+
cursorWriter.write(new TextEncoder().encode('hello, world\n'));
|
|
296
298
|
}
|
|
297
|
-
|
|
299
|
+
cursorWriter.finish(); // remember to call this!
|
|
298
300
|
```
|
|
299
301
|
|
|
300
302
|
If you need to set a format tag for the byte array, put it in the `formatTag` field of the writer before you call `finish`.
|
|
@@ -302,11 +304,11 @@ If you need to set a format tag for the byte array, put it in the `formatTag` fi
|
|
|
302
304
|
To read a byte array incrementally, get a reader from a cursor:
|
|
303
305
|
|
|
304
306
|
```typescript
|
|
305
|
-
const longTextCursor =
|
|
306
|
-
const cursorReader =
|
|
307
|
+
const longTextCursor = moment.getCursor('long-text');
|
|
308
|
+
const cursorReader = longTextCursor!.reader();
|
|
307
309
|
let lineCount = 0, line: number[] = [];
|
|
308
310
|
const buf = new Uint8Array(1024);
|
|
309
|
-
for (let n; (n =
|
|
311
|
+
for (let n; (n = cursorReader.read(buf)) > 0; ) {
|
|
310
312
|
for (let i = 0; i < n; i++) {
|
|
311
313
|
if (buf[i] === 0x0A) { lineCount++; line = []; }
|
|
312
314
|
else line.push(buf[i]);
|
|
@@ -321,24 +323,24 @@ expect(lineCount).toBe(50);
|
|
|
321
323
|
All data structures support iteration. Here's an example of iterating over an `ArrayList` and printing all of the keys and values of each `HashMap` contained in it:
|
|
322
324
|
|
|
323
325
|
```typescript
|
|
324
|
-
const peopleCursor =
|
|
326
|
+
const peopleCursor = moment.getCursor('people');
|
|
325
327
|
const people = new ReadArrayList(peopleCursor!);
|
|
326
328
|
|
|
327
|
-
const peopleIter =
|
|
328
|
-
while (
|
|
329
|
-
const personCursor =
|
|
329
|
+
const peopleIter = people.iterator();
|
|
330
|
+
while (peopleIter.hasNext()) {
|
|
331
|
+
const personCursor = peopleIter.next();
|
|
330
332
|
const person = new ReadHashMap(personCursor!);
|
|
331
|
-
const personIter =
|
|
332
|
-
while (
|
|
333
|
-
const kvPairCursor =
|
|
334
|
-
const kvPair =
|
|
333
|
+
const personIter = person.iterator();
|
|
334
|
+
while (personIter.hasNext()) {
|
|
335
|
+
const kvPairCursor = personIter.next();
|
|
336
|
+
const kvPair = kvPairCursor!.readKeyValuePair();
|
|
335
337
|
|
|
336
|
-
const key = new TextDecoder().decode(
|
|
338
|
+
const key = new TextDecoder().decode(kvPair.keyCursor.readBytes(MAX_READ_BYTES));
|
|
337
339
|
|
|
338
340
|
switch (kvPair.valueCursor.slot().tag) {
|
|
339
341
|
case Tag.SHORT_BYTES:
|
|
340
342
|
case Tag.BYTES:
|
|
341
|
-
console.log(`${key}: ${new TextDecoder().decode(
|
|
343
|
+
console.log(`${key}: ${new TextDecoder().decode(kvPair.valueCursor.readBytes(MAX_READ_BYTES))}`);
|
|
342
344
|
break;
|
|
343
345
|
case Tag.UINT:
|
|
344
346
|
console.log(`${key}: ${kvPair.valueCursor.readUint()}`);
|
|
@@ -365,16 +367,16 @@ The hashing data structures will create the hash for you when you call methods l
|
|
|
365
367
|
When initializing a database, you tell xitdb how to hash with the `Hasher`. If you're using SHA-1, it will look like this:
|
|
366
368
|
|
|
367
369
|
```typescript
|
|
368
|
-
using core =
|
|
370
|
+
using core = new CoreBufferedFile('main.db');
|
|
369
371
|
const hasher = new Hasher('SHA-1');
|
|
370
|
-
const db =
|
|
372
|
+
const db = new Database(core, hasher);
|
|
371
373
|
```
|
|
372
374
|
|
|
373
375
|
The size of the hash in bytes will be stored in the database's header. If you try opening it later with a hashing algorithm that has the wrong hash size, it will throw an exception. If you are unsure what hash size the database uses, this creates a chicken-and-egg problem. You can read the header before initializing the database like this:
|
|
374
376
|
|
|
375
377
|
```typescript
|
|
376
|
-
|
|
377
|
-
const header =
|
|
378
|
+
core.seek(0);
|
|
379
|
+
const header = Header.read(core);
|
|
378
380
|
expect(header.hashSize).toBe(20);
|
|
379
381
|
```
|
|
380
382
|
|
|
@@ -387,8 +389,8 @@ const hasher = new Hasher('SHA-1', Hasher.stringToId('sha1'));
|
|
|
387
389
|
The hash id is only written to the database header when it is first initialized. When you open it later, the hash id in the `Hasher` is ignored. You can read the hash id of an existing database like this:
|
|
388
390
|
|
|
389
391
|
```typescript
|
|
390
|
-
|
|
391
|
-
const header =
|
|
392
|
+
core.seek(0);
|
|
393
|
+
const header = Header.read(core);
|
|
392
394
|
expect(Hasher.idToString(header.hashId)).toBe("sha1");
|
|
393
395
|
```
|
|
394
396
|
|
|
@@ -424,12 +426,12 @@ switch (hashIdStr) {
|
|
|
424
426
|
Normally, an immutable database grows forever, because old data is never deleted. To reclaim disk space and clear the history, xitdb supports compaction. This involves completely rebuilding the database file to only contain the data accessible from the latest copy (i.e., "moment") of the database.
|
|
425
427
|
|
|
426
428
|
```typescript
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const compactDb = await db.compact(compactCore);
|
|
429
|
+
using compactCore = new CoreBufferedFile('compact.db');
|
|
430
|
+
const compactDb = db.compact(compactCore);
|
|
431
431
|
|
|
432
432
|
// read from the new compacted db
|
|
433
433
|
const history = new ReadArrayList(compactDb.rootCursor());
|
|
434
|
-
expect(
|
|
434
|
+
expect(history.count()).toBe(1);
|
|
435
435
|
```
|
|
436
|
+
|
|
437
|
+
This compacted database will be in a separate file. If you want to delete the original database and replace it with this one, you'll need to do that yourself. It is not possible to compact a database in-place (using the same file as the target database); doing so would fail and would render your original database unreadable.
|
|
@@ -2,16 +2,15 @@ import type { Core, DataReader, DataWriter } from './core';
|
|
|
2
2
|
import { CoreFile } from './core-file';
|
|
3
3
|
export declare class CoreBufferedFile implements Core {
|
|
4
4
|
file: RandomAccessBufferedFile;
|
|
5
|
-
constructor(
|
|
6
|
-
static create(filePath: string, bufferSize?: number): Promise<CoreBufferedFile>;
|
|
5
|
+
constructor(filePath: string, bufferSize?: number);
|
|
7
6
|
reader(): DataReader;
|
|
8
7
|
writer(): DataWriter;
|
|
9
|
-
length():
|
|
10
|
-
seek(pos: number):
|
|
8
|
+
length(): number;
|
|
9
|
+
seek(pos: number): void;
|
|
11
10
|
position(): number;
|
|
12
|
-
setLength(len: number):
|
|
13
|
-
flush():
|
|
14
|
-
sync():
|
|
11
|
+
setLength(len: number): void;
|
|
12
|
+
flush(): void;
|
|
13
|
+
sync(): void;
|
|
15
14
|
[Symbol.dispose](): void;
|
|
16
15
|
}
|
|
17
16
|
declare class RandomAccessBufferedFile implements DataReader, DataWriter {
|
|
@@ -20,22 +19,21 @@ declare class RandomAccessBufferedFile implements DataReader, DataWriter {
|
|
|
20
19
|
private bufferSize;
|
|
21
20
|
private filePos;
|
|
22
21
|
private memoryPos;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
length(): Promise<number>;
|
|
22
|
+
constructor(filePath: string, bufferSize?: number);
|
|
23
|
+
seek(pos: number): void;
|
|
24
|
+
length(): number;
|
|
27
25
|
position(): number;
|
|
28
|
-
setLength(len: number):
|
|
29
|
-
flush():
|
|
30
|
-
sync():
|
|
31
|
-
write(buffer: Uint8Array):
|
|
32
|
-
writeByte(v: number):
|
|
33
|
-
writeShort(v: number):
|
|
34
|
-
writeLong(v: number):
|
|
35
|
-
readFully(buffer: Uint8Array):
|
|
36
|
-
readByte():
|
|
37
|
-
readShort():
|
|
38
|
-
readInt():
|
|
39
|
-
readLong():
|
|
26
|
+
setLength(len: number): void;
|
|
27
|
+
flush(): void;
|
|
28
|
+
sync(): void;
|
|
29
|
+
write(buffer: Uint8Array): void;
|
|
30
|
+
writeByte(v: number): void;
|
|
31
|
+
writeShort(v: number): void;
|
|
32
|
+
writeLong(v: number): void;
|
|
33
|
+
readFully(buffer: Uint8Array): void;
|
|
34
|
+
readByte(): number;
|
|
35
|
+
readShort(): number;
|
|
36
|
+
readInt(): number;
|
|
37
|
+
readLong(): number;
|
|
40
38
|
}
|
|
41
39
|
export {};
|
package/dist/core-file.d.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import type { Core, DataReader, DataWriter } from './core';
|
|
2
|
-
import type { FileHandle } from 'fs/promises';
|
|
3
2
|
export declare class CoreFile implements Core {
|
|
4
3
|
filePath: string;
|
|
5
4
|
private _position;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
static create(filePath: string): Promise<CoreFile>;
|
|
5
|
+
fd: number;
|
|
6
|
+
constructor(filePath: string);
|
|
9
7
|
reader(): DataReader;
|
|
10
8
|
writer(): DataWriter;
|
|
11
|
-
length():
|
|
12
|
-
seek(pos: number):
|
|
9
|
+
length(): number;
|
|
10
|
+
seek(pos: number): void;
|
|
13
11
|
position(): number;
|
|
14
|
-
setLength(len: number):
|
|
15
|
-
flush():
|
|
16
|
-
sync():
|
|
12
|
+
setLength(len: number): void;
|
|
13
|
+
flush(): void;
|
|
14
|
+
sync(): void;
|
|
17
15
|
[Symbol.dispose](): void;
|
|
18
16
|
}
|
package/dist/core-memory.d.ts
CHANGED
|
@@ -4,12 +4,12 @@ export declare class CoreMemory implements Core {
|
|
|
4
4
|
constructor();
|
|
5
5
|
reader(): DataReader;
|
|
6
6
|
writer(): DataWriter;
|
|
7
|
-
length():
|
|
8
|
-
seek(pos: number):
|
|
7
|
+
length(): number;
|
|
8
|
+
seek(pos: number): void;
|
|
9
9
|
position(): number;
|
|
10
|
-
setLength(len: number):
|
|
11
|
-
flush():
|
|
12
|
-
sync():
|
|
10
|
+
setLength(len: number): void;
|
|
11
|
+
flush(): void;
|
|
12
|
+
sync(): void;
|
|
13
13
|
}
|
|
14
14
|
declare class RandomAccessMemory implements DataReader, DataWriter {
|
|
15
15
|
private buffer;
|
|
@@ -23,14 +23,14 @@ declare class RandomAccessMemory implements DataReader, DataWriter {
|
|
|
23
23
|
setLength(len: number): void;
|
|
24
24
|
reset(): void;
|
|
25
25
|
toByteArray(): Uint8Array;
|
|
26
|
-
write(data: Uint8Array):
|
|
27
|
-
writeByte(v: number):
|
|
28
|
-
writeShort(v: number):
|
|
29
|
-
writeLong(v: number):
|
|
30
|
-
readFully(b: Uint8Array):
|
|
31
|
-
readByte():
|
|
32
|
-
readShort():
|
|
33
|
-
readInt():
|
|
34
|
-
readLong():
|
|
26
|
+
write(data: Uint8Array): void;
|
|
27
|
+
writeByte(v: number): void;
|
|
28
|
+
writeShort(v: number): void;
|
|
29
|
+
writeLong(v: number): void;
|
|
30
|
+
readFully(b: Uint8Array): void;
|
|
31
|
+
readByte(): number;
|
|
32
|
+
readShort(): number;
|
|
33
|
+
readInt(): number;
|
|
34
|
+
readLong(): number;
|
|
35
35
|
}
|
|
36
36
|
export {};
|
package/dist/core.d.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
export interface DataReader {
|
|
2
|
-
readFully(buffer: Uint8Array):
|
|
3
|
-
readByte():
|
|
4
|
-
readShort():
|
|
5
|
-
readInt():
|
|
6
|
-
readLong():
|
|
2
|
+
readFully(buffer: Uint8Array): void;
|
|
3
|
+
readByte(): number;
|
|
4
|
+
readShort(): number;
|
|
5
|
+
readInt(): number;
|
|
6
|
+
readLong(): number;
|
|
7
7
|
}
|
|
8
8
|
export interface DataWriter {
|
|
9
|
-
write(buffer: Uint8Array):
|
|
10
|
-
writeByte(v: number):
|
|
11
|
-
writeShort(v: number):
|
|
12
|
-
writeLong(v: number):
|
|
9
|
+
write(buffer: Uint8Array): void;
|
|
10
|
+
writeByte(v: number): void;
|
|
11
|
+
writeShort(v: number): void;
|
|
12
|
+
writeLong(v: number): void;
|
|
13
13
|
}
|
|
14
14
|
export interface Core {
|
|
15
15
|
reader(): DataReader;
|
|
16
16
|
writer(): DataWriter;
|
|
17
|
-
length():
|
|
18
|
-
seek(pos: number):
|
|
17
|
+
length(): number;
|
|
18
|
+
seek(pos: number): void;
|
|
19
19
|
position(): number;
|
|
20
|
-
setLength(len: number):
|
|
21
|
-
flush():
|
|
22
|
-
sync():
|
|
20
|
+
setLength(len: number): void;
|
|
21
|
+
flush(): void;
|
|
22
|
+
sync(): void;
|
|
23
23
|
}
|