@wovin/storage-fs 0.1.36 → 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.
@@ -1,2 +1,2 @@
1
1
  import{open as S}from"lmdb";import{Logger as c}from"besonders-logger";var{DEBUG:s,VERBOSE:f,ERROR:h}=c.setup(c.INFO),l=class d{root;applogs;threadIndex;blocks;static async init(t){s(`[StorageFs.init] ENTER - path=${t}`),s("[StorageFs.init] Opening LMDB root database");let o=S({path:t,compression:!0});s("[StorageFs.init] LMDB root database opened"),s("[StorageFs.init] Opening applogs DB");let r=o.openDB({name:"applogs"});s("[StorageFs.init] Applogs DB opened"),s("[StorageFs.init] Opening threadIndex DB");let e=o.openDB({name:"thread_index",dupSort:!0});s("[StorageFs.init] ThreadIndex DB opened"),s("[StorageFs.init] Opening blocks DB");let n=o.openDB({name:"blocks"});s("[StorageFs.init] Blocks DB opened"),s("[StorageFs.init] Creating StorageFs instance");let p=new d(o,r,e,n);return s("[StorageFs.init] EXIT - StorageFs instance created successfully"),p}constructor(t,o,r,e){this.root=t,this.applogs=o,this.threadIndex=r,this.blocks=e}async storeApplogs(t,o){let r=Date.now();s("[StorageFs.storeApplogs] ===== ENTER ====="),s(`[StorageFs.storeApplogs] threadId: ${t}`),s(`[StorageFs.storeApplogs] applogs.length: ${o.length}`);let e=0;s("[StorageFs.storeApplogs] About to start transaction");let n=Date.now();try{this.root.transactionSync(()=>{for(let g of o){let i=g.cid;if(this.applogs.doesExist(i)){this.threadIndex.doesExist(t,i)||this.threadIndex.put(t,i);continue}this.applogs.put(i,JSON.stringify(g)),this.threadIndex.put(t,i),e++}});let a=Date.now()-n;s(`[StorageFs.storeApplogs] Transaction completed in ${a}ms`)}catch(a){let g=Date.now()-n;throw h(`[StorageFs.storeApplogs] Transaction error after ${g}ms:`,a),a}let p=Date.now()-r;return s(`[StorageFs.storeApplogs] ===== SUCCESS in ${p}ms, stored ${e} applogs`),e}getApplogs(t){let o=[],r=this.threadIndex.getValues(t);for(let e of r){let n=this.applogs.get(e);n&&o.push(JSON.parse(n))}return o}getApplogByCid(t){let o=typeof t=="string"?t:t.toString(),r=this.applogs.get(o);return r?JSON.parse(r):null}hasApplog(t){let o=typeof t=="string"?t:t.toString();return this.applogs.doesExist(o)}async get(t){let o=this.blocks.get(t.toString());if(!o)throw new Error(`Block not found: ${t}`);return o}async put(t,o){await this.blocks.put(t.toString(),o)}async has(t){return this.blocks.doesExist(t.toString())}listThreads(){let t=new Set;for(let{key:o}of this.threadIndex.getRange({}))t.add(o);return Array.from(t)}async deleteThread(t){this.root.transactionSync(()=>{let o=Array.from(this.threadIndex.getValues(t));for(let r of o)this.threadIndex.remove(t,r)}),s(`[StorageFs] Deleted thread "${t}"`)}getThreadApplogCount(t){return this.threadIndex.getValuesCount(t)}isApplogInThread(t,o){let r=typeof o=="string"?o:o.toString();return this.threadIndex.doesExist(t,r)}close(){this.root.close()}};export{l as LmdbConnector,l as StorageFs};
2
- //# sourceMappingURL=index.min.js.map
2
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "@wovin/storage-fs",
3
- "version": "0.1.36",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
- "main": "./dist/index.min.js",
6
- "module": "./dist/index.min.js",
7
- "browser": "./dist/index.min.js",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "browser": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "import": "./dist/index.min.js",
11
+ "import": "./dist/index.js",
12
12
  "types": "./dist/index.d.ts"
13
13
  }
14
14
  },
15
15
  "files": [
16
- "./dist/"
16
+ "./dist/",
17
+ "./src/"
17
18
  ],
18
19
  "publishConfig": {
19
20
  "access": "public"
@@ -26,7 +27,7 @@
26
27
  "besonders-logger": "1.0.1",
27
28
  "lmdb": "^3.0.0",
28
29
  "multiformats": "^13.0.1",
29
- "@wovin/core": "^0.1.36"
30
+ "@wovin/core": "^0.2.0"
30
31
  },
