@hashtree/dexie 0.1.0 → 0.1.3

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 ADDED
@@ -0,0 +1,49 @@
1
+ # @hashtree/dexie
2
+
3
+ IndexedDB storage adapter for hashtree using Dexie.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @hashtree/dexie
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { DexieStore } from '@hashtree/dexie';
15
+ import { HashTree } from '@hashtree/core';
16
+
17
+ const store = new DexieStore('my-hashtree-db');
18
+ const tree = new HashTree({ store });
19
+
20
+ // Store persists to IndexedDB
21
+ await tree.putFile(data);
22
+ ```
23
+
24
+ ## Features
25
+
26
+ - Persistent browser storage
27
+ - LRU eviction support
28
+ - Automatic schema migrations
29
+
30
+ ## API
31
+
32
+ ```typescript
33
+ const store = new DexieStore(dbName?: string);
34
+
35
+ await store.get(hash);
36
+ await store.put(hash, data);
37
+ await store.has(hash);
38
+ await store.delete(hash);
39
+ await store.keys();
40
+ await store.clear();
41
+ await store.count();
42
+ await store.totalBytes();
43
+ await store.evict(maxBytes);
44
+ store.close();
45
+ ```
46
+
47
+ ## License
48
+
49
+ MIT
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export declare class DexieStore implements Store {
24
24
  count(): Promise<number>;
25
25
  /**
26
26
  * Get total bytes stored
27
+ * Uses cursor to avoid loading all blobs into memory at once
27
28
  */
28
29
  totalBytes(): Promise<number>;
29
30
  /**
@@ -40,3 +41,4 @@ export declare class DexieStore implements Store {
40
41
  */
41
42
  static deleteDatabase(dbName?: string): Promise<void>;
42
43
  }
44
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAkClD;;;GAGG;AACH,qBAAa,UAAW,YAAW,KAAK;IACtC,OAAO,CAAC,EAAE,CAAa;gBAEX,MAAM,GAAE,MAAmB;IAIjC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IAYnD,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAyB3C,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAYjC,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAe1C;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAW7B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAS9B;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAanC;;;OAGG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4B9C;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;WACU,cAAc,CAAC,MAAM,GAAE,MAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;CAGxE"}
package/dist/index.js CHANGED
@@ -36,7 +36,8 @@ export class DexieStore {
36
36
  async put(hash, data) {
37
37
  const hashHex = toHex(hash);
38
38
  try {
39
- await this.db.blobs.put({ hashHex, data: new Uint8Array(data), lastAccess: Date.now() });
39
+ // Store directly - IDB will clone the data internally
40
+ await this.db.blobs.put({ hashHex, data, lastAccess: Date.now() });
40
41
  return true;
41
42
  }
42
43
  catch (e) {
@@ -55,9 +56,13 @@ export class DexieStore {
55
56
  // Update lastAccess timestamp for LRU tracking (fire-and-forget)
56
57
  // We need to re-put the entry to avoid fake-indexeddb corruption issues with partial updates
57
58
  this.db.blobs.put({ ...entry, lastAccess: Date.now() }).catch(() => { });
58
- // Use slice to ensure we get exact data without extra buffer bytes
59
- // IndexedDB can store the entire backing ArrayBuffer of a view
59
+ // Return directly - IDB returns a fresh copy already
60
+ // Only slice if the view doesn't match the buffer (rare edge case)
60
61
  const data = entry.data;
62
+ if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength) {
63
+ return data;
64
+ }
65
+ // Rare: view is a subset of a larger buffer, need to copy
61
66
  return new Uint8Array(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
62
67
  }
63
68
  catch (e) {
@@ -68,8 +73,9 @@ export class DexieStore {
68
73
  async has(hash) {
69
74
  const hashHex = toHex(hash);
70
75
  try {
71
- const entry = await this.db.blobs.get(hashHex);
72
- return entry !== undefined;
76
+ // Use count with where clause - doesn't load the blob data
77
+ const count = await this.db.blobs.where('hashHex').equals(hashHex).count();
78
+ return count > 0;
73
79
  }
74
80
  catch (e) {
75
81
  console.error('[DexieStore] has error:', e);
@@ -96,8 +102,9 @@ export class DexieStore {
96
102
  */
97
103
  async keys() {
98
104
  try {
99
- const hexKeys = await this.db.blobs.toCollection().primaryKeys();
100
- return hexKeys.map(hex => fromHex(hex));
105
+ // Only fetch the primary keys, not the blob data
106
+ const hashHexes = await this.db.blobs.toCollection().primaryKeys();
107
+ return hashHexes.map(hex => fromHex(hex));
101
108
  }
102
109
  catch (e) {
103
110
  console.error('[DexieStore] keys error:', e);
@@ -129,12 +136,13 @@ export class DexieStore {
129
136
  }
130
137
  /**
131
138
  * Get total bytes stored
139
+ * Uses cursor to avoid loading all blobs into memory at once
132
140
  */
133
141
  async totalBytes() {
134
142
  try {
135
143
  let total = 0;
136
- await this.db.blobs.each(e => {
137
- total += e.data.length;
144
+ await this.db.blobs.each(entry => {
145
+ total += entry.data.byteLength;
138
146
  });
139
147
  return total;
140
148
  }
@@ -161,7 +169,7 @@ export class DexieStore {
161
169
  if (bytesRemoved >= targetRemoval)
162
170
  break;
163
171
  await this.db.blobs.delete(entry.hashHex);
164
- bytesRemoved += entry.data.length;
172
+ bytesRemoved += entry.data.byteLength;
165
173
  entriesRemoved++;
166
174
  }
167
175
  console.log(`[DexieStore] Evicted ${entriesRemoved} entries (${bytesRemoved} bytes)`);
@@ -1 +1,2 @@
1
1
  export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
@@ -1 +1,2 @@
1
1
  import 'fake-indexeddb/auto';
2
+ //# sourceMappingURL=test-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-setup.d.ts","sourceRoot":"","sources":["../src/test-setup.ts"],"names":[],"mappings":"AACA,OAAO,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,10 +1,6 @@
1
1
  {
2
2
  "name": "@hashtree/dexie",
3
- "version": "0.1.0",
4
- "license": "MIT",
5
- "publishConfig": {
6
- "access": "public"
7
- },
3
+ "version": "0.1.3",
8
4
  "description": "Dexie-based IndexedDB store for hashtree",
9
5
  "type": "module",
10
6
  "main": "dist/index.js",
@@ -18,19 +14,28 @@
18
14
  "files": [
19
15
  "dist"
20
16
  ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "hashtree",
22
+ "dexie",
23
+ "indexeddb",
24
+ "storage"
25
+ ],
26
+ "author": "Martti Malmi",
27
+ "license": "MIT",
21
28
  "dependencies": {
22
- "dexie": "^4.0.11",
23
- "@hashtree/core": "0.1.0"
29
+ "dexie": "^4.2.1",
30
+ "@hashtree/core": "0.1.3"
24
31
  },
25
32
  "devDependencies": {
26
- "fake-indexeddb": "^6.0.0",
27
- "jsdom": "^26.0.0",
28
- "typescript": "^5.8.3",
29
- "vitest": "^2.1.8"
33
+ "typescript": "^5.3.0",
34
+ "vitest": "^2.0.0"
30
35
  },
31
36
  "scripts": {
32
37
  "build": "tsc",
33
- "clean": "rm -rf dist",
34
- "test": "vitest run"
38
+ "test": "vitest run",
39
+ "test:watch": "vitest"
35
40
  }
36
41
  }