@fireproof/core 0.10.2-dev → 0.10.4-dev

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.10.2-dev",
3
+ "version": "0.10.4-dev",
4
4
  "description": "Immutable embedded distributed database for the web",
5
5
  "main": "dist/fireproof.cjs.js",
6
6
  "module": "dist/fireproof.esm.js",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "browser": "./dist/fireproof.browser.js",
15
15
  "files": [
16
+ "src",
16
17
  "dist/fireproof.*"
17
18
  ],
18
19
  "type": "module",
@@ -68,7 +69,6 @@
68
69
  "@ipld/dag-cbor": "^9.0.3",
69
70
  "async": "^3.2.4",
70
71
  "idb": "^7.1.1",
71
- "multiformats": "^12.0.1",
72
- "prolly-trees": "^1.0.4"
72
+ "multiformats": "^12.0.1"
73
73
  }
74
74
  }
@@ -0,0 +1,89 @@
1
+ import { Link } from 'multiformats'
2
+ import { create, encode, decode } from 'multiformats/block'
3
+ import { sha256 as hasher } from 'multiformats/hashes/sha2'
4
+ import * as codec from '@ipld/dag-cbor'
5
+ import { put, get, EventData } from '@alanshaw/pail/crdt'
6
+ import { EventFetcher } from '@alanshaw/pail/clock'
7
+
8
+ import { TransactionBlockstore as Blockstore, Transaction } from './transaction'
9
+ import { DocUpdate, ClockHead, BlockFetcher, AnyLink, DocValue, BulkResult } from './types'
10
+
11
+ export function makeGetBlock(blocks: BlockFetcher) {
12
+ return async (address: Link) => {
13
+ const block = await blocks.get(address)
14
+ if (!block) throw new Error(`Missing block ${address.toString()}`)
15
+ const { cid, bytes } = block
16
+ return create({ cid, bytes, hasher, codec })
17
+ }
18
+ }
19
+
20
+ export async function applyBulkUpdateToCrdt(
21
+ tblocks: Transaction,
22
+ head: ClockHead,
23
+ updates: DocUpdate[],
24
+ options?: object
25
+ ): Promise<BulkResult> {
26
+ for (const update of updates) {
27
+ const link = await makeLinkForDoc(tblocks, update)
28
+ const result = await put(tblocks, head, update.key, link, options)
29
+ for (const { cid, bytes } of [...result.additions, ...result.removals, result.event]) {
30
+ tblocks.putSync(cid, bytes)
31
+ }
32
+ head = result.head
33
+ }
34
+ return { head }
35
+ }
36
+
37
+ async function makeLinkForDoc(blocks: Transaction, update: DocUpdate): Promise<AnyLink> {
38
+ let value: DocValue
39
+ if (update.del) {
40
+ value = { del: true }
41
+ } else {
42
+ value = { doc: update.value }
43
+ }
44
+ const block = await encode({ value, hasher, codec })
45
+ blocks.putSync(block.cid, block.bytes)
46
+ return block.cid
47
+ }
48
+
49
+ export async function getValueFromCrdt(blocks: Blockstore, head: ClockHead, key: string): Promise<DocValue> {
50
+ const link = await get(blocks, head, key)
51
+ if (!link) throw new Error(`Missing key ${key}`)
52
+ return await getValueFromLink(blocks, link)
53
+ }
54
+
55
+ export async function getValueFromLink(blocks: Blockstore, link: AnyLink): Promise<DocValue> {
56
+ const block = await blocks.get(link)
57
+ if (!block) throw new Error(`Missing block ${link.toString()}`)
58
+ const { value } = (await decode({ bytes: block.bytes, hasher, codec })) as { value: DocValue }
59
+ return value
60
+ }
61
+
62
+ export async function clockChangesSince(
63
+ blocks: Blockstore,
64
+ _head: ClockHead,
65
+ _since: ClockHead
66
+ ): Promise<{ result: DocUpdate[] }> {
67
+ const eventsFetcher = new EventFetcher<EventData>(blocks)
68
+ const updates = await gatherUpdates(blocks, eventsFetcher, _head, _since)
69
+ return { result: updates.reverse() }
70
+ }
71
+
72
+ async function gatherUpdates(blocks: Blockstore, eventsFetcher: EventFetcher<EventData>, head: ClockHead, since: ClockHead, updates: DocUpdate[] = []): Promise<DocUpdate[]> {
73
+ for (const link of since) {
74
+ if (head.includes(link)) {
75
+ throw new Error('found since in head, this is good, remove this error ' + updates.length)
76
+ return updates
77
+ }
78
+ }
79
+ for (const link of head) {
80
+ const { value: event } = await eventsFetcher.get(link)
81
+ const { key, value } = event.data
82
+ const docValue = await getValueFromLink(blocks, value)
83
+ updates.push({ key, value: docValue.doc, del: docValue.del })
84
+ if (event.parents) {
85
+ updates = await gatherUpdates(blocks, eventsFetcher, event.parents, since, updates)
86
+ }
87
+ }
88
+ return updates
89
+ }
package/src/crdt.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { TransactionBlockstore as Blockstore } from './transaction'
2
+ import { DocUpdate, BulkResult, ClockHead } from './types'
3
+ import { clockChangesSince, applyBulkUpdateToCrdt, getValueFromCrdt } from './crdt-helpers'
4
+
5
+ export class CRDT {
6
+ name: string | null
7
+ ready: Promise<void>
8
+
9
+ private _blocks: Blockstore
10
+ private _head: ClockHead
11
+
12
+ constructor(name?: string, blocks?: Blockstore) {
13
+ this.name = name || null
14
+ this._blocks = blocks || new Blockstore(name)
15
+ this._head = []
16
+ this.ready = this._blocks.ready.then(({ head }: { head: ClockHead }) => {
17
+ this._head = head // todo multi head support here
18
+ })
19
+ }
20
+
21
+ async bulk(updates: DocUpdate[], options?: object): Promise<BulkResult> {
22
+ await this.ready
23
+ const tResult: BulkResult = await this._blocks.transaction(async tblocks => {
24
+ const { head } = await applyBulkUpdateToCrdt(tblocks, this._head, updates, options)
25
+ this._head = head // we want multi head support here if allowing calls to bulk in parallel
26
+ return { head }
27
+ })
28
+ return tResult
29
+ }
30
+
31
+ // async root(): Promise<any> {
32
+ // async eventsSince(since: EventLink<T>): Promise<{clockCIDs: CIDCounter, result: T[]}> {
33
+ // async getAll(rootCache: any = null): Promise<{root: any, cids: CIDCounter, clockCIDs: CIDCounter, result: T[]}> {
34
+
35
+ async get(key: string) {
36
+ await this.ready
37
+ const result = await getValueFromCrdt(this._blocks, this._head, key)
38
+ if (result.del) return null
39
+ return result
40
+ }
41
+
42
+ async changes(since: ClockHead) {
43
+ return await clockChangesSince(this._blocks, this._head, since)
44
+ }
45
+ }
@@ -0,0 +1,61 @@
1
+ // @ts-ignore
2
+ import cargoQueue from 'async/cargoQueue'
3
+ import { CRDT } from './crdt'
4
+ import { Doc, BulkResult, DocUpdate, DbResponse, ClockHead } from './types'
5
+
6
+ export class Database {
7
+ name: string
8
+ config: object
9
+ _crdt: CRDT
10
+ _writeQueue: any
11
+ constructor(name: string, config = {}) {
12
+ this.name = name
13
+ this.config = config
14
+ this._crdt = new CRDT(name)
15
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
16
+ this._writeQueue = cargoQueue(async (updates: DocUpdate[]) => {
17
+ return await this._crdt.bulk(updates)
18
+ })
19
+ }
20
+
21
+ async put(doc: Doc): Promise<DbResponse> {
22
+ const { _id, ...value } = doc
23
+ return await new Promise<DbResponse>((resolve, reject) => {
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
25
+ this._writeQueue.push({ key: _id, value }, function (err: Error | null, result?: BulkResult) {
26
+ if (err) reject(err)
27
+ resolve({ id: doc._id, clock: result?.head } as DbResponse)
28
+ })
29
+ })
30
+ }
31
+
32
+ async get(id: string): Promise<Doc> {
33
+ const got = await this._crdt.get(id).catch(e => {
34
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
35
+ e.message = `Not found: ${id} - ` + e.message
36
+ throw e
37
+ })
38
+ if (!got) throw new Error(`Not found: ${id}`)
39
+ const { doc } = got
40
+ return { _id: id, ...doc }
41
+ }
42
+
43
+ async del(id: string): Promise<DbResponse> {
44
+ return await new Promise<DbResponse>((resolve, reject) => {
45
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
46
+ this._writeQueue.push({ key: id, del: true }, function (err: Error | null, result?: BulkResult) {
47
+ if (err) reject(err)
48
+ resolve({ id, clock: result?.head } as DbResponse)
49
+ })
50
+ })
51
+ }
52
+
53
+ async changes(since: ClockHead): Promise<{ rows: { key: string; value: Doc }[] }> {
54
+ const { result } = await this._crdt.changes(since)
55
+ const rows = result.map(({ key, value }) => ({
56
+ key,
57
+ value: { _id: key, ...value } as Doc
58
+ }))
59
+ return { rows }
60
+ }
61
+ }
@@ -0,0 +1,6 @@
1
+ import { Database } from './database'
2
+ export class Fireproof {
3
+ static storage(name: string): Database {
4
+ return new Database(name)
5
+ }
6
+ }
@@ -0,0 +1,53 @@
1
+ import { BlockView, CID } from 'multiformats'
2
+ import { Block, encode, decode } from 'multiformats/block'
3
+ import { sha256 as hasher } from 'multiformats/hashes/sha2'
4
+ import * as raw from 'multiformats/codecs/raw'
5
+ import * as CBW from '@ipld/car/buffer-writer'
6
+ import * as codec from '@ipld/dag-cbor'
7
+ import { CarReader } from '@ipld/car'
8
+
9
+ import { Transaction } from './transaction'
10
+ import { AnyBlock, BulkResult, ClockHead, AnyLink } from './types'
11
+
12
+ export async function makeCarFile(
13
+ t: Transaction,
14
+ { head }: BulkResult,
15
+ cars: AnyLink[]
16
+ ): Promise<BlockView<unknown, number, number, 1>> {
17
+ if (!head) throw new Error('no head')
18
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
19
+ const fpCarHeaderBlock = (await encode({
20
+ value: { fp: { head, cars } },
21
+ hasher,
22
+ codec
23
+ })) as AnyBlock
24
+ await t.put(fpCarHeaderBlock.cid, fpCarHeaderBlock.bytes)
25
+
26
+ let size = 0
27
+ const headerSize = CBW.headerLength({ roots: [fpCarHeaderBlock.cid as CID<unknown, number, number, 1>] })
28
+ size += headerSize
29
+ for (const { cid, bytes } of t.entries()) {
30
+ size += CBW.blockLength({ cid, bytes } as Block<unknown, number, number, 1>)
31
+ }
32
+ const buffer = new Uint8Array(size)
33
+ const writer = CBW.createWriter(buffer, { headerSize })
34
+
35
+ writer.addRoot(fpCarHeaderBlock.cid as CID<unknown, number, number, 1>)
36
+
37
+ for (const { cid, bytes } of t.entries()) {
38
+ writer.write({ cid, bytes } as Block<unknown, number, number, 1>)
39
+ }
40
+ writer.close()
41
+ return await encode({ value: writer.bytes, hasher, codec: raw })
42
+ }
43
+
44
+ export async function parseCarFile(reader: CarReader): Promise<{ head: ClockHead; cars: AnyLink[] }> {
45
+ const roots = await reader.getRoots()
46
+ const header = await reader.get(roots[0])
47
+ if (!header) throw new Error('missing header block')
48
+ const got = await decode({ bytes: header.bytes, hasher, codec })
49
+ const {
50
+ fp: { head, cars }
51
+ } = got.value as { fp: { head: ClockHead; cars: AnyLink[] } }
52
+ return { head, cars }
53
+ }
package/src/loader.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { CarReader } from '@ipld/car'
2
+
3
+ // import { CarStoreFS, HeaderStoreFS } from './store-fs'
4
+ import { CarStoreIDB as CarStore, HeaderStoreLS as HeaderStore } from './store-browser'
5
+ import { makeCarFile, parseCarFile } from './loader-helpers'
6
+ import { Transaction } from './transaction'
7
+ import { AnyBlock, AnyLink, BulkResult, ClockHead } from './types'
8
+ import { CID } from 'multiformats'
9
+
10
+ export class Loader {
11
+ name: string
12
+ headerStore: HeaderStore
13
+ carStore: CarStore
14
+ carLog: AnyLink[] = []
15
+ carsReaders: Map<string, CarReader> = new Map()
16
+ ready: Promise<{ head: ClockHead}> // todo this will be a map of headers by branch name
17
+ constructor(name: string) {
18
+ this.name = name
19
+ this.headerStore = new HeaderStore(name)
20
+ this.carStore = new CarStore(name)
21
+ // todo config with multiple branches
22
+ this.ready = this.headerStore.load('main').then(async header => {
23
+ if (!header) return { head: [] }
24
+ const car = await this.carStore.load(header.car)
25
+ return await this.ingestCarHead(header.car, car)
26
+ })
27
+ }
28
+
29
+ async commit(t: Transaction, done: BulkResult): Promise<AnyLink> {
30
+ const car = await makeCarFile(t, done, this.carLog)
31
+ await this.carStore.save(car)
32
+ this.carLog.push(car.cid)
33
+ await this.headerStore.save(car.cid)
34
+ return car.cid
35
+ }
36
+
37
+ async loadCar(cid: AnyLink): Promise<CarReader> {
38
+ if (this.carsReaders.has(cid.toString())) return this.carsReaders.get(cid.toString()) as CarReader
39
+ const car = await this.carStore.load(cid)
40
+ if (!car) throw new Error(`missing car file ${cid.toString()}`)
41
+ const reader = await CarReader.fromBytes(car.bytes)
42
+ this.carsReaders.set(cid.toString(), reader)
43
+ return reader
44
+ }
45
+
46
+ async ingestCarHead(cid: AnyLink, car: AnyBlock): Promise<{ head: ClockHead, cars: AnyLink[]}> {
47
+ const reader = await CarReader.fromBytes(car.bytes)
48
+ this.carsReaders.set(cid.toString(), reader)
49
+ const { head, cars } = await parseCarFile(reader)
50
+ await this.getMoreReaders(cars)
51
+ return { head, cars }
52
+ }
53
+
54
+ async getMoreReaders(cids: AnyLink[]) {
55
+ for (const cid of cids) {
56
+ await this.loadCar(cid)
57
+ }
58
+ }
59
+
60
+ async getBlock(cid: CID): Promise<AnyBlock | undefined> {
61
+ for (const [, reader] of [...this.carsReaders].reverse()) { // reverse is faster
62
+ const block = await reader.get(cid)
63
+ if (block) return block
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,78 @@
1
+ import { openDB, IDBPDatabase } from 'idb'
2
+
3
+ import { AnyBlock, AnyLink } from './types'
4
+ import { CarStore, HeaderStore, StoredHeader } from './store'
5
+
6
+ export const FORMAT = '0.9'
7
+
8
+ export class CarStoreIDB extends CarStore {
9
+ keyId: string = 'public'
10
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
11
+ idb: IDBPDatabase<unknown> | null = null
12
+ name: string = 'default'
13
+ async withDB(dbWorkFun: (arg0: any) => any) {
14
+ if (!this.idb) {
15
+ const dbName = `fp.${FORMAT}.${this.keyId}.${this.name}.valet`
16
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
17
+ this.idb = await openDB(dbName, 1, {
18
+ upgrade(db): void {
19
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
20
+ db.createObjectStore('cars')
21
+ }
22
+ })
23
+ }
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
25
+ return await dbWorkFun(this.idb)
26
+ }
27
+
28
+ async load(cid: AnyLink): Promise<AnyBlock> {
29
+ console.log('loading', cid.toString())
30
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
31
+ return await this.withDB(async (db: IDBPDatabase<unknown>) => {
32
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
33
+ const tx = db.transaction(['cars'], 'readonly')
34
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
35
+ const bytes = (await tx.objectStore('cars').get(cid.toString())) as Uint8Array
36
+ if (!bytes) throw new Error(`missing idb block ${cid.toString()}`)
37
+ console.log('loaded', cid.toString())
38
+ return { cid, bytes }
39
+ })
40
+ }
41
+
42
+ async save(car: AnyBlock): Promise<void> {
43
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
44
+ return await this.withDB(async (db: IDBPDatabase<unknown>) => {
45
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
46
+ const tx = db.transaction(['cars'], 'readwrite')
47
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
48
+ await tx.objectStore('cars').put(car.bytes, car.cid.toString())
49
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
50
+ return await tx.done
51
+ })
52
+ }
53
+ }
54
+
55
+ export class HeaderStoreLS extends HeaderStore {
56
+ keyId: string = 'public'
57
+ name: string = 'default'
58
+
59
+ headerKey(branch: string) {
60
+ return `fp.${FORMAT}.${this.keyId}.${this.name}.${branch}`
61
+ }
62
+
63
+ // eslint-disable-next-line @typescript-eslint/require-await
64
+ async load(branch: string = 'main'): Promise<StoredHeader | null> {
65
+ try {
66
+ const bytes = localStorage.getItem(this.headerKey(branch))
67
+ return bytes ? this.parseHeader(bytes.toString()) : null
68
+ } catch (e) {}
69
+ }
70
+
71
+ // eslint-disable-next-line @typescript-eslint/require-await
72
+ async save(carCid: AnyLink, branch: string = 'main') {
73
+ try {
74
+ const headerKey = this.headerKey(branch)
75
+ return localStorage.setItem(headerKey, this.makeHeader(carCid))
76
+ } catch (e) {}
77
+ }
78
+ }
@@ -0,0 +1,51 @@
1
+ import { join, dirname } from 'node:path'
2
+ import { homedir } from 'node:os'
3
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
4
+
5
+ import { AnyBlock, AnyLink } from './types'
6
+ import { HeaderStore, CarStore, StoredHeader } from './store'
7
+
8
+ export const FORMAT = '0.9'
9
+
10
+ const encoder = new TextEncoder()
11
+
12
+ export const defaultConfig = {
13
+ dataDir: join(homedir(), '.fireproof', 'v' + FORMAT)
14
+ }
15
+
16
+ export class HeaderStoreFS extends HeaderStore {
17
+ async load(branch?: string): Promise<StoredHeader|null> {
18
+ branch = branch || 'main'
19
+ const filepath = join(defaultConfig.dataDir, this.name, branch + '.json')
20
+ const bytes = await readFile(filepath).catch((e: Error & { code: string}) => {
21
+ if (e.code === 'ENOENT') return null
22
+ throw e
23
+ })
24
+ return bytes ? this.parseHeader(bytes.toString()) : null
25
+ }
26
+
27
+ async save(carCid: AnyLink, branch?: string) {
28
+ branch = branch || 'main'
29
+ const filepath = join(defaultConfig.dataDir, this.name, branch + '.json')
30
+ const bytes = this.makeHeader(carCid)
31
+ await writePathFile(filepath, encoder.encode(bytes))
32
+ }
33
+ }
34
+
35
+ export class CarStoreFS extends CarStore {
36
+ async save(car: AnyBlock): Promise<void> {
37
+ const filepath = join(defaultConfig.dataDir, this.name, car.cid.toString() + '.car')
38
+ await writePathFile(filepath, car.bytes)
39
+ }
40
+
41
+ async load(cid: AnyLink): Promise<AnyBlock> {
42
+ const filepath = join(defaultConfig.dataDir, this.name, cid.toString() + '.car')
43
+ const bytes = await readFile(filepath)
44
+ return { cid, bytes: new Uint8Array(bytes) }
45
+ }
46
+ }
47
+
48
+ async function writePathFile(path: string, data: Uint8Array) {
49
+ await mkdir(dirname(path), { recursive: true })
50
+ return await writeFile(path, data)
51
+ }
package/src/store.ts ADDED
@@ -0,0 +1,32 @@
1
+ import { parse } from 'multiformats/link'
2
+ import { AnyLink } from './types'
3
+
4
+ export class StoredHeader {
5
+ car: AnyLink
6
+ constructor(jsonHeader: { car: string }) {
7
+ this.car = parse(jsonHeader.car)
8
+ }
9
+ }
10
+
11
+ export class HeaderStore {
12
+ name: string
13
+ constructor(name: string) {
14
+ this.name = name
15
+ }
16
+
17
+ makeHeader(car: AnyLink): string {
18
+ return JSON.stringify({ car: car.toString() })
19
+ }
20
+
21
+ parseHeader(headerData: string) {
22
+ const header = JSON.parse(headerData) as { car: string }
23
+ return new StoredHeader(header)
24
+ }
25
+ }
26
+
27
+ export class CarStore {
28
+ name: string
29
+ constructor(name: string) {
30
+ this.name = name
31
+ }
32
+ }
@@ -0,0 +1,68 @@
1
+ import { MemoryBlockstore } from '@alanshaw/pail/block'
2
+ import { BlockFetcher, AnyBlock, AnyLink, BulkResult, ClockHead } from './types'
3
+ import { Loader } from './loader'
4
+ import { CID } from 'multiformats'
5
+
6
+ /** forked from
7
+ * https://github.com/alanshaw/pail/blob/main/src/block.js
8
+ * thanks Alan
9
+ **/
10
+
11
+ export class Transaction extends MemoryBlockstore {
12
+ constructor(private parent: BlockFetcher) {
13
+ super()
14
+ this.parent = parent
15
+ }
16
+
17
+ async get(cid: AnyLink): Promise<AnyBlock | undefined> {
18
+ return this.parent.get(cid)
19
+ }
20
+
21
+ async superGet(cid: AnyLink): Promise<AnyBlock | undefined> {
22
+ return super.get(cid)
23
+ }
24
+ }
25
+
26
+ export class TransactionBlockstore implements BlockFetcher {
27
+ name: string | null = null
28
+ ready: Promise<{ head: ClockHead }> // todo this will be a map of headers by branch name
29
+
30
+ private transactions: Set<Transaction> = new Set()
31
+ private loader: Loader | null = null
32
+
33
+ constructor(name?: string, loader?: Loader) {
34
+ if (name) {
35
+ this.name = name
36
+ this.loader = loader || new Loader(name)
37
+ this.ready = this.loader.ready
38
+ } else {
39
+ this.ready = Promise.resolve({ head: [] })
40
+ }
41
+ }
42
+
43
+ // eslint-disable-next-line @typescript-eslint/require-await
44
+ async put() {
45
+ throw new Error('use a transaction to put')
46
+ }
47
+
48
+ async get(cid: AnyLink): Promise<AnyBlock | undefined> {
49
+ for (const f of this.transactions) {
50
+ const v = await f.superGet(cid)
51
+ if (v) return v
52
+ }
53
+ if (!this.loader) return
54
+ return await this.loader.getBlock(cid as CID)
55
+ }
56
+
57
+ async transaction(fn: (t: Transaction) => Promise<BulkResult>) {
58
+ const t = new Transaction(this)
59
+ this.transactions.add(t)
60
+ const done: BulkResult = await fn(t)
61
+ if (done) { return { ...done, car: await this.commit(t, done) } }
62
+ return done
63
+ }
64
+
65
+ async commit(t: Transaction, done: BulkResult): Promise<AnyLink | undefined> {
66
+ return await this.loader?.commit(t, done)
67
+ }
68
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { Link } from 'multiformats'
2
+ import { EventLink } from '@alanshaw/pail/clock'
3
+ import { EventData } from '@alanshaw/pail/crdt'
4
+
5
+ export type ClockHead = EventLink<EventData>[]
6
+
7
+ export type BulkResult = {
8
+ head: ClockHead
9
+ car?: AnyLink
10
+ }
11
+
12
+ type DocBody = {
13
+ [key: string]: any
14
+ }
15
+
16
+ export type Doc = DocBody & {
17
+ _id: string
18
+ }
19
+
20
+ export type DocUpdate = {
21
+ key: string
22
+ value?: DocBody
23
+ del?: boolean
24
+ }
25
+
26
+ export type DocValue = {
27
+ doc?: DocBody
28
+ del?: boolean
29
+ }
30
+
31
+ export type AnyLink = Link<unknown, number, number, 1 | 0>
32
+ export type AnyBlock = { cid: AnyLink; bytes: Uint8Array }
33
+ export type BlockFetcher = { get: (link: AnyLink) => Promise<AnyBlock | undefined> }
34
+
35
+ export type DbResponse = {
36
+ id: string
37
+ clock: ClockHead
38
+ }