xitdb 0.2.0 → 0.4.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/LICENSE +21 -0
- package/README.md +33 -18
- package/package.json +7 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (Expat)
|
|
2
|
+
|
|
3
|
+
Copyright (c) contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
xitdb is an immutable database written in TypeScript
|
|
3
|
+
<br/>
|
|
4
|
+
<br/>
|
|
5
|
+
<b>Choose your flavor:</b>
|
|
6
|
+
<a href="https://github.com/radarroark/xitdb">Zig</a> |
|
|
7
|
+
<a href="https://github.com/radarroark/xitdb-java">Java</a> |
|
|
8
|
+
<a href="https://github.com/codeboost/xitdb-clj">Clojure</a> |
|
|
9
|
+
<a href="https://github.com/radarroark/xitdb-ts">TypeScript</a>
|
|
10
|
+
</p>
|
|
2
11
|
|
|
3
12
|
* Each transaction efficiently creates a new "copy" of the database, and past copies can still be read from.
|
|
4
13
|
* It supports writing to a file as well as purely in-memory use.
|
|
5
14
|
* No query engine of any kind. You just write data structures (primarily an `ArrayList` and `HashMap`) that can be nested arbitrarily.
|
|
6
15
|
* No dependencies besides the JavaScript standard library.
|
|
7
|
-
*
|
|
16
|
+
* It is available [on npm](https://www.npmjs.com/package/xitdb).
|
|
8
17
|
|
|
9
18
|
This database was originally made for the [xit version control system](https://github.com/radarroark/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/radarroark/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.
|
|
10
19
|
|
|
@@ -82,18 +91,18 @@ const moment = await ReadHashMap.create(momentCursor!);
|
|
|
82
91
|
// the cursor to "foo" and then calling readBytes on it
|
|
83
92
|
const fooCursor = await moment.getCursorByString('foo');
|
|
84
93
|
const fooValue = await fooCursor!.readBytes(MAX_READ_BYTES);
|
|
85
|
-
|
|
94
|
+
expect(new TextDecoder().decode(fooValue)).toBe('foo');
|
|
86
95
|
|
|
87
96
|
// to get the "fruits" list, we get the cursor to it and
|
|
88
97
|
// then pass it to the ReadArrayList constructor
|
|
89
98
|
const fruitsCursor = await moment.getCursorByString('fruits');
|
|
90
99
|
const fruits = new ReadArrayList(fruitsCursor!);
|
|
91
|
-
|
|
100
|
+
expect(await fruits.count()).toBe(3);
|
|
92
101
|
|
|
93
102
|
// now we can get the first item from the fruits list and read it
|
|
94
103
|
const appleCursor = await fruits.getCursor(0);
|
|
95
104
|
const appleValue = await appleCursor!.readBytes(MAX_READ_BYTES);
|
|
96
|
-
|
|
105
|
+
expect(new TextDecoder().decode(appleValue)).toBe('apple');
|
|
97
106
|
```
|
|
98
107
|
|
|
99
108
|
## Initializing a Database
|
|
@@ -142,7 +151,7 @@ Then, you can read it like this:
|
|
|
142
151
|
```typescript
|
|
143
152
|
const randomNumberCursor = await moment.getCursorByString('random-number');
|
|
144
153
|
const randomNumber = await randomNumberCursor!.readBytesObject(MAX_READ_BYTES);
|
|
145
|
-
|
|
154
|
+
expect(new TextDecoder().decode(randomNumber.formatTag!)).toBe('bi');
|
|
146
155
|
const randomBigInt = randomNumber.value;
|
|
147
156
|
```
|
|
148
157
|
|
|
@@ -176,12 +185,12 @@ const moment = await ReadHashMap.create(momentCursor!);
|
|
|
176
185
|
// the food list includes the fruits
|
|
177
186
|
const foodCursor = await moment.getCursorByString('food');
|
|
178
187
|
const food = new ReadArrayList(foodCursor!);
|
|
179
|
-
|
|
188
|
+
expect(await food.count()).toBe(6);
|
|
180
189
|
|
|
181
190
|
// ...but the fruits list hasn't been changed
|
|
182
191
|
const fruitsCursor = await moment.getCursorByString('fruits');
|
|
183
192
|
const fruits = new ReadArrayList(fruitsCursor!);
|
|
184
|
-
|
|
193
|
+
expect(await fruits.count()).toBe(3);
|
|
185
194
|
```
|
|
186
195
|
|
|
187
196
|
Before we continue, let's save the latest history index, so we can revert back to this moment of the database later:
|
|
@@ -217,12 +226,12 @@ const moment = await ReadHashMap.create(momentCursor!);
|
|
|
217
226
|
// the cities list contains all four
|
|
218
227
|
const citiesCursor = await moment.getCursorByString('cities');
|
|
219
228
|
const cities = new ReadArrayList(citiesCursor!);
|
|
220
|
-
|
|
229
|
+
expect(await cities.count()).toBe(4);
|
|
221
230
|
|
|
222
231
|
// ..but so does big-cities! we did not intend to mutate this
|
|
223
232
|
const bigCitiesCursor = await moment.getCursorByString('big-cities');
|
|
224
233
|
const bigCities = new ReadArrayList(bigCitiesCursor!);
|
|
225
|
-
|
|
234
|
+
expect(await bigCities.count()).toBe(4);
|
|
226
235
|
```
|
|
227
236
|
|
|
228
237
|
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.
|
|
@@ -263,12 +272,12 @@ const moment = await ReadHashMap.create(momentCursor!);
|
|
|
263
272
|
// the cities list contains all four
|
|
264
273
|
const citiesCursor = await moment.getCursorByString('cities');
|
|
265
274
|
const cities = new ReadArrayList(citiesCursor!);
|
|
266
|
-
|
|
275
|
+
expect(await cities.count()).toBe(4);
|
|
267
276
|
|
|
268
277
|
// and big-cities only contains the original two
|
|
269
278
|
const bigCitiesCursor = await moment.getCursorByString('big-cities');
|
|
270
279
|
const bigCities = new ReadArrayList(bigCitiesCursor!);
|
|
271
|
-
|
|
280
|
+
expect(await bigCities.count()).toBe(2);
|
|
272
281
|
```
|
|
273
282
|
|
|
274
283
|
## Large Byte Arrays
|
|
@@ -291,10 +300,16 @@ To read a byte array incrementally, get a reader from a cursor:
|
|
|
291
300
|
```typescript
|
|
292
301
|
const longTextCursor = await moment.getCursorByString('long-text');
|
|
293
302
|
const cursorReader = await longTextCursor!.reader();
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
let lineCount = 0, line: number[] = [];
|
|
304
|
+
const buf = new Uint8Array(1024);
|
|
305
|
+
for (let n; (n = await cursorReader.read(buf)) > 0; ) {
|
|
306
|
+
for (let i = 0; i < n; i++) {
|
|
307
|
+
if (buf[i] === 0x0A) { lineCount++; line = []; }
|
|
308
|
+
else line.push(buf[i]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (line.length > 0) lineCount++;
|
|
312
|
+
expect(lineCount).toBe(50);
|
|
298
313
|
```
|
|
299
314
|
|
|
300
315
|
## Iterators
|
|
@@ -358,7 +373,7 @@ The size of the hash in bytes will be stored in the database's header. If you tr
|
|
|
358
373
|
```typescript
|
|
359
374
|
await core.seek(0);
|
|
360
375
|
const header = await Header.read(core);
|
|
361
|
-
|
|
376
|
+
expect(header.hashSize).toBe(20);
|
|
362
377
|
```
|
|
363
378
|
|
|
364
379
|
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:
|
|
@@ -372,7 +387,7 @@ The hash id is only written to the database header when it is first initialized.
|
|
|
372
387
|
```typescript
|
|
373
388
|
await core.seek(0);
|
|
374
389
|
const header = await Header.read(core);
|
|
375
|
-
|
|
390
|
+
expect(Hasher.idToString(header.hashId)).toBe("sha1");
|
|
376
391
|
```
|
|
377
392
|
|
|
378
393
|
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:
|
package/package.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xitdb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "An immutable database",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/radarroark/xitdb-ts.git"
|
|
9
|
+
},
|
|
4
10
|
"type": "module",
|
|
5
11
|
"main": "dist/index.js",
|
|
6
12
|
"types": "dist/index.d.ts",
|