xitdb 0.12.0 → 0.14.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.
Files changed (63) hide show
  1. package/README.md +120 -118
  2. package/dist/core-buffered-file.d.ts +23 -25
  3. package/dist/core-buffered-file.js +182 -0
  4. package/dist/core-file.d.ts +8 -10
  5. package/dist/core-file.js +105 -0
  6. package/dist/core-memory.d.ts +15 -15
  7. package/dist/core-memory.js +152 -0
  8. package/dist/core.d.ts +14 -14
  9. package/dist/core.js +1 -0
  10. package/dist/database.d.ts +204 -80
  11. package/dist/database.js +3047 -0
  12. package/dist/exceptions.d.ts +4 -0
  13. package/dist/exceptions.js +62 -0
  14. package/dist/hasher.d.ts +2 -1
  15. package/dist/hasher.js +49 -0
  16. package/dist/index.d.ts +30 -26
  17. package/dist/index.js +35 -3986
  18. package/dist/read-array-list.d.ts +8 -8
  19. package/dist/read-array-list.js +35 -0
  20. package/dist/read-counted-hash-map.d.ts +3 -3
  21. package/dist/read-counted-hash-map.js +19 -0
  22. package/dist/read-counted-hash-set.d.ts +3 -3
  23. package/dist/read-counted-hash-set.js +19 -0
  24. package/dist/read-cursor.d.ts +29 -23
  25. package/dist/read-cursor.js +577 -0
  26. package/dist/read-hash-map.d.ts +22 -22
  27. package/dist/read-hash-map.js +65 -0
  28. package/dist/read-hash-set.d.ts +13 -13
  29. package/dist/read-hash-set.js +47 -0
  30. package/dist/read-linked-array-list.d.ts +8 -8
  31. package/dist/read-linked-array-list.js +35 -0
  32. package/dist/read-sorted-map.d.ts +21 -0
  33. package/dist/read-sorted-map.js +73 -0
  34. package/dist/read-sorted-set.d.ts +19 -0
  35. package/dist/read-sorted-set.js +65 -0
  36. package/dist/slot-pointer.d.ts +1 -1
  37. package/dist/slot-pointer.js +11 -0
  38. package/dist/slot.d.ts +2 -2
  39. package/dist/slot.js +41 -0
  40. package/dist/slotted.d.ts +1 -1
  41. package/dist/slotted.js +1 -0
  42. package/dist/tag.d.ts +3 -1
  43. package/dist/tag.js +25 -0
  44. package/dist/write-array-list.d.ts +13 -14
  45. package/dist/write-array-list.js +42 -0
  46. package/dist/write-counted-hash-map.d.ts +4 -5
  47. package/dist/write-counted-hash-map.js +18 -0
  48. package/dist/write-counted-hash-set.d.ts +4 -5
  49. package/dist/write-counted-hash-set.js +18 -0
  50. package/dist/write-cursor.d.ts +15 -15
  51. package/dist/write-cursor.js +124 -0
  52. package/dist/write-hash-map.d.ts +22 -23
  53. package/dist/write-hash-map.js +90 -0
  54. package/dist/write-hash-set.d.ts +16 -17
  55. package/dist/write-hash-set.js +59 -0
  56. package/dist/write-linked-array-list.d.ts +16 -17
  57. package/dist/write-linked-array-list.js +52 -0
  58. package/dist/write-sorted-map.d.ts +12 -0
  59. package/dist/write-sorted-map.js +37 -0
  60. package/dist/write-sorted-set.d.ts +10 -0
  61. package/dist/write-sorted-set.js +29 -0
  62. package/dist/writeable-data.js +68 -0
  63. package/package.json +6 -6
package/README.md CHANGED
@@ -17,6 +17,7 @@
17
17
  * Reads never block writes, and a database can be read from multiple threads/processes without locks.
18
18
  * No query engine of any kind. You just write data structures (primarily an `ArrayList` and `HashMap`) that can be nested arbitrarily.
19
19
  * No dependencies besides the JavaScript standard library.
