@fireproof/core 0.0.1
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/index.js +5 -0
- package/package.json +70 -0
- package/scripts/propernames/gen.sh +3 -0
- package/scripts/randomcid.js +12 -0
- package/scripts/words/gen.js +55 -0
- package/src/block.js +75 -0
- package/src/blockstore.js +246 -0
- package/src/clock.js +352 -0
- package/src/db-index.js +196 -0
- package/src/fireproof.js +209 -0
- package/src/link.d.ts +3 -0
- package/src/listener.js +111 -0
- package/src/prolly.js +235 -0
- package/src/valet.js +142 -0
- package/test/clock.test.js +725 -0
- package/test/db-index.test.js +214 -0
- package/test/fireproof.test.js +287 -0
- package/test/helpers.js +45 -0
- package/test/listener.test.js +102 -0
- package/test/prolly.test.js +203 -0
@@ -0,0 +1,214 @@
|
|
1
|
+
import { describe, it, beforeEach } from 'mocha'
|
2
|
+
import assert from 'node:assert'
|
3
|
+
import Blockstore from '../src/blockstore.js'
|
4
|
+
import Fireproof from '../src/fireproof.js'
|
5
|
+
import Index from '../src/db-index.js'
|
6
|
+
console.x = function () {}
|
7
|
+
|
8
|
+
describe('Index query', () => {
|
9
|
+
let database, index
|
10
|
+
beforeEach(async () => {
|
11
|
+
database = new Fireproof(new Blockstore(), []) // todo: these need a cloud name aka w3name, add this after we have cloud storage of blocks
|
12
|
+
const docs = [
|
13
|
+
{ _id: 'a1s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'alice', age: 40 },
|
14
|
+
{ _id: 'b2s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'bob', age: 40 },
|
15
|
+
{ _id: 'c3s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'carol', age: 43 },
|
16
|
+
{ _id: 'd4s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'dave', age: 48 },
|
17
|
+
{ _id: 'e4s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'emily', age: 4 },
|
18
|
+
{ _id: 'f4s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'frank', age: 7 }
|
19
|
+
]
|
20
|
+
for (const doc of docs) {
|
21
|
+
const id = doc._id
|
22
|
+
const response = await database.put(doc)
|
23
|
+
assert(response)
|
24
|
+
assert(response.id, 'should have id')
|
25
|
+
assert.equal(response.id, id)
|
26
|
+
}
|
27
|
+
index = new Index(database, function (doc, map) {
|
28
|
+
map(doc.age, doc.name)
|
29
|
+
})
|
30
|
+
})
|
31
|
+
it('query index range', async () => {
|
32
|
+
const result = await index.query({ range: [41, 44] })
|
33
|
+
assert(result, 'did return result')
|
34
|
+
assert(result.rows)
|
35
|
+
assert.equal(result.rows.length, 1, 'one row matched')
|
36
|
+
assert.equal(result.rows[0].key, 43)
|
37
|
+
assert(result.rows[0].value === 'carol', 'correct value')
|
38
|
+
})
|
39
|
+
it.skip('query exact key', async () => {
|
40
|
+
const result = await index.query({ key: 43 })
|
41
|
+
assert(result, 'did return result')
|
42
|
+
assert(result.rows)
|
43
|
+
assert.equal(result.rows.length, 1, 'one row matched')
|
44
|
+
assert.equal(result.rows[0].key, 43)
|
45
|
+
assert(result.rows[0].value === 'carol', 'correct value')
|
46
|
+
})
|
47
|
+
it('query twice', async () => {
|
48
|
+
let result = await index.query({ range: [41, 44] })
|
49
|
+
assert(result, 'did return result')
|
50
|
+
assert(result.rows)
|
51
|
+
assert.equal(result.rows.length, 1, 'one row matched')
|
52
|
+
result = await index.query({ range: [41, 44] })
|
53
|
+
assert(result.rows[0].key === 43, 'correct key')
|
54
|
+
assert(result.rows[0].value === 'carol', 'correct value')
|
55
|
+
})
|
56
|
+
it('query two rows oops', async () => {
|
57
|
+
const result = await index.query({ range: [39, 41] })
|
58
|
+
assert(result, 'did return result')
|
59
|
+
assert(result.rows)
|
60
|
+
assert.equal(result.rows[0].key, 40, 'correct key') // TODO fix this is currently collating as strings - use gson?
|
61
|
+
assert.equal(result.rows.length, 2, '2 row matched')
|
62
|
+
assert(result.rows[0].value === 'alice', 'correct value')
|
63
|
+
})
|
64
|
+
it('query two rows easy', async () => {
|
65
|
+
const result = await index.query({ range: [40, 41] })
|
66
|
+
assert(result, 'did return result')
|
67
|
+
assert(result.rows)
|
68
|
+
assert.equal(result.rows.length, 2, '2 row matched')
|
69
|
+
assert(result.rows[0].key === 40, 'correct key')
|
70
|
+
assert(result.rows[0].value === 'alice', 'correct value')
|
71
|
+
})
|
72
|
+
it('update index', async () => {
|
73
|
+
const bresult = await index.query({ range: [2, 90] })
|
74
|
+
assert(bresult, 'did return bresult')
|
75
|
+
// console.x('bresult.rows', bresult.rows)
|
76
|
+
assert.equal(bresult.rows.length, 6, 'all row matched')
|
77
|
+
|
78
|
+
const oldHead = database.clock
|
79
|
+
|
80
|
+
const notYet = await database.get('xxxx-3c3a-4b5e-9c1c-8c5c0c5c0c5c').catch((e) => e)
|
81
|
+
assert.equal(notYet.message, 'Not found', 'not yet there')
|
82
|
+
console.x('initial Xander 53', notYet)
|
83
|
+
const response = await database.put({ _id: 'xxxx-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'Xander', age: 53 })
|
84
|
+
assert(response)
|
85
|
+
assert(response.id, 'should have id')
|
86
|
+
|
87
|
+
const gotX = await database.get(response.id)
|
88
|
+
assert(gotX)
|
89
|
+
assert(gotX.name === 'Xander', 'got Xander')
|
90
|
+
console.x('got X')
|
91
|
+
|
92
|
+
const snap = database.snapshot(oldHead)
|
93
|
+
|
94
|
+
const aliceOld = await snap.get('a1s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')// .catch((e) => e)
|
95
|
+
console.x('aliceOld', aliceOld)
|
96
|
+
assert.equal(aliceOld.name, 'alice', 'alice old')
|
97
|
+
|
98
|
+
const noX = await snap.get(response.id).catch((e) => e)
|
99
|
+
assert.equal(noX.message, 'Not found', 'not yet there')
|
100
|
+
|
101
|
+
const allresult = await index.query({ range: [2, 90] })
|
102
|
+
assert.equal(allresult.rows.length, 7, 'all row matched')
|
103
|
+
|
104
|
+
const result = await index.query({ range: [51, 54] })
|
105
|
+
assert(result, 'did return result')
|
106
|
+
assert(result.rows)
|
107
|
+
assert.equal(result.rows.length, 1, '1 row matched')
|
108
|
+
assert(result.rows[0].key === 53, 'correct key')
|
109
|
+
})
|
110
|
+
it('update index with document update to different key', async () => {
|
111
|
+
await index.query({ range: [51, 54] })
|
112
|
+
|
113
|
+
console.x('--- make Xander 53')
|
114
|
+
const DOCID = 'xxxx-3c3a-4b5e-9c1c-8c5c0c5c0c5c'
|
115
|
+
const r1 = await database.put({ _id: DOCID, name: 'Xander', age: 53 })
|
116
|
+
assert(r1.id, 'should have id')
|
117
|
+
|
118
|
+
const result = await index.query({ range: [51, 54] })
|
119
|
+
assert(result, 'did return result')
|
120
|
+
assert(result.rows)
|
121
|
+
console.x('result.rows', result.rows)
|
122
|
+
assert.equal(result.rows.length, 1, '1 row matched')
|
123
|
+
assert(result.rows[0].key === 53, 'correct key')
|
124
|
+
|
125
|
+
const snap = database.snapshot(database.clock)
|
126
|
+
|
127
|
+
console.x('--- make Xander 63')
|
128
|
+
const response = await database.put({ _id: DOCID, name: 'Xander', age: 63 })
|
129
|
+
assert(response)
|
130
|
+
assert(response.id, 'should have id')
|
131
|
+
|
132
|
+
const oldXander = await snap.get(r1.id)
|
133
|
+
assert.equal(oldXander.age, 53, 'old xander')
|
134
|
+
// console.x('--- test snapshot', snap.clock)
|
135
|
+
|
136
|
+
const newZander = await database.get(r1.id)
|
137
|
+
assert.equal(newZander.age, 63, 'new xander')
|
138
|
+
|
139
|
+
// console.x('--- test liveshot', database.clock)
|
140
|
+
|
141
|
+
const result2 = await index.query({ range: [61, 64] })
|
142
|
+
assert(result2, 'did return result')
|
143
|
+
assert(result2.rows)
|
144
|
+
assert.equal(result2.rows.length, 1, '1 row matched')
|
145
|
+
assert(result2.rows[0].key === 63, 'correct key')
|
146
|
+
|
147
|
+
const resultempty = await index.query({ range: [51, 54] })
|
148
|
+
assert(resultempty, 'did return resultempty')
|
149
|
+
assert(resultempty.rows)
|
150
|
+
console.x('resultempty.rows', resultempty.rows)
|
151
|
+
assert(resultempty.rows.length === 0, 'old Xander should be gone')
|
152
|
+
|
153
|
+
const allresult = await index.query({ range: [2, 90] })
|
154
|
+
console.x('allresult.rows', allresult.rows)
|
155
|
+
// todo
|
156
|
+
assert.equal(allresult.rows.length, 7, 'all row matched')
|
157
|
+
})
|
158
|
+
it('update index with document deletion', async () => {
|
159
|
+
await index.query({ range: [51, 54] })
|
160
|
+
|
161
|
+
console.x('--- make Xander 53')
|
162
|
+
const DOCID = 'xxxx-3c3a-4b5e-9c1c-8c5c0c5c0c5c'
|
163
|
+
const r1 = await database.put({ _id: DOCID, name: 'Xander', age: 53 })
|
164
|
+
assert(r1.id, 'should have id')
|
165
|
+
|
166
|
+
const result = await index.query({ range: [51, 54] })
|
167
|
+
assert(result, 'did return result')
|
168
|
+
assert(result.rows)
|
169
|
+
console.x('result.rows', result.rows)
|
170
|
+
assert.equal(result.rows.length, 1, '1 row matched')
|
171
|
+
assert(result.rows[0].key === 53, 'correct key')
|
172
|
+
|
173
|
+
const snap = database.snapshot(database.clock)
|
174
|
+
|
175
|
+
console.x('--- delete Xander 53')
|
176
|
+
const response = await database.del(DOCID)
|
177
|
+
assert(response)
|
178
|
+
assert(response.id, 'should have id')
|
179
|
+
|
180
|
+
const oldXander = await snap.get(r1.id)
|
181
|
+
assert.equal(oldXander.age, 53, 'old xander')
|
182
|
+
// console.x('--- test snapshot', snap.clock)
|
183
|
+
|
184
|
+
const newZander = await database.get(r1.id).catch((e) => e)
|
185
|
+
assert.equal(newZander.message, 'Not found', 'new xander')
|
186
|
+
// console.x('--- test liveshot', database.clock)
|
187
|
+
|
188
|
+
const allresult = await index.query({ range: [2, 90] })
|
189
|
+
console.x('allresult.rows', allresult.rows)
|
190
|
+
// todo
|
191
|
+
assert.equal(allresult.rows.length, 6, 'all row matched')
|
192
|
+
|
193
|
+
const result2 = await index.query({ range: [51, 54] })
|
194
|
+
assert(result2, 'did return result')
|
195
|
+
assert(result2.rows)
|
196
|
+
assert.equal(result2.rows.length, 0, '0 row matched')
|
197
|
+
})
|
198
|
+
})
|
199
|
+
|
200
|
+
describe('Index query with bad index definition', () => {
|
201
|
+
let database, index
|
202
|
+
beforeEach(async () => {
|
203
|
+
database = new Fireproof(new Blockstore(), []) // todo: these need a cloud name aka w3name, add this after we have cloud storage of blocks
|
204
|
+
await database.put({ _id: 'a1s3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c', name: 'alice', age: 40 })
|
205
|
+
index = new Index(database, function (doc, map) {
|
206
|
+
map(doc.oops.missingField, doc.name)
|
207
|
+
})
|
208
|
+
})
|
209
|
+
it('query index range', async () => {
|
210
|
+
await index.query({ range: [41, 44] }).catch((e) => {
|
211
|
+
assert(/missingField/.test(e.message))
|
212
|
+
})
|
213
|
+
})
|
214
|
+
})
|
@@ -0,0 +1,287 @@
|
|
1
|
+
import { describe, it, beforeEach } from 'mocha'
|
2
|
+
import assert from 'node:assert'
|
3
|
+
import Blockstore from '../src/blockstore.js'
|
4
|
+
import Fireproof from '../src/fireproof.js'
|
5
|
+
|
6
|
+
let database, resp0
|
7
|
+
|
8
|
+
// const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
9
|
+
|
10
|
+
describe('Fireproof', () => {
|
11
|
+
beforeEach(async () => {
|
12
|
+
database = new Fireproof(new Blockstore(), []) // todo: these need a cloud name aka w3name, add this after we have cloud storage of blocks
|
13
|
+
resp0 = await database.put({
|
14
|
+
_id: '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c',
|
15
|
+
name: 'alice',
|
16
|
+
age: 42
|
17
|
+
})
|
18
|
+
})
|
19
|
+
|
20
|
+
it('put and get document', async () => {
|
21
|
+
assert(resp0.id, 'should have id')
|
22
|
+
assert.equal(resp0.id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
23
|
+
|
24
|
+
const avalue = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
25
|
+
assert.equal(avalue.name, 'alice')
|
26
|
+
assert.equal(avalue.age, 42)
|
27
|
+
assert.equal(avalue._id, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
28
|
+
})
|
29
|
+
it('has a factory for making new instances with default settings', async () => {
|
30
|
+
// TODO if you pass it an email it asks the local keyring, and if no key, does the email validation thing
|
31
|
+
const db = await Fireproof.storage({ email: 'jchris@gmail.com' })
|
32
|
+
assert(db instanceof Fireproof)
|
33
|
+
})
|
34
|
+
it('an empty database has no documents', async () => {
|
35
|
+
const db = Fireproof.storage()
|
36
|
+
assert(db instanceof Fireproof)
|
37
|
+
const e = await db.get('8c5c0c5c0c5c').catch((err) => err)
|
38
|
+
assert.equal(e.message, 'Not found')
|
39
|
+
const changes = await db.changesSince()
|
40
|
+
assert.equal(changes.rows.length, 0)
|
41
|
+
})
|
42
|
+
it('update existing document', async () => {
|
43
|
+
// const alice = await database.get('1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
44
|
+
// assert.equal(alice.name, 'alice')
|
45
|
+
|
46
|
+
const dogKey = 'aster-3c3a-4b5e-9c1c-8c5c0c5c0c5c'
|
47
|
+
const value = {
|
48
|
+
_id: dogKey,
|
49
|
+
name: 'aster',
|
50
|
+
age: 2
|
51
|
+
}
|
52
|
+
const response = await database.put(value)
|
53
|
+
assert(response.id, 'should have id')
|
54
|
+
assert.equal(response.id, dogKey)
|
55
|
+
assert.equal(value._id, dogKey)
|
56
|
+
const oldClock = database.clock
|
57
|
+
|
58
|
+
const avalue = await database.get(dogKey)
|
59
|
+
assert.equal(avalue.name, value.name)
|
60
|
+
assert.equal(avalue.age, value.age)
|
61
|
+
assert.equal(avalue._id, dogKey)
|
62
|
+
|
63
|
+
avalue.age = 3
|
64
|
+
const response2 = await database.put(avalue)
|
65
|
+
assert(response2.id, 'should have id')
|
66
|
+
assert.equal(response2.id, dogKey)
|
67
|
+
|
68
|
+
const bvalue = await database.get(dogKey)
|
69
|
+
assert.equal(bvalue.name, value.name)
|
70
|
+
assert.equal(bvalue.age, 3)
|
71
|
+
assert.equal(bvalue._id, dogKey)
|
72
|
+
|
73
|
+
const snapshot = database.snapshot(oldClock)
|
74
|
+
const snapdoc = await snapshot.get(dogKey)
|
75
|
+
// console.log('snapdoc', snapdoc)
|
76
|
+
// assert(snapdoc.id, 'should have id')
|
77
|
+
assert.equal(snapdoc._id, dogKey)
|
78
|
+
assert.equal(snapdoc.age, 2)
|
79
|
+
})
|
80
|
+
it("update document with validation function that doesn't allow it", async () => {
|
81
|
+
const validationDatabase = new Fireproof(new Blockstore(), [], {
|
82
|
+
validateChange: (newDoc, oldDoc, authCtx) => {
|
83
|
+
if (newDoc.name === 'bob') {
|
84
|
+
throw new Error('no bobs allowed')
|
85
|
+
}
|
86
|
+
}
|
87
|
+
})
|
88
|
+
const validResp = await validationDatabase.put({
|
89
|
+
_id: '111-alice',
|
90
|
+
name: 'alice',
|
91
|
+
age: 42
|
92
|
+
})
|
93
|
+
const getResp = await validationDatabase.get(validResp.id)
|
94
|
+
assert.equal(getResp.name, 'alice')
|
95
|
+
|
96
|
+
let e = await validationDatabase
|
97
|
+
.put({
|
98
|
+
_id: '222-bob',
|
99
|
+
name: 'bob',
|
100
|
+
age: 11
|
101
|
+
})
|
102
|
+
.catch((e) => e)
|
103
|
+
assert.equal(e.message, 'no bobs allowed')
|
104
|
+
|
105
|
+
e = await validationDatabase.get('222-bob').catch((e) => e)
|
106
|
+
assert.equal(e.message, 'Not found')
|
107
|
+
})
|
108
|
+
|
109
|
+
it('get missing document', async () => {
|
110
|
+
const e = await database.get('missing').catch((e) => e)
|
111
|
+
assert.equal(e.message, 'Not found')
|
112
|
+
})
|
113
|
+
it('delete a document', async () => {
|
114
|
+
const id = '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c'
|
115
|
+
const found = await database.get(id)
|
116
|
+
assert.equal(found._id, id)
|
117
|
+
const deleted = await database.del(id)
|
118
|
+
assert.equal(deleted.id, id)
|
119
|
+
const e = await database
|
120
|
+
.get(id)
|
121
|
+
.then((doc) => assert.equal('should be deleted', JSON.stringify(doc)))
|
122
|
+
.catch((e) => {
|
123
|
+
if (e.message !== 'Not found') {
|
124
|
+
throw e
|
125
|
+
}
|
126
|
+
return e
|
127
|
+
})
|
128
|
+
assert.equal(e.message, 'Not found')
|
129
|
+
})
|
130
|
+
|
131
|
+
it("delete a document with validation function that doesn't allow it", async () => {
|
132
|
+
const validationDatabase = new Fireproof(new Blockstore(), [], {
|
133
|
+
validateChange: (newDoc, oldDoc, authCtx) => {
|
134
|
+
if (oldDoc.name === 'bob') {
|
135
|
+
throw new Error('no changing bob')
|
136
|
+
}
|
137
|
+
}
|
138
|
+
})
|
139
|
+
const validResp = await validationDatabase.put({
|
140
|
+
_id: '222-bob',
|
141
|
+
name: 'bob',
|
142
|
+
age: 11
|
143
|
+
})
|
144
|
+
const getResp = await validationDatabase.get(validResp.id)
|
145
|
+
assert.equal(getResp.name, 'bob')
|
146
|
+
|
147
|
+
const e = await validationDatabase
|
148
|
+
.put({
|
149
|
+
_id: '222-bob',
|
150
|
+
name: 'bob',
|
151
|
+
age: 12
|
152
|
+
})
|
153
|
+
.catch((e) => e)
|
154
|
+
assert.equal(e.message, 'no changing bob')
|
155
|
+
|
156
|
+
let prevBob = await validationDatabase.get('222-bob')
|
157
|
+
assert.equal(prevBob.name, 'bob')
|
158
|
+
assert.equal(prevBob.age, 11)
|
159
|
+
|
160
|
+
const e2 = await validationDatabase.del('222-bob').catch((e) => e)
|
161
|
+
assert.equal(e2.message, 'no changing bob')
|
162
|
+
|
163
|
+
prevBob = await validationDatabase.get('222-bob')
|
164
|
+
assert.equal(prevBob.name, 'bob')
|
165
|
+
assert.equal(prevBob.age, 11)
|
166
|
+
})
|
167
|
+
|
168
|
+
it('provides docs since', async () => {
|
169
|
+
const result = await database.changesSince()
|
170
|
+
assert.equal(result.rows.length, 1)
|
171
|
+
assert.equal(result.rows[0].key, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
172
|
+
|
173
|
+
const result2 = await database.changesSince(result.clock)
|
174
|
+
assert.equal(result2.rows.length, 0)
|
175
|
+
|
176
|
+
const bKey = 'befbef-3c3a-4b5e-9c1c-bbbbbb'
|
177
|
+
const bvalue = {
|
178
|
+
_id: bKey,
|
179
|
+
name: 'bob',
|
180
|
+
age: 44
|
181
|
+
}
|
182
|
+
const response = await database.put(bvalue)
|
183
|
+
assert(response.id, 'should have id')
|
184
|
+
assert.equal(response.id, bKey)
|
185
|
+
|
186
|
+
const res3 = await database.changesSince(result2.clock)
|
187
|
+
assert.equal(res3.rows.length, 1)
|
188
|
+
|
189
|
+
const res4 = await database.changesSince(res3.clock)
|
190
|
+
assert.equal(res4.rows.length, 0)
|
191
|
+
assert.equal(res4.clock[0], res3.clock[0])
|
192
|
+
assert.equal(res4.clock.length, res3.clock.length)
|
193
|
+
|
194
|
+
const cKey = 'cefecef-3c3a-4b5e-9c1c-bbbbbb'
|
195
|
+
const value = {
|
196
|
+
_id: cKey,
|
197
|
+
name: 'carol',
|
198
|
+
age: 44
|
199
|
+
}
|
200
|
+
const response2 = await database.put(value)
|
201
|
+
assert(response2.id, 'should have id')
|
202
|
+
assert.equal(response2.id, cKey)
|
203
|
+
|
204
|
+
const res5 = await database.changesSince(res4.clock)
|
205
|
+
|
206
|
+
// await database.visClock()
|
207
|
+
|
208
|
+
assert.equal(res5.rows.length, 1)
|
209
|
+
|
210
|
+
const res6 = await database.changesSince(result2.clock)
|
211
|
+
assert.equal(res6.rows.length, 2)
|
212
|
+
|
213
|
+
const resultAll = await database.changesSince()
|
214
|
+
assert.equal(resultAll.rows.length, 3)
|
215
|
+
assert.equal(resultAll.rows[0].key, '1ef3b32a-3c3a-4b5e-9c1c-8c5c0c5c0c5c')
|
216
|
+
|
217
|
+
const res7 = await database.changesSince(resultAll.clock)
|
218
|
+
assert.equal(res7.rows.length, 0)
|
219
|
+
|
220
|
+
const valueCupdate = {
|
221
|
+
_id: cKey,
|
222
|
+
name: 'carol update',
|
223
|
+
age: 45
|
224
|
+
}
|
225
|
+
const responseUpdate = await database.put(valueCupdate)
|
226
|
+
assert(responseUpdate.id)
|
227
|
+
|
228
|
+
const res8 = await database.changesSince(resultAll.clock)
|
229
|
+
assert.equal(res8.rows.length, 1)
|
230
|
+
|
231
|
+
const res9 = await database.changesSince(res8.clock)
|
232
|
+
assert.equal(res9.rows.length, 0)
|
233
|
+
})
|
234
|
+
|
235
|
+
it.skip('docs since repeated changes', async () => {
|
236
|
+
assert.equal((await database.changesSince()).rows.length, 1)
|
237
|
+
let resp, doc, changes
|
238
|
+
for (let index = 0; index < 200; index++) {
|
239
|
+
const id = '' + (300 - index).toString()
|
240
|
+
resp = await database.put({ index, _id: id }).catch(e => {
|
241
|
+
assert.equal(e.message, 'put failed on _id: ' + id)
|
242
|
+
})
|
243
|
+
assert(resp.id)
|
244
|
+
doc = await database.get(resp.id).catch(e => {
|
245
|
+
console.trace('failed', e)
|
246
|
+
assert.equal(e.message, 'get failed on _id: ' + id)
|
247
|
+
})
|
248
|
+
assert.equal(doc.index, index)
|
249
|
+
changes = await database.changesSince().catch(e => {
|
250
|
+
assert.equal(e.message, 'changesSince failed on _id: ' + id)
|
251
|
+
})
|
252
|
+
assert.equal(changes.rows.length, index + 2)
|
253
|
+
}
|
254
|
+
}).timeout(20000)
|
255
|
+
it('concurrent transactions', async () => {
|
256
|
+
assert.equal((await database.changesSince()).rows.length, 1)
|
257
|
+
const promises = []
|
258
|
+
let putYes = 0
|
259
|
+
for (let index = 0; index < 20; index++) {
|
260
|
+
const id = 'a' + (300 - index).toString()
|
261
|
+
promises.push(database.put({ index, _id: id }).catch(e => {
|
262
|
+
assert.equal(e.message, 'put failed on _id: ' + id)
|
263
|
+
}).then(r => {
|
264
|
+
if (r.id) {
|
265
|
+
putYes++
|
266
|
+
return database.get(r.id).catch(e => {
|
267
|
+
// assert.equal(e.message, 'get failed on _id: ' + r.id)
|
268
|
+
}).then(d => {
|
269
|
+
// assert.equal(d.index, index)
|
270
|
+
return r.id
|
271
|
+
})
|
272
|
+
}
|
273
|
+
}))
|
274
|
+
promises.push(database.changesSince().catch(e => {
|
275
|
+
assert.equal(e.message, 'changesSince failed')
|
276
|
+
}).then(c => {
|
277
|
+
assert(c.rows.length > 0)
|
278
|
+
return c.rows.length
|
279
|
+
}))
|
280
|
+
}
|
281
|
+
const got = await Promise.all(promises)
|
282
|
+
assert.equal(got.length, putYes * 2)
|
283
|
+
// console.log('putYes', putYes)
|
284
|
+
// await sleep(1000)
|
285
|
+
assert.equal((await database.changesSince()).rows.length, 2)
|
286
|
+
}).timeout(20000)
|
287
|
+
})
|
package/test/helpers.js
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
import crypto from 'node:crypto'
|
2
|
+
// import assert from 'node:assert'
|
3
|
+
import * as Link from 'multiformats/link'
|
4
|
+
import * as raw from 'multiformats/codecs/raw'
|
5
|
+
import { sha256 } from 'multiformats/hashes/sha2'
|
6
|
+
import { MemoryBlockstore } from '../src/block.js'
|
7
|
+
|
8
|
+
// console.x = console.log
|
9
|
+
// console.log = function (...args) {
|
10
|
+
// // window.mutedLog = window.mutedLog || []
|
11
|
+
// // window.mutedLog.push(args)
|
12
|
+
// }
|
13
|
+
|
14
|
+
/** @param {number} size */
|
15
|
+
export async function randomCID (size) {
|
16
|
+
const hash = await sha256.digest(await randomBytes(size))
|
17
|
+
return Link.create(raw.code, hash)
|
18
|
+
}
|
19
|
+
|
20
|
+
/** @param {number} size */
|
21
|
+
export async function randomBytes (size) {
|
22
|
+
const bytes = new Uint8Array(size)
|
23
|
+
while (size) {
|
24
|
+
const chunk = new Uint8Array(Math.min(size, 65_536))
|
25
|
+
crypto.getRandomValues(chunk)
|
26
|
+
size -= bytes.length
|
27
|
+
bytes.set(chunk, size)
|
28
|
+
}
|
29
|
+
return bytes
|
30
|
+
}
|
31
|
+
|
32
|
+
export class Blockstore extends MemoryBlockstore {
|
33
|
+
|
34
|
+
}
|
35
|
+
|
36
|
+
let seq = 0
|
37
|
+
export function seqEventData (tag = '') {
|
38
|
+
return {
|
39
|
+
type: 'put',
|
40
|
+
value: `event${seq++}${tag}`
|
41
|
+
}
|
42
|
+
}
|
43
|
+
export function setSeq (n) {
|
44
|
+
seq = n
|
45
|
+
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import { describe, it, beforeEach } from 'mocha'
|
2
|
+
import assert from 'node:assert'
|
3
|
+
import Blockstore from '../src/blockstore.js'
|
4
|
+
import Fireproof from '../src/fireproof.js'
|
5
|
+
import Listener from '../src/listener.js'
|
6
|
+
|
7
|
+
let database, listener, star
|
8
|
+
|
9
|
+
describe('Listener', () => {
|
10
|
+
beforeEach(async () => {
|
11
|
+
database = new Fireproof(new Blockstore(), []) // todo: these need a cloud name aka w3name, add this after we have cloud storage of blocks
|
12
|
+
const docs = [ // dave is first today
|
13
|
+
{ _id: 'd4s3b32a-3c3a', name: 'dave', age: 48 },
|
14
|
+
{ _id: 'a1s3b32a-3c3a', name: 'alice', age: 40 },
|
15
|
+
{ _id: 'b2s3b32a-3c3a', name: 'bob', age: 40 },
|
16
|
+
{ _id: 'c3s3b32a-3c3a', name: 'carol', age: 43 },
|
17
|
+
{ _id: 'e4s3b32a-3c3a', name: 'emily', age: 4 },
|
18
|
+
{ _id: 'f4s3b32a-3c3a', name: 'frank', age: 7 }
|
19
|
+
]
|
20
|
+
for (const doc of docs) {
|
21
|
+
const id = doc._id
|
22
|
+
const response = await database.put(doc)
|
23
|
+
assert(response)
|
24
|
+
assert(response.id, 'should have id')
|
25
|
+
assert.equal(response.id, id)
|
26
|
+
}
|
27
|
+
listener = new Listener(database, function (doc, emit) {
|
28
|
+
if (doc.name) { emit('person') }
|
29
|
+
})
|
30
|
+
star = new Listener(database)
|
31
|
+
})
|
32
|
+
it('all listeners get the reset event', (done) => {
|
33
|
+
let count = 0
|
34
|
+
const check = () => {
|
35
|
+
console.log('increment check count')
|
36
|
+
count++
|
37
|
+
if (count === 3) done()
|
38
|
+
}
|
39
|
+
const startClock = database.clock
|
40
|
+
database.put({ _id: 'k645-87tk', name: 'karl' }).then((ok) => {
|
41
|
+
listener.on('person', check)
|
42
|
+
listener.on('not-found', check)
|
43
|
+
star.on('*', check)
|
44
|
+
database.put({ _id: 'k645-87tk', name: 'karl2' }).then((ok) => {
|
45
|
+
assert(ok.id)
|
46
|
+
assert.notEqual(database.clock, startClock)
|
47
|
+
database.setClock(startClock)
|
48
|
+
}).catch(done)
|
49
|
+
})
|
50
|
+
})
|
51
|
+
|
52
|
+
it('can listen to all events', (done) => {
|
53
|
+
star.on('*', (key) => {
|
54
|
+
assert.equal(key, 'i645-87ti')
|
55
|
+
done()
|
56
|
+
})
|
57
|
+
database.put({ _id: 'i645-87ti', name: 'isaac' }).then((ok) => {
|
58
|
+
assert(ok.id)
|
59
|
+
}).catch(done)
|
60
|
+
})
|
61
|
+
it('shares only new events by default', (done) => {
|
62
|
+
listener.on('person', (key) => {
|
63
|
+
assert.equal(key, 'g645-87tg')
|
64
|
+
done()
|
65
|
+
})
|
66
|
+
database.put({ _id: 'g645-87tg', name: 'gwen' }).then((ok) => {
|
67
|
+
assert(ok.id)
|
68
|
+
}).catch(done)
|
69
|
+
}).timeout(200)
|
70
|
+
it('shares all events if asked', async () => {
|
71
|
+
let people = 0
|
72
|
+
listener.on('person', (key) => {
|
73
|
+
people++
|
74
|
+
}, null)
|
75
|
+
// this has to take longer than the database save operation
|
76
|
+
// it's safe to make this number longer if it start failing
|
77
|
+
await sleep(50)
|
78
|
+
assert.equal(people, 6)
|
79
|
+
}).timeout(200)
|
80
|
+
it('shares events since db.clock', (done) => {
|
81
|
+
const clock = database.clock
|
82
|
+
const afterEvent = () => {
|
83
|
+
database.put({ _id: 'j645-87tj', name: 'jimmy' })
|
84
|
+
}
|
85
|
+
let people = 0
|
86
|
+
database.put({ _id: 'h645-87th', name: 'harold' }).then(newPerson => {
|
87
|
+
assert(newPerson.id)
|
88
|
+
listener.on('person', (key) => {
|
89
|
+
people++
|
90
|
+
if (people === 1) {
|
91
|
+
assert.equal(key, 'h645-87th')
|
92
|
+
} else {
|
93
|
+
assert.equal(key, 'j645-87tj')
|
94
|
+
done()
|
95
|
+
}
|
96
|
+
if (people === 1) afterEvent()
|
97
|
+
}, clock)
|
98
|
+
})
|
99
|
+
}).timeout(200)
|
100
|
+
})
|
101
|
+
|
102
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|