@fireproof/core 0.0.1 → 0.0.2-dev-test
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.md +232 -0
- package/index.d.ts +2 -0
- package/index.js +3 -5
- package/index.js.map +1 -0
- package/index.ts +2 -0
- package/package.json +26 -49
- package/tsconfig.json +18 -0
- package/scripts/propernames/gen.sh +0 -3
- package/scripts/randomcid.js +0 -12
- package/scripts/words/gen.js +0 -55
- package/src/block.js +0 -75
- package/src/blockstore.js +0 -246
- package/src/clock.js +0 -352
- package/src/db-index.js +0 -196
- package/src/fireproof.js +0 -209
- package/src/link.d.ts +0 -3
- package/src/listener.js +0 -111
- package/src/prolly.js +0 -235
- package/src/valet.js +0 -142
- package/test/clock.test.js +0 -725
- package/test/db-index.test.js +0 -214
- package/test/fireproof.test.js +0 -287
- package/test/helpers.js +0 -45
- package/test/listener.test.js +0 -102
- package/test/prolly.test.js +0 -203
package/src/prolly.js
DELETED
@@ -1,235 +0,0 @@
|
|
1
|
-
import { advance, EventFetcher, EventBlock, findCommonAncestorWithSortedEvents, findUnknownSortedEvents } from './clock.js'
|
2
|
-
import { create, load } from 'prolly-trees/map'
|
3
|
-
import * as codec from '@ipld/dag-cbor'
|
4
|
-
import { sha256 as hasher } from 'multiformats/hashes/sha2'
|
5
|
-
import { MemoryBlockstore, MultiBlockFetcher } from './block.js'
|
6
|
-
import { doTransaction } from './blockstore.js'
|
7
|
-
|
8
|
-
import { nocache as cache } from 'prolly-trees/cache'
|
9
|
-
import { bf, simpleCompare as compare } from 'prolly-trees/utils'
|
10
|
-
import { create as createBlock } from 'multiformats/block'
|
11
|
-
const opts = { cache, chunker: bf(3), codec, hasher, compare }
|
12
|
-
|
13
|
-
const withLog = async (label, fn) => {
|
14
|
-
const resp = await fn()
|
15
|
-
// console.log('withLog', label, !!resp)
|
16
|
-
return resp
|
17
|
-
}
|
18
|
-
|
19
|
-
const makeGetBlock = (blocks) => async (address) => {
|
20
|
-
const { cid, bytes } = await withLog(address, () => blocks.get(address))
|
21
|
-
return createBlock({ cid, bytes, hasher, codec })
|
22
|
-
}
|
23
|
-
|
24
|
-
async function createAndSaveNewEvent (inBlocks, mblocks, getBlock, bigPut, root, { key, value, del }, head, additions, removals = []) {
|
25
|
-
const data = {
|
26
|
-
type: 'put',
|
27
|
-
root: {
|
28
|
-
cid: root.cid,
|
29
|
-
bytes: root.bytes,
|
30
|
-
value: root.value
|
31
|
-
},
|
32
|
-
key
|
33
|
-
}
|
34
|
-
|
35
|
-
if (del) {
|
36
|
-
data.value = null
|
37
|
-
data.type = 'del'
|
38
|
-
} else {
|
39
|
-
data.value = value
|
40
|
-
}
|
41
|
-
/** @type {EventData} */
|
42
|
-
|
43
|
-
const event = await EventBlock.create(data, head)
|
44
|
-
bigPut(event)
|
45
|
-
head = await advance(inBlocks, head, event.cid)
|
46
|
-
|
47
|
-
return {
|
48
|
-
root,
|
49
|
-
additions,
|
50
|
-
removals,
|
51
|
-
head,
|
52
|
-
event
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
const makeGetAndPutBlock = (inBlocks) => {
|
57
|
-
const mblocks = new MemoryBlockstore()
|
58
|
-
const blocks = new MultiBlockFetcher(mblocks, inBlocks)
|
59
|
-
const getBlock = makeGetBlock(blocks)
|
60
|
-
const put = inBlocks.put.bind(inBlocks)
|
61
|
-
const bigPut = async (block, additions) => {
|
62
|
-
// console.log('bigPut', block.cid.toString())
|
63
|
-
const { cid, bytes } = block
|
64
|
-
put(cid, bytes)
|
65
|
-
mblocks.putSync(cid, bytes)
|
66
|
-
if (additions) {
|
67
|
-
additions.set(cid.toString(), block)
|
68
|
-
}
|
69
|
-
}
|
70
|
-
return { getBlock, bigPut, mblocks, blocks }
|
71
|
-
}
|
72
|
-
|
73
|
-
const bulkFromEvents = (sorted) =>
|
74
|
-
sorted.map(({ value: event }) => {
|
75
|
-
const {
|
76
|
-
data: { type, value, key }
|
77
|
-
} = event
|
78
|
-
return type === 'put' ? { key, value } : { key, del: true }
|
79
|
-
})
|
80
|
-
|
81
|
-
// Get the value of the root from the ancestor event
|
82
|
-
/**
|
83
|
-
*
|
84
|
-
* @param {EventFetcher} events
|
85
|
-
* @param {Link} ancestor
|
86
|
-
* @param {*} getBlock
|
87
|
-
* @returns
|
88
|
-
*/
|
89
|
-
const prollyRootFromAncestor = async (events, ancestor, getBlock) => {
|
90
|
-
// console.log('prollyRootFromAncestor', ancestor)
|
91
|
-
const event = await events.get(ancestor)
|
92
|
-
const { root } = event.value.data
|
93
|
-
// console.log('prollyRootFromAncestor', root.cid, JSON.stringify(root.value))
|
94
|
-
return load({ cid: root.cid, get: getBlock, ...opts })
|
95
|
-
}
|
96
|
-
|
97
|
-
/**
|
98
|
-
* Put a value (a CID) for the given key. If the key exists it's value is overwritten.
|
99
|
-
*
|
100
|
-
* @param {import('./block').BlockFetcher} blocks Bucket block storage.
|
101
|
-
* @param {import('./clock').EventLink<EventData>[]} head Merkle clock head.
|
102
|
-
* @param {string} key The key of the value to put.
|
103
|
-
* @param {import('./link').AnyLink} value The value to put.
|
104
|
-
* @param {object} [options]
|
105
|
-
* @returns {Promise<Result>}
|
106
|
-
*/
|
107
|
-
export async function put (inBlocks, head, event, options) {
|
108
|
-
const { getBlock, bigPut, mblocks, blocks } = makeGetAndPutBlock(inBlocks)
|
109
|
-
|
110
|
-
// If the head is empty, we create a new event and return the root and addition blocks
|
111
|
-
if (!head.length) {
|
112
|
-
const additions = new Map()
|
113
|
-
let root
|
114
|
-
for await (const node of create({ get: getBlock, list: [event], ...opts })) {
|
115
|
-
root = await node.block
|
116
|
-
bigPut(root, additions)
|
117
|
-
}
|
118
|
-
return createAndSaveNewEvent(inBlocks, mblocks, getBlock, bigPut, root, event, head, Array.from(additions.values()))
|
119
|
-
}
|
120
|
-
|
121
|
-
// Otherwise, we find the common ancestor and update the root and other blocks
|
122
|
-
const events = new EventFetcher(blocks)
|
123
|
-
const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
|
124
|
-
const prollyRootNode = await prollyRootFromAncestor(events, ancestor, getBlock)
|
125
|
-
|
126
|
-
const bulkOperations = bulkFromEvents(sorted)
|
127
|
-
const { root: newProllyRootNode, blocks: newBlocks } = await prollyRootNode.bulk([...bulkOperations, event]) // ading delete support here
|
128
|
-
const prollyRootBlock = await newProllyRootNode.block
|
129
|
-
const additions = new Map() // ; const removals = new Map()
|
130
|
-
bigPut(prollyRootBlock, additions)
|
131
|
-
for (const nb of newBlocks) {
|
132
|
-
bigPut(nb, additions)
|
133
|
-
}
|
134
|
-
|
135
|
-
return createAndSaveNewEvent(inBlocks, mblocks, getBlock, bigPut,
|
136
|
-
prollyRootBlock, event, head, Array.from(additions.values()) /*, Array.from(removals.values()) */)
|
137
|
-
}
|
138
|
-
|
139
|
-
/**
|
140
|
-
* Determine the effective prolly root given the current merkle clock head.
|
141
|
-
*
|
142
|
-
* @param {import('./block').BlockFetcher} blocks Bucket block storage.
|
143
|
-
* @param {import('./clock').EventLink<EventData>[]} head Merkle clock head.
|
144
|
-
*/
|
145
|
-
export async function root (inBlocks, head) {
|
146
|
-
if (!head.length) {
|
147
|
-
throw new Error('no head')
|
148
|
-
}
|
149
|
-
const { getBlock, blocks } = makeGetAndPutBlock(inBlocks)
|
150
|
-
const events = new EventFetcher(blocks)
|
151
|
-
const { ancestor, sorted } = await findCommonAncestorWithSortedEvents(events, head)
|
152
|
-
const prollyRootNode = await prollyRootFromAncestor(events, ancestor, getBlock)
|
153
|
-
|
154
|
-
// Perform bulk operations (put or delete) for each event in the sorted array
|
155
|
-
const bulkOperations = bulkFromEvents(sorted)
|
156
|
-
const { root: newProllyRootNode, blocks: newBlocks } = await prollyRootNode.bulk(bulkOperations)
|
157
|
-
const prollyRootBlock = await newProllyRootNode.block
|
158
|
-
// console.log('emphemeral blocks', newBlocks.map((nb) => nb.cid.toString()))
|
159
|
-
// todo maybe these should go to a temp blockstore?
|
160
|
-
await doTransaction('root', inBlocks, async (transactionBlockstore) => {
|
161
|
-
const { bigPut } = makeGetAndPutBlock(transactionBlockstore)
|
162
|
-
for (const nb of newBlocks) {
|
163
|
-
bigPut(nb)
|
164
|
-
}
|
165
|
-
bigPut(prollyRootBlock)
|
166
|
-
})
|
167
|
-
|
168
|
-
return newProllyRootNode // .block).cid // todo return live object not cid
|
169
|
-
}
|
170
|
-
|
171
|
-
/**
|
172
|
-
* Get the list of events not known by the `since` event
|
173
|
-
* @param {import('./block').BlockFetcher} blocks Bucket block storage.
|
174
|
-
* @param {import('./clock').EventLink<EventData>[]} head Merkle clock head.
|
175
|
-
* @param {import('./clock').EventLink<EventData>} since Event to compare against.
|
176
|
-
* @returns {Promise<import('./clock').EventLink<EventData>[]>}
|
177
|
-
*/
|
178
|
-
export async function eventsSince (blocks, head, since) {
|
179
|
-
if (!head.length) {
|
180
|
-
throw new Error('no head')
|
181
|
-
}
|
182
|
-
const sinceHead = [...since, ...head]
|
183
|
-
const unknownSorted3 = await findUnknownSortedEvents(blocks, sinceHead,
|
184
|
-
await findCommonAncestorWithSortedEvents(blocks, sinceHead))
|
185
|
-
return unknownSorted3.map(({ value: { data } }) => data)
|
186
|
-
}
|
187
|
-
|
188
|
-
/**
|
189
|
-
*
|
190
|
-
* @param {import('./block').BlockFetcher} blocks Bucket block storage.
|
191
|
-
* @param {import('./clock').EventLink<EventData>[]} head Merkle clock head.
|
192
|
-
*
|
193
|
-
* @returns {Promise<import('./prolly').Entry[]>}
|
194
|
-
*
|
195
|
-
*/
|
196
|
-
export async function getAll (blocks, head) {
|
197
|
-
// todo use the root node left around from put, etc
|
198
|
-
// move load to a central place
|
199
|
-
if (!head.length) {
|
200
|
-
return []
|
201
|
-
}
|
202
|
-
const prollyRootNode = await root(blocks, head)
|
203
|
-
const { result } = await prollyRootNode.getAllEntries()
|
204
|
-
return result.map(({ key, value }) => ({ key, value }))
|
205
|
-
}
|
206
|
-
|
207
|
-
/**
|
208
|
-
* @param {import('./block').BlockFetcher} blocks Bucket block storage.
|
209
|
-
* @param {import('./clock').EventLink<EventData>[]} head Merkle clock head.
|
210
|
-
* @param {string} key The key of the value to retrieve.
|
211
|
-
*/
|
212
|
-
export async function get (blocks, head, key) {
|
213
|
-
// instead pass root from db? and always update on change
|
214
|
-
if (!head.length) {
|
215
|
-
return null
|
216
|
-
}
|
217
|
-
const prollyRootNode = await root(blocks, head)
|
218
|
-
const { result } = await prollyRootNode.get(key)
|
219
|
-
return result
|
220
|
-
}
|
221
|
-
|
222
|
-
/**
|
223
|
-
* @typedef {{
|
224
|
-
* type: 'put'|'del'
|
225
|
-
* key: string
|
226
|
-
* value: import('./link').AnyLink
|
227
|
-
* root: import('./shard').ShardLink
|
228
|
-
* }} EventData
|
229
|
-
*
|
230
|
-
* @typedef {{
|
231
|
-
* root: import('./shard').ShardLink
|
232
|
-
* head: import('./clock').EventLink<EventData>[]
|
233
|
-
* event: import('./clock').EventBlockView<EventData>
|
234
|
-
* } & import('./db-index').ShardDiff} Result
|
235
|
-
*/
|
package/src/valet.js
DELETED
@@ -1,142 +0,0 @@
|
|
1
|
-
import { CarReader } from '@ipld/car'
|
2
|
-
import { CID } from 'multiformats/cid'
|
3
|
-
import { openDB } from 'idb'
|
4
|
-
|
5
|
-
// const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
6
|
-
let storageSupported = false
|
7
|
-
try {
|
8
|
-
storageSupported = (window.localStorage && true)
|
9
|
-
} catch (e) { }
|
10
|
-
|
11
|
-
export default class Valet {
|
12
|
-
#cars = new Map() // cars by cid
|
13
|
-
#cidToCar = new Map() // cid to car
|
14
|
-
|
15
|
-
#db = null
|
16
|
-
|
17
|
-
withDB = async (dbWorkFun) => {
|
18
|
-
if (!storageSupported) return
|
19
|
-
if (!this.#db) {
|
20
|
-
this.#db = await openDB('valet', 1, {
|
21
|
-
upgrade (db) {
|
22
|
-
db.createObjectStore('cars') // todo use database name
|
23
|
-
const cidToCar = db.createObjectStore('cidToCar', { keyPath: 'car' })
|
24
|
-
cidToCar.createIndex('cids', 'cids', { multiEntry: true })
|
25
|
-
}
|
26
|
-
})
|
27
|
-
}
|
28
|
-
return await dbWorkFun(this.#db)
|
29
|
-
}
|
30
|
-
|
31
|
-
/**
|
32
|
-
*
|
33
|
-
* @param {string} carCid
|
34
|
-
* @param {*} value
|
35
|
-
*/
|
36
|
-
async parkCar (carCid, value, cids) {
|
37
|
-
this.#cars.set(carCid, value)
|
38
|
-
for (const cid of cids) {
|
39
|
-
this.#cidToCar.set(cid, carCid)
|
40
|
-
}
|
41
|
-
|
42
|
-
await this.withDB(async (db) => {
|
43
|
-
const tx = db.transaction(['cars', 'cidToCar'], 'readwrite')
|
44
|
-
await tx.objectStore('cars').put(value, carCid)
|
45
|
-
await tx.objectStore('cidToCar').put({ car: carCid, cids: Array.from(cids) })
|
46
|
-
return await tx.done
|
47
|
-
})
|
48
|
-
}
|
49
|
-
|
50
|
-
async getBlock (dataCID) {
|
51
|
-
// return await this.#valetGet(dataCID)
|
52
|
-
// const MEMcarCid = this.#cidToCar.get(dataCID)
|
53
|
-
|
54
|
-
// return await this.withDB(async (db) => {
|
55
|
-
// const tx = db.transaction(['cars', 'cidToCar'], 'readonly')
|
56
|
-
// const carBytes = await tx.objectStore('cars').get(MEMcarCid)
|
57
|
-
// const reader = await CarReader.fromBytes(carBytes)
|
58
|
-
// const gotBlock = await reader.get(CID.parse(dataCID))
|
59
|
-
// if (gotBlock) {
|
60
|
-
// return gotBlock.bytes
|
61
|
-
// }
|
62
|
-
// })
|
63
|
-
|
64
|
-
return await this.withDB(async (db) => {
|
65
|
-
const tx = db.transaction(['cars', 'cidToCar'], 'readonly')
|
66
|
-
const indexResp = await tx.objectStore('cidToCar').index('cids').get(dataCID)
|
67
|
-
const carCid = indexResp?.car
|
68
|
-
if (!carCid) {
|
69
|
-
return
|
70
|
-
}
|
71
|
-
const carBytes = await tx.objectStore('cars').get(carCid)
|
72
|
-
const reader = await CarReader.fromBytes(carBytes)
|
73
|
-
const gotBlock = await reader.get(CID.parse(dataCID))
|
74
|
-
if (gotBlock) {
|
75
|
-
return gotBlock.bytes
|
76
|
-
}
|
77
|
-
})
|
78
|
-
}
|
79
|
-
|
80
|
-
/**
|
81
|
-
* Internal function to load blocks from persistent storage.
|
82
|
-
* Currently it just searches all the cars for the block, but in the future
|
83
|
-
* we need to index the block CIDs to the cars, and reference that to find the block.
|
84
|
-
* This index will also allow us to use accelerator links for the gateway when needed.
|
85
|
-
* It can itself be a prolly tree...
|
86
|
-
* @param {string} cid
|
87
|
-
* @returns {Promise<Uint8Array|undefined>}
|
88
|
-
*/
|
89
|
-
#valetGet = async (cid) => {
|
90
|
-
const carCid = this.#cidToCar.get(cid)
|
91
|
-
if (carCid) {
|
92
|
-
const carBytes = this.#cars.get(carCid)
|
93
|
-
const reader = await CarReader.fromBytes(carBytes)
|
94
|
-
const gotBlock = await reader.get(CID.parse(cid))
|
95
|
-
if (gotBlock) {
|
96
|
-
return gotBlock.bytes
|
97
|
-
}
|
98
|
-
}
|
99
|
-
}
|
100
|
-
}
|
101
|
-
|
102
|
-
export class MemoryValet {
|
103
|
-
#cars = new Map() // cars by cid
|
104
|
-
#cidToCar = new Map() // cid to car
|
105
|
-
|
106
|
-
/**
|
107
|
-
*
|
108
|
-
* @param {string} carCid
|
109
|
-
* @param {*} value
|
110
|
-
*/
|
111
|
-
async parkCar (carCid, value, cids) {
|
112
|
-
this.#cars.set(carCid, value)
|
113
|
-
for (const cid of cids) {
|
114
|
-
this.#cidToCar.set(cid, carCid)
|
115
|
-
}
|
116
|
-
}
|
117
|
-
|
118
|
-
async getBlock (dataCID) {
|
119
|
-
return await this.#valetGet(dataCID)
|
120
|
-
}
|
121
|
-
|
122
|
-
/**
|
123
|
-
* Internal function to load blocks from persistent storage.
|
124
|
-
* Currently it just searches all the cars for the block, but in the future
|
125
|
-
* we need to index the block CIDs to the cars, and reference that to find the block.
|
126
|
-
* This index will also allow us to use accelerator links for the gateway when needed.
|
127
|
-
* It can itself be a prolly tree...
|
128
|
-
* @param {string} cid
|
129
|
-
* @returns {Promise<Uint8Array|undefined>}
|
130
|
-
*/
|
131
|
-
#valetGet = async (cid) => {
|
132
|
-
const carCid = this.#cidToCar.get(cid)
|
133
|
-
if (carCid) {
|
134
|
-
const carBytes = this.#cars.get(carCid)
|
135
|
-
const reader = await CarReader.fromBytes(carBytes)
|
136
|
-
const gotBlock = await reader.get(CID.parse(cid))
|
137
|
-
if (gotBlock) {
|
138
|
-
return gotBlock.bytes
|
139
|
-
}
|
140
|
-
}
|
141
|
-
}
|
142
|
-
}
|