20
+ * Fully synchronous API — no async/await needed.
20
21
  * Available [on npm](https://www.npmjs.com/package/xitdb).
21
22
 
22
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.
@@ -36,13 +37,13 @@ In this example, we create a new database, write some data in a transaction, and
36
37
 
37
38
  ```typescript
38
39
  // init the db
39
- using core = await CoreBufferedFile.create('main.db');
40
+ using core = new CoreBufferedFile('main.db');
40
41
  const hasher = new Hasher('SHA-1');
41
- const db = await Database.create(core, hasher);
42
+ const db = new Database(core, hasher);
42
43
 
43
44
  // to get the benefits of immutability, the top-level data structure
44
45
  // must be an ArrayList, so each transaction is stored as an item in it
45
- const history = await WriteArrayList.create(db.rootCursor());
46
+ const history = new WriteArrayList(db.rootCursor());
46
47
 
47
48
  // this is how a transaction is executed. we call history.appendContext,
48
49
  // providing it with the most recent copy of the db and a context
@@ -61,53 +62,53 @@ const history = await WriteArrayList.create(db.rootCursor());
61
62
  // {"name": "Alice", "age": 25},
62
63
  // {"name": "Bob", "age": 42}
63
64
  // ]}
64
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
65
- const moment = await WriteHashMap.create(cursor);
66
-
67
- await moment.put('foo', new Bytes('foo'));
68
- await moment.put('bar', new Bytes('bar'));
69
-
70
- const fruitsCursor = await moment.putCursor('fruits');
71
- const fruits = await WriteArrayList.create(fruitsCursor);
72
- await fruits.append(new Bytes('apple'));
73
- await fruits.append(new Bytes('pear'));
74
- await fruits.append(new Bytes('grape'));
75
-
76
- const peopleCursor = await moment.putCursor('people');
77
- const people = await WriteArrayList.create(peopleCursor);
78
-
79
- const aliceCursor = await people.appendCursor();
80
- const alice = await WriteHashMap.create(aliceCursor);
81
- await alice.put('name', new Bytes('Alice'));
82
- await alice.put('age', new Uint(25));
83
-
84
- const bobCursor = await people.appendCursor();
85
- const bob = await WriteHashMap.create(bobCursor);
86
- await bob.put('name', new Bytes('Bob'));
87
- await bob.put('age', new Uint(42));
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));
88
89
  });
89
90
 
90
91
  // get the most recent copy of the database, like a moment
91
92
  // in time. the -1 index will return the last index in the list.
92
- const momentCursor = await history.getCursor(-1);
93
+ const momentCursor = history.getCursor(-1);
93
94
  const moment = new ReadHashMap(momentCursor!);
94
95
 
95
96
  // we can read the value of "foo" from the map by getting
96
97
  // the cursor to "foo" and then calling readBytes on it
97
- const fooCursor = await moment.getCursor('foo');
98
- const fooValue = await fooCursor!.readBytes(MAX_READ_BYTES);
99
- expect(new TextDecoder().decode(fooValue)).toBe('foo');
98
+ const fooCursor = moment.getCursor('foo');
99
+ const fooValue = fooCursor!.readBytes(MAX_READ_BYTES);
100
+ assert.strictEqual(new TextDecoder().decode(fooValue), 'foo');
100
101
 
101
102
  // to get the "fruits" list, we get the cursor to it and
102
103
  // then pass it to the ReadArrayList constructor
103
- const fruitsCursor = await moment.getCursor('fruits');
104
+ const fruitsCursor = moment.getCursor('fruits');
104
105
  const fruits = new ReadArrayList(fruitsCursor!);
105
- expect(await fruits.count()).toBe(3);
106
+ assert.strictEqual(fruits.count(), 3);
106
107
 
107
108
  // now we can get the first item from the fruits list and read it
108
- const appleCursor = await fruits.getCursor(0);
109
- const appleValue = await appleCursor!.readBytes(MAX_READ_BYTES);
110
- expect(new TextDecoder().decode(appleValue)).toBe('apple');
109
+ const appleCursor = fruits.getCursor(0);
110
+ const appleValue = appleCursor!.readBytes(MAX_READ_BYTES);
111
+ assert.strictEqual(new TextDecoder().decode(appleValue), 'apple');
111
112
  ```
112
113
 
113
114
  ## Initializing a Database
@@ -131,8 +132,9 @@ In xitdb there are a variety of immutable data structures that you can nest arbi
131
132
  * `CountedHashMap` and `CountedHashSet` are just a `HashMap` and `HashSet` that maintain a count of their contents
132
133
  * `ArrayList` is a growable array
133
134
  * `LinkedArrayList` is like an `ArrayList` that can also be efficiently sliced and concatenated
135
+ * `SortedMap` and `SortedSet` are like a `HashMap` and `HashSet` where the keys are byte arrays kept in lexicographic order
134
136
 
135
- All data structures use the hash array mapped trie, invented by Phil Bagwell. The `LinkedArrayList` is based on his later work on RRB trees. These data structures were originally made immutable and widely available by Rich Hickey in Clojure. To my knowledge, they haven't been available in any open source database until xitdb.
137
+ The `Hash`-based data structures and the `Arraylist` use the hash array mapped trie, invented by Phil Bagwell (originally made immutable and widely available by Rich Hickey in Clojure). The `LinkedArrayList`, `SortedMap`, and `SortedSet` are based on a B-tree.
136
138
 
137
139
  There are also scalar types you can store in the above-mentioned data structures:
138
140
 
@@ -148,15 +150,15 @@ In xitdb, you can optionally store a format tag with a byte array. A format tag
148
150
  ```typescript
149
151
  const randomBytes = new Uint8Array(32);
150
152
  crypto.getRandomValues(randomBytes);
151
- await moment.put('random-number', new Bytes(randomBytes, new TextEncoder().encode('bi')));
153
+ moment.put('random-number', new Bytes(randomBytes, new TextEncoder().encode('bi')));
152
154
  ```
153
155
 
154
156
  Then, you can read it like this:
155
157
 
156
158
  ```typescript
157
- const randomNumberCursor = await moment.getCursor('random-number');
158
- const randomNumber = await randomNumberCursor!.readBytesObject(MAX_READ_BYTES);
159
- expect(new TextDecoder().decode(randomNumber.formatTag!)).toBe('bi');
159
+ const randomNumberCursor = moment.getCursor('random-number');
160
+ const randomNumber = randomNumberCursor!.readBytesObject(MAX_READ_BYTES);
161
+ assert.strictEqual(new TextDecoder().decode(randomNumber.formatTag!), 'bi');
160
162
  const randomBigInt = randomNumber.value;
161
163
  ```
162
164
 
@@ -167,76 +169,76 @@ There are many types you may want to store this way. Maybe an ISO-8601 date like
167
169
  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:
168
170
 
169
171
  ```typescript
170
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
171
- const moment = await WriteHashMap.create(cursor);
172
+ history.appendContext(history.getSlot(-1), (cursor) => {
173
+ const moment = new WriteHashMap(cursor);
172
174
 
173
- const fruitsCursor = await moment.getCursor('fruits');
175
+ const fruitsCursor = moment.getCursor('fruits');
174
176
  const fruits = new ReadArrayList(fruitsCursor!);
175
177
 
176
178
  // create a new key called "food" whose initial value is
177
179
  // based on the "fruits" list
178
- const foodCursor = await moment.putCursor('food');
179
- await foodCursor.write(fruits.slot());
180
+ const foodCursor = moment.putCursor('food');
181
+ foodCursor.write(fruits.slot());
180
182
 
181
- const food = await WriteArrayList.create(foodCursor);
182
- await food.append(new Bytes('eggs'));
183
- await food.append(new Bytes('rice'));
184
- await food.append(new Bytes('fish'));
183
+ const food = new WriteArrayList(foodCursor);
184
+ food.append(new Bytes('eggs'));
185
+ food.append(new Bytes('rice'));
186
+ food.append(new Bytes('fish'));
185
187
  });
186
188
 
187
- const momentCursor = await history.getCursor(-1);
189
+ const momentCursor = history.getCursor(-1);
188
190
  const moment = new ReadHashMap(momentCursor!);
189
191
 
190
192
  // the food list includes the fruits
191
- const foodCursor = await moment.getCursor('food');
193
+ const foodCursor = moment.getCursor('food');
192
194
  const food = new ReadArrayList(foodCursor!);
193
- expect(await food.count()).toBe(6);
195
+ assert.strictEqual(food.count(), 6);
194
196
 
195
197
  // ...but the fruits list hasn't been changed
196
- const fruitsCursor = await moment.getCursor('fruits');
198
+ const fruitsCursor = moment.getCursor('fruits');
197
199
  const fruits = new ReadArrayList(fruitsCursor!);
198
- expect(await fruits.count()).toBe(3);
200
+ assert.strictEqual(fruits.count(), 3);
199
201
  ```
200
202
 
201
203
  Before we continue, let's save the latest history index, so we can revert back to this moment of the database later:
202
204
 
203
205
  ```typescript
204
- const historyIndex = (await history.count()) - 1;
206
+ const historyIndex = history.count() - 1;
205
207
  ```
206
208
 
207
209
  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:
208
210
 
209
211
  ```typescript
210
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
211
- const moment = await WriteHashMap.create(cursor);
212
+ history.appendContext(history.getSlot(-1), (cursor) => {
213
+ const moment = new WriteHashMap(cursor);
212
214
 
213
- const bigCitiesCursor = await moment.putCursor('big-cities');
214
- const bigCities = await WriteArrayList.create(bigCitiesCursor);
215
- await bigCities.append(new Bytes('New York, NY'));
216
- await bigCities.append(new Bytes('Los Angeles, CA'));
215
+ const bigCitiesCursor = moment.putCursor('big-cities');
216
+ const bigCities = new WriteArrayList(bigCitiesCursor);
217
+ bigCities.append(new Bytes('New York, NY'));
218
+ bigCities.append(new Bytes('Los Angeles, CA'));
217
219
 
218
220
  // create a new key called "cities" whose initial value is
219
221
  // based on the "big-cities" list
220
- const citiesCursor = await moment.putCursor('cities');
221
- await citiesCursor.write(bigCities.slot());
222
+ const citiesCursor = moment.putCursor('cities');
223
+ citiesCursor.write(bigCities.slot());
222
224
 
223
- const cities = await WriteArrayList.create(citiesCursor);
224
- await cities.append(new Bytes('Charleston, SC'));
225
- await cities.append(new Bytes('Louisville, KY'));
225
+ const cities = new WriteArrayList(citiesCursor);
226
+ cities.append(new Bytes('Charleston, SC'));
227
+ cities.append(new Bytes('Louisville, KY'));
226
228
  });
227
229
 
228
- const momentCursor = await history.getCursor(-1);
230
+ const momentCursor = history.getCursor(-1);
229
231
  const moment = new ReadHashMap(momentCursor!);
230
232
 
231
233
  // the cities list contains all four
232
- const citiesCursor = await moment.getCursor('cities');
234
+ const citiesCursor = moment.getCursor('cities');
233
235
  const cities = new ReadArrayList(citiesCursor!);
234
- expect(await cities.count()).toBe(4);
236
+ assert.strictEqual(cities.count(), 4);
235
237
 
236
238
  // ..but so does big-cities! we did not intend to mutate this
237
- const bigCitiesCursor = await moment.getCursor('big-cities');
239
+ const bigCitiesCursor = moment.getCursor('big-cities');
238
240
  const bigCities = new ReadArrayList(bigCitiesCursor!);
239
- expect(await bigCities.count()).toBe(4);
241
+ assert.strictEqual(bigCities.count(), 4);
240
242
  ```
241
243
 
242
244
  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.
@@ -244,45 +246,45 @@ The reason that `big-cities` was mutated is because all data in a given transact
244
246
  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:
245
247
 
246
248
  ```typescript
247
- await history.append((await history.getSlot(historyIndex))!);
249
+ history.append(history.getSlot(historyIndex)!);
248
250
  ```
249
251
 
250
252
  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:
251
253
 
252
254
  ```typescript
253
- await history.appendContext(await history.getSlot(-1), async (cursor) => {
254
- const moment = await WriteHashMap.create(cursor);
255
+ history.appendContext(history.getSlot(-1), (cursor) => {
256
+ const moment = new WriteHashMap(cursor);
255
257
 
256
- const bigCitiesCursor = await moment.putCursor('big-cities');
257
- const bigCities = await WriteArrayList.create(bigCitiesCursor);
258
- await bigCities.append(new Bytes('New York, NY'));
259
- await bigCities.append(new Bytes('Los Angeles, CA'));
258
+ const bigCitiesCursor = moment.putCursor('big-cities');
259
+ const bigCities = new WriteArrayList(bigCitiesCursor);
260
+ bigCities.append(new Bytes('New York, NY'));
261
+ bigCities.append(new Bytes('Los Angeles, CA'));
260
262
 
261
263
  // freeze here, so big-cities won't be mutated
262
264
  cursor.db.freeze();
263
265
 
264
266
  // create a new key called "cities" whose initial value is
265
267
  // based on the "big-cities" list
266
- const citiesCursor = await moment.putCursor('cities');
267
- await citiesCursor.write(bigCities.slot());
268
+ const citiesCursor = moment.putCursor('cities');
269
+ citiesCursor.write(bigCities.slot());
268
270
 
269
- const cities = await WriteArrayList.create(citiesCursor);
270
- await cities.append(new Bytes('Charleston, SC'));
271
- await cities.append(new Bytes('Louisville, KY'));
271
+ const cities = new WriteArrayList(citiesCursor);
272
+ cities.append(new Bytes('Charleston, SC'));
273
+ cities.append(new Bytes('Louisville, KY'));
272
274
  });
273
275
 
274
- const momentCursor = await history.getCursor(-1);
276
+ const momentCursor = history.getCursor(-1);
275
277
  const moment = new ReadHashMap(momentCursor!);
276
278
 
277
279
  // the cities list contains all four
278
- const citiesCursor = await moment.getCursor('cities');
280
+ const citiesCursor = moment.getCursor('cities');
279
281
  const cities = new ReadArrayList(citiesCursor!);
280
- expect(await cities.count()).toBe(4);
282
+ assert.strictEqual(cities.count(), 4);
281
283
 
282
284
  // and big-cities only contains the original two
283
- const bigCitiesCursor = await moment.getCursor('big-cities');
285
+ const bigCitiesCursor = moment.getCursor('big-cities');
284
286
  const bigCities = new ReadArrayList(bigCitiesCursor!);
285
- expect(await bigCities.count()).toBe(2);
287
+ assert.strictEqual(bigCities.count(), 2);
286
288
  ```
287
289
 
288
290
  ## Large Byte Arrays
@@ -290,12 +292,12 @@ expect(await bigCities.count()).toBe(2);
290
292
  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:
291
293
 
292
294
  ```typescript
293
- const longTextCursor = await moment.putCursor('long-text');
294
- const cursorWriter = await longTextCursor.writer();
295
+ const longTextCursor = moment.putCursor('long-text');
296
+ const cursorWriter = longTextCursor.writer();
295
297
  for (let i = 0; i < 50; i++) {
296
- await cursorWriter.write(new TextEncoder().encode('hello, world\n'));
298
+ cursorWriter.write(new TextEncoder().encode('hello, world\n'));
297
299
  }
298
- await cursorWriter.finish(); // remember to call this!
300
+ cursorWriter.finish(); // remember to call this!
299
301
  ```
300
302
 
301
303
  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`.
@@ -303,18 +305,18 @@ If you need to set a format tag for the byte array, put it in the `formatTag` fi
303
305
  To read a byte array incrementally, get a reader from a cursor:
304
306
 
305
307
  ```typescript
306
- const longTextCursor = await moment.getCursor('long-text');
307
- const cursorReader = await longTextCursor!.reader();
308
+ const longTextCursor = moment.getCursor('long-text');
309
+ const cursorReader = longTextCursor!.reader();
308
310
  let lineCount = 0, line: number[] = [];
309
311
  const buf = new Uint8Array(1024);
310
- for (let n; (n = await cursorReader.read(buf)) > 0; ) {
312
+ for (let n; (n = cursorReader.read(buf)) > 0; ) {
311
313
  for (let i = 0; i < n; i++) {
312
314
  if (buf[i] === 0x0A) { lineCount++; line = []; }
313
315
  else line.push(buf[i]);
314
316
  }
315
317
  }
316
318
  if (line.length > 0) lineCount++;
317
- expect(lineCount).toBe(50);
319
+ assert.strictEqual(lineCount, 50);
318
320
  ```
319
321
 
320
322
  ## Iterators
@@ -322,24 +324,24 @@ expect(lineCount).toBe(50);
322
324
  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:
323
325
 
324
326
  ```typescript
325
- const peopleCursor = await moment.getCursor('people');
327
+ const peopleCursor = moment.getCursor('people');
326
328
  const people = new ReadArrayList(peopleCursor!);
327
329
 
328
- const peopleIter = await people.iterator();
329
- while (await peopleIter.hasNext()) {
330
- const personCursor = await peopleIter.next();
330
+ const peopleIter = people.iterator();
331
+ while (peopleIter.hasNext()) {
332
+ const personCursor = peopleIter.next();
331
333
  const person = new ReadHashMap(personCursor!);
332
- const personIter = await person.iterator();
333
- while (await personIter.hasNext()) {
334
- const kvPairCursor = await personIter.next();
335
- const kvPair = await kvPairCursor!.readKeyValuePair();
334
+ const personIter = person.iterator();
335
+ while (personIter.hasNext()) {
336
+ const kvPairCursor = personIter.next();
337
+ const kvPair = kvPairCursor!.readKeyValuePair();
336
338
 
337
- const key = new TextDecoder().decode(await kvPair.keyCursor.readBytes(MAX_READ_BYTES));
339
+ const key = new TextDecoder().decode(kvPair.keyCursor.readBytes(MAX_READ_BYTES));
338
340
 
339
341
  switch (kvPair.valueCursor.slot().tag) {
340
342
  case Tag.SHORT_BYTES:
341
343
  case Tag.BYTES:
342
- console.log(`${key}: ${new TextDecoder().decode(await kvPair.valueCursor.readBytes(MAX_READ_BYTES))}`);
344
+ console.log(`${key}: ${new TextDecoder().decode(kvPair.valueCursor.readBytes(MAX_READ_BYTES))}`);
343
345
  break;
344
346
  case Tag.UINT:
345
347
  console.log(`${key}: ${kvPair.valueCursor.readUint()}`);
@@ -366,17 +368,17 @@ The hashing data structures will create the hash for you when you call methods l
366
368
  When initializing a database, you tell xitdb how to hash with the `Hasher`. If you're using SHA-1, it will look like this:
367
369
 
368
370
  ```typescript
369
- using core = await CoreBufferedFile.create('main.db');
371
+ using core = new CoreBufferedFile('main.db');
370
372
  const hasher = new Hasher('SHA-1');
371
- const db = await Database.create(core, hasher);
373
+ const db = new Database(core, hasher);
372
374
  ```
373
375
 
374
376
  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:
375
377
 
376
378
  ```typescript
377
- await core.seek(0);
378
- const header = await Header.read(core);
379
- expect(header.hashSize).toBe(20);
379
+ core.seek(0);
380
+ const header = Header.read(core);
381
+ assert.strictEqual(header.hashSize, 20);
380
382
  ```
381
383
 
382
384
  The hash size alone does not disambiguate hashing algorithms, though. In addition, xitdb reserves four bytes in the header that you can use to put the name of the algorithm. You must provide it in the `Hasher` constructor:
@@ -388,9 +390,9 @@ const hasher = new Hasher('SHA-1', Hasher.stringToId('sha1'));
388
390
  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:
389
391
 
390
392
  ```typescript
391
- await core.seek(0);
392
- const header = await Header.read(core);
393
- expect(Hasher.idToString(header.hashId)).toBe("sha1");
393
+ core.seek(0);
394
+ const header = Header.read(core);
395
+ assert.strictEqual(Hasher.idToString(header.hashId), "sha1");
394
396
  ```
395
397
 
396
398
  If you want to use SHA-256, I recommend using `sha2` as the hash id. You can then distinguish between SHA-256 and SHA-512 using the hash size, like this:
@@ -425,12 +427,12 @@ switch (hashIdStr) {
425
427
  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.
426
428
 
427
429
  ```typescript
428
- using compactCore = await CoreBufferedFile.create('compact.db');
429
- const compactDb = await db.compact(compactCore);
430
+ using compactCore = new CoreBufferedFile('compact.db');
431
+ const compactDb = db.compact(compactCore);
430
432
 
431
433
  // read from the new compacted db
432
434
  const history = new ReadArrayList(compactDb.rootCursor());
433
- expect(await history.count()).toBe(1);
435
+ assert.strictEqual(history.count(), 1);
434
436
  ```
435
437
 
436
438
  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.
@@ -1,17 +1,16 @@
1
- import type { Core, DataReader, DataWriter } from './core';
2
- import { CoreFile } from './core-file';
1
+ import type { Core, DataReader, DataWriter } from './core.js';
2
+ import { CoreFile } from './core-file.js';
3
3
  export declare class CoreBufferedFile implements Core {
4
4
  file: RandomAccessBufferedFile;
5
- constructor(file: RandomAccessBufferedFile);
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(): Promise<number>;
10
- seek(pos: number): Promise<void>;
8
+ length(): number;
9
+ seek(pos: number): void;
11
10
  position(): number;
12
- setLength(len: number): Promise<void>;
13
- flush(): Promise<void>;
14
- sync(): Promise<void>;
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
- private constructor();
24
- static create(filePath: string, bufferSize?: number): Promise<RandomAccessBufferedFile>;
25
- seek(pos: number): Promise<void>;
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): Promise<void>;
29
- flush(): Promise<void>;
30
- sync(): Promise<void>;
31
- write(buffer: Uint8Array): Promise<void>;
32
- writeByte(v: number): Promise<void>;
33
- writeShort(v: number): Promise<void>;
34
- writeLong(v: number): Promise<void>;
35
- readFully(buffer: Uint8Array): Promise<void>;
36
- readByte(): Promise<number>;
37
- readShort(): Promise<number>;
38
- readInt(): Promise<number>;
39
- readLong(): Promise<number>;
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 {};