@fireproof/core 0.10.3-dev → 0.10.5-dev

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireproof/core",
3
- "version": "0.10.3-dev",
3
+ "version": "0.10.5-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",
@@ -9,10 +9,15 @@
9
9
  "import": "./dist/fireproof.esm.js",
10
10
  "require": "./dist/fireproof.cjs.js"
11
11
  },
12
- "./browser": "./dist/fireproof.browser.js"
12
+ "./browser": {
13
+ "import": "./dist/fireproof.browser.esm.js",
14
+ "require": "./dist/fireproof.browser.cjs.js",
15
+ "script": "./dist/fireproof.browser.iife.js"
16
+ }
13
17
  },
14
- "browser": "./dist/fireproof.browser.js",
18
+ "browser": "./dist/fireproof.browser.iife.js",
15
19
  "files": [
20
+ "src",
16
21
  "dist/fireproof.*"
17
22
  ],
18
23
  "type": "module",
@@ -68,7 +73,6 @@
68
73
  "@ipld/dag-cbor": "^9.0.3",
69
74
  "async": "^3.2.4",
70
75
  "idb": "^7.1.1",
71
- "multiformats": "^12.0.1",
72
- "prolly-trees": "^1.0.4"
76
+ "multiformats": "^12.0.1"
73
77
  }
74
- }
78
+ }
@@ -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
+ }