31
32
  "devDependencies": {
32
33
  "concurrently": "^8.2.2",
package/src/index.ts ADDED
@@ -0,0 +1,210 @@
1
+ import { open, RootDatabase, Database } from 'lmdb'
2
+ import type { Applog, CidString } from '@wovin/core'
3
+ import type { BlockStore } from '@wovin/core/blockstore'
4
+ import { Logger } from 'besonders-logger'
5
+ import { CID } from 'multiformats/cid'
6
+
7
+ const { DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO)
8
+
9
+ /**
10
+ * File-system based storage using LMDB.
11
+ * Implements BlockStore for use with wovin block caching and snapshot chain walking.
12
+ */
13
+ export class StorageFs implements BlockStore {
14
+ private root: RootDatabase
15
+ private applogs: Database<string, string> // cidString → Applog JSON
16
+ private threadIndex: Database<string, string> // threadId → cidString (dupSort)
17
+ private blocks: Database<Uint8Array, string> // cidString → bytes
18
+
19
+ static async init(path: string): Promise<StorageFs> {
20
+ DEBUG(`[StorageFs.init] ENTER - path=${path}`)
21
+
22
+ DEBUG(`[StorageFs.init] Opening LMDB root database`)
23
+ const root = open({
24
+ path,
25
+ compression: true,
26
+ })
27
+ DEBUG(`[StorageFs.init] LMDB root database opened`)
28
+
29
+ DEBUG(`[StorageFs.init] Opening applogs DB`)
30
+ const applogs = root.openDB<string, string>({
31
+ name: 'applogs',
32
+ })
33
+ DEBUG(`[StorageFs.init] Applogs DB opened`)
34
+
35
+ DEBUG(`[StorageFs.init] Opening threadIndex DB`)
36
+ const threadIndex = root.openDB<string, string>({
37
+ name: 'thread_index',
38
+ dupSort: true,
39
+ })
40
+ DEBUG(`[StorageFs.init] ThreadIndex DB opened`)
41
+
42
+ DEBUG(`[StorageFs.init] Opening blocks DB`)
43
+ const blocks = root.openDB<Uint8Array, string>({
44
+ name: 'blocks',
45
+ })
46
+ DEBUG(`[StorageFs.init] Blocks DB opened`)
47
+
48
+ DEBUG(`[StorageFs.init] Creating StorageFs instance`)
49
+ const instance = new StorageFs(root, applogs, threadIndex, blocks)
50
+ DEBUG(`[StorageFs.init] EXIT - StorageFs instance created successfully`)
51
+ return instance
52
+ }
53
+
54
+ private constructor(
55
+ root: RootDatabase,
56
+ applogs: Database<string, string>,
57
+ threadIndex: Database<string, string>,
58
+ blocks: Database<Uint8Array, string>,
59
+ ) {
60
+ this.root = root
61
+ this.applogs = applogs
62
+ this.threadIndex = threadIndex
63
+ this.blocks = blocks
64
+ }
65
+
66
+ // === Applogs ===
67
+
68
+ async storeApplogs(threadId: string, applogs: Applog[]): Promise<number> {
69
+ const fnStart = Date.now()
70
+ DEBUG(`[StorageFs.storeApplogs] ===== ENTER =====`)
71
+ DEBUG(`[StorageFs.storeApplogs] threadId: ${threadId}`)
72
+ DEBUG(`[StorageFs.storeApplogs] applogs.length: ${applogs.length}`)
73
+ let stored = 0
74
+
75
+ DEBUG(`[StorageFs.storeApplogs] About to start transaction`)
76
+ const txStart = Date.now()
77
+
78
+ try {
79
+ // CRITICAL FIX: Use transactionSync instead of transaction
80
+ // Async transactions (transaction method) are NOT supported in Deno's NAPI
81
+ // Reference: https://github.com/kriszyp/lmdb-js#usage
82
+ // "Deno and Bun's support for NAPI is not very stable yet, and currently
83
+ // asynchronous transactions (transaction method) are not supported"
84
+
85
+ // NOTE: No logging inside transaction - LMDB transactions must be fast and synchronous
86
+ this.root.transactionSync(() => {
87
+ for (const applog of applogs) {
88
+ const cidStr = applog.cid
89
+
90
+ // Check if already stored (dedup by CID)
91
+ if (this.applogs.doesExist(cidStr)) {
92
+ // Applog exists, but still add to thread index if not already there
93
+ if (!this.threadIndex.doesExist(threadId, cidStr)) {
94
+ this.threadIndex.put(threadId, cidStr)
95
+ }
96
+ continue
97
+ }
98
+
99
+ // Store applog
100
+ this.applogs.put(cidStr, JSON.stringify(applog))
101
+
102
+ // Add to thread index
103
+ this.threadIndex.put(threadId, cidStr)
104
+
105
+ stored++
106
+ }
107
+ })
108
+
109
+ const txDuration = Date.now() - txStart
110
+ DEBUG(`[StorageFs.storeApplogs] Transaction completed in ${txDuration}ms`)
111
+ } catch (err) {
112
+ const txDuration = Date.now() - txStart
113
+ ERROR(`[StorageFs.storeApplogs] Transaction error after ${txDuration}ms:`, err)
114
+ throw err
115
+ }
116
+
117
+ const fnDuration = Date.now() - fnStart
118
+ DEBUG(`[StorageFs.storeApplogs] ===== SUCCESS in ${fnDuration}ms, stored ${stored} applogs`)
119
+ return stored
120
+ }
121
+
122
+ getApplogs(threadId: string): Applog[] {
123
+ const applogs: Applog[] = []
124
+ const cidStrings = this.threadIndex.getValues(threadId)
125
+
126
+ for (const cidStr of cidStrings) {
127
+ const json = this.applogs.get(cidStr)
128
+ if (json) {
129
+ applogs.push(JSON.parse(json))
130
+ }
131
+ }
132
+
133
+ return applogs
134
+ }
135
+
136
+ getApplogByCid(cid: CID | CidString): Applog | null {
137
+ const cidStr = typeof cid === 'string' ? cid : cid.toString()
138
+ const json = this.applogs.get(cidStr)
139
+ if (!json) return null
140
+ return JSON.parse(json)
141
+ }
142
+
143
+ hasApplog(cid: CID | CidString): boolean {
144
+ const cidStr = typeof cid === 'string' ? cid : cid.toString()
145
+ return this.applogs.doesExist(cidStr)
146
+ }
147
+
148
+ // === Blocks (BlockStore interface) ===
149
+
150
+ async get(cid: CID): Promise<Uint8Array> {
151
+ const bytes = this.blocks.get(cid.toString())
152
+ if (!bytes) throw new Error(`Block not found: ${cid}`)
153
+ return bytes
154
+ }
155
+
156
+ async put(cid: CID, bytes: Uint8Array): Promise<void> {
157
+ await this.blocks.put(cid.toString(), bytes)
158
+ }
159
+
160
+ async has(cid: CID): Promise<boolean> {
161
+ return this.blocks.doesExist(cid.toString())
162
+ }
163
+
164
+ // === Threads ===
165
+
166
+ listThreads(): string[] {
167
+ const threads = new Set<string>()
168
+ // Iterate over all keys in thread_index
169
+ for (const { key } of this.threadIndex.getRange({})) {
170
+ threads.add(key)
171
+ }
172
+ return Array.from(threads)
173
+ }
174
+
175
+ async deleteThread(threadId: string): Promise<void> {
176
+ // Use transactionSync for Deno compatibility (async transactions not supported in Deno NAPI)
177
+ this.root.transactionSync(() => {
178
+ // Get all CIDs in this thread
179
+ const cidStrings = Array.from(this.threadIndex.getValues(threadId))
180
+
181
+ // Remove all entries for this thread from index
182
+ for (const cidStr of cidStrings) {
183
+ this.threadIndex.remove(threadId, cidStr)
184
+ }
185
+
186
+ // Note: We don't delete the applogs themselves - they may be in other threads
187
+ // Orphan cleanup could be a separate maintenance operation
188
+ })
189
+
190
+ DEBUG(`[StorageFs] Deleted thread "${threadId}"`)
191
+ }
192
+
193
+ // === Utilities ===
194
+
195
+ getThreadApplogCount(threadId: string): number {
196
+ return this.threadIndex.getValuesCount(threadId)
197
+ }
198
+
199
+ isApplogInThread(threadId: string, cid: CID | CidString): boolean {
200
+ const cidStr = typeof cid === 'string' ? cid : cid.toString()
201
+ return this.threadIndex.doesExist(threadId, cidStr)
202
+ }
203
+
204
+ close(): void {
205
+ this.root.close()
206
+ }
207
+ }
208
+
209
+ // Re-export for backwards compatibility during transition
210
+ export { StorageFs as LmdbConnector }
File without changes