opal-pouchdb 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/LICENSE +7 -0
- data/README.md +45 -0
- data/Rakefile +17 -0
- data/config.ru +12 -0
- data/lib/opal-pouchdb.rb +1 -0
- data/lib/opal/pouchdb.rb +4 -0
- data/lib/opal/pouchdb/version.rb +5 -0
- data/opal-pouchdb.gemspec +22 -0
- data/opal/opal-pouchdb.rb +95 -0
- data/opal/pouchdb/all_documents.rb +30 -0
- data/opal/pouchdb/conversion.rb +28 -0
- data/opal/pouchdb/database.rb +266 -0
- data/opal/pouchdb/event_emitter.rb +27 -0
- data/opal/pouchdb/replication.rb +17 -0
- data/opal/pouchdb/row.rb +15 -0
- data/spec/database_events_spec.rb +81 -0
- data/spec/database_spec.rb +331 -0
- data/spec/pouchdb/es5-shim.min.js +7 -0
- data/spec/pouchdb/index.html.erb +10 -0
- data/spec/pouchdb/pouchdb.js +11 -0
- data/spec/replication_spec.rb +109 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/sync_spec.rb +82 -0
- metadata +139 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module PouchDB
|
2
|
+
class EventEmitter
|
3
|
+
include Conversion
|
4
|
+
|
5
|
+
def initialize(stream)
|
6
|
+
@native = stream
|
7
|
+
end
|
8
|
+
|
9
|
+
def on(event, &blk)
|
10
|
+
%x{
|
11
|
+
#{@native}.on(event, function(change) {
|
12
|
+
#{blk.call(OBJECT_CONVERSION.call(`change`))}
|
13
|
+
})
|
14
|
+
}
|
15
|
+
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def then
|
20
|
+
as_opal_promise(`#{@native}`)
|
21
|
+
end
|
22
|
+
|
23
|
+
def cancel
|
24
|
+
`#{@native}.cancel()`
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PouchDB
|
2
|
+
class Replication
|
3
|
+
include Conversion
|
4
|
+
|
5
|
+
def initialize(native)
|
6
|
+
@native = native
|
7
|
+
end
|
8
|
+
|
9
|
+
def to(db, options = {})
|
10
|
+
EventEmitter.new(`#{@native}.replicate.to(#{database_as_string(db)}, #{options.to_n})`)
|
11
|
+
end
|
12
|
+
|
13
|
+
def from(db, options = {})
|
14
|
+
EventEmitter.new(`#{@native}.replicate.from(#{database_as_string(db)}, #{options.to_n})`)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/opal/pouchdb/row.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "PouchDB::Database#changes" do
|
4
|
+
describe "changes" do
|
5
|
+
async "calls a block when a 'change' event is emitted" do
|
6
|
+
with_new_database do |db|
|
7
|
+
db.post(name: "I Change Things")
|
8
|
+
db.post(name: "I Change Things Too")
|
9
|
+
|
10
|
+
stream = db.changes
|
11
|
+
count = 0
|
12
|
+
stream.on "change" do count += 1 end
|
13
|
+
|
14
|
+
delayed(1.2) do |p|
|
15
|
+
p.resolve(count == 2)
|
16
|
+
end.then do |c|
|
17
|
+
async do
|
18
|
+
expect(c).to be(true)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "cancellation" do
|
26
|
+
async "calls a block when the 'complete' event is emitted" do
|
27
|
+
with_new_database do |db|
|
28
|
+
stream = db.changes
|
29
|
+
cancelled = false
|
30
|
+
stream.on "complete" do cancelled = true end
|
31
|
+
|
32
|
+
delayed(1) do |p|
|
33
|
+
p.resolve(cancelled)
|
34
|
+
end.then do |c|
|
35
|
+
async do
|
36
|
+
expect(c).to be(true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "single-shot" do
|
44
|
+
# TODO: Make this pass. I think I'm missing some important
|
45
|
+
# point of the "Single-shot" mode.
|
46
|
+
pending "calls a block with all the changes" do
|
47
|
+
with_new_database do |db|
|
48
|
+
db.post(classification: "Important Stuff")
|
49
|
+
db.post(classification: "REALLY Important Stuff")
|
50
|
+
|
51
|
+
db.changes(limit: 1, since: 0).then do |cs|
|
52
|
+
async do
|
53
|
+
expect(cs).to eq(1)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
async "passes options along" do
|
61
|
+
with_new_database do |db|
|
62
|
+
db.post(name: "Ishmael")
|
63
|
+
db.post(name: "Fishmael")
|
64
|
+
|
65
|
+
changes = []
|
66
|
+
db.changes(include_docs: true).on "change" do |c|
|
67
|
+
changes << c
|
68
|
+
end
|
69
|
+
|
70
|
+
delayed(1.2) do |p|
|
71
|
+
p.resolve(changes)
|
72
|
+
end.then do |c|
|
73
|
+
async do
|
74
|
+
expect(c.size).to eq(2)
|
75
|
+
expect(c.first["doc"]["name"]).to eq("Ishmael")
|
76
|
+
expect(c.last["doc"]["name"]).to eq("Fishmael")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,331 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
describe PouchDB::Database do
|
5
|
+
let(:docs) {
|
6
|
+
[
|
7
|
+
{ name: "Banana", color: "yellow" },
|
8
|
+
{ name: "Apple", color: "red" },
|
9
|
+
{ name: "Green Apple", color: "green" }
|
10
|
+
]
|
11
|
+
}
|
12
|
+
|
13
|
+
let(:docs_with_ids) {
|
14
|
+
docs.map { |d|
|
15
|
+
d.merge(_id: d[:name].downcase.gsub(/\s+/, "-"))
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
let(:sorted_ids) {
|
20
|
+
docs_with_ids.map { |d| d[:_id] }.sort
|
21
|
+
}
|
22
|
+
|
23
|
+
describe "#initialize" do
|
24
|
+
it "requires a name" do
|
25
|
+
expect { PouchDB::Database.new }.to raise_error(KeyError)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#destroy" do
|
30
|
+
let(:database_name) { "throaway_test_database" }
|
31
|
+
|
32
|
+
async "calls the returned Promise's success handler" do
|
33
|
+
with_new_database do |db|
|
34
|
+
db.destroy.then do |response|
|
35
|
+
run_async do
|
36
|
+
expect(response["ok"]).to be(true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#put" do
|
44
|
+
async "creating a new Document calls the returned Promise's success handler" do
|
45
|
+
with_new_database do |db|
|
46
|
+
promise = db.put(docs_with_ids.first)
|
47
|
+
|
48
|
+
promise.then do |response|
|
49
|
+
run_async do
|
50
|
+
expect(response).not_to be_nil
|
51
|
+
expect(response["rev"]).not_to be_nil
|
52
|
+
expect(response["id"]).to eq("banana")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
async "updating an existing Document calls the returned Promise's success handler" do
|
59
|
+
with_new_database do |db|
|
60
|
+
db.put(docs_with_ids.first).then do |created|
|
61
|
+
update = { name: "Bananananas" }
|
62
|
+
|
63
|
+
db.put(update, doc_id: created["id"], doc_rev: created["rev"]).then do |updated|
|
64
|
+
run_async do
|
65
|
+
expect(updated["rev"]).not_to eq(created["rev"])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
async "calls the returned Promise's error handler" do
|
73
|
+
with_new_database(false) do |db|
|
74
|
+
db.put(docs.first).fail do |error|
|
75
|
+
run_async do
|
76
|
+
expect(error.message).to match(/_id is required/)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#post" do
|
84
|
+
async "posting new Document generates an id" do
|
85
|
+
with_new_database do |db|
|
86
|
+
promise = db.post(docs.first)
|
87
|
+
|
88
|
+
promise.then do |response|
|
89
|
+
run_async do
|
90
|
+
expect(response["rev"]).not_to be_nil
|
91
|
+
expect(response["id"]).not_to be_nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#bulk_docs" do
|
99
|
+
async "generates ids if Documents don't have them" do
|
100
|
+
with_new_database do |db|
|
101
|
+
db.bulk_docs(docs).then do |response|
|
102
|
+
run_async do
|
103
|
+
expect(response.size).to eq(3)
|
104
|
+
expect(response.all? { |r| r["ok"] }).to be(true)
|
105
|
+
expect(response.map { |r| r["id"] }.none?(&:empty?)).to be(true)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
async "keeps passed-in ids if Documents have them" do
|
112
|
+
with_new_database do |db|
|
113
|
+
db.bulk_docs(docs_with_ids).then do |response|
|
114
|
+
run_async do
|
115
|
+
expect(response.map { |r| r["id"] }.sort).to eq(sorted_ids)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
async "updates sets of Documents" do
|
122
|
+
with_new_database do |db|
|
123
|
+
by_id = Hash[docs_with_ids.map { |d| [d[:_id], d] }]
|
124
|
+
|
125
|
+
db.bulk_docs(docs_with_ids).then do |created|
|
126
|
+
new_versions = created.map { |r|
|
127
|
+
d = by_id[r["id"]]
|
128
|
+
d.merge(_id: r["id"], _rev: r["rev"], name: d[:name].reverse)
|
129
|
+
}
|
130
|
+
|
131
|
+
created_by_id = Hash[created.map { |c| [c[:id], c] }]
|
132
|
+
|
133
|
+
db.bulk_docs(new_versions).then do |updated|
|
134
|
+
run_async do
|
135
|
+
updated.each do |ud|
|
136
|
+
cd = created_by_id[ud["id"]]
|
137
|
+
expect(ud["rev"]).not_to eq(cd["rev"])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
async "mixes errors with successes (non-transactional)" do
|
146
|
+
with_new_database do |db|
|
147
|
+
db.put(docs_with_ids.first).then do
|
148
|
+
db.bulk_docs(docs_with_ids)
|
149
|
+
end.then do |response|
|
150
|
+
run_async do
|
151
|
+
errors = response.select { |r| r.is_a?(Exception) }
|
152
|
+
ok = response - errors
|
153
|
+
|
154
|
+
expect(ok.size).to eq(2)
|
155
|
+
expect(errors.size).to eq(1)
|
156
|
+
expect(errors.first.message).to match(/conflict/)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "#get" do
|
164
|
+
async "calls the returned Promise's success handler with a Document" do
|
165
|
+
with_new_database do |db|
|
166
|
+
db.put(_id: "magic_object", contents: "It's Magic").then do
|
167
|
+
db.get("magic_object")
|
168
|
+
end.then do |doc|
|
169
|
+
run_async do
|
170
|
+
expect(doc["_id"]).to eq("magic_object")
|
171
|
+
expect(doc["contents"]).to eq("It's Magic")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
async "correctly serializes/deserializes nested Hashes" do
|
178
|
+
with_new_database do |db|
|
179
|
+
promise = db.put(_id: "nasty_nested",
|
180
|
+
contents: { foo: { bar: { baz: 1 } } })
|
181
|
+
|
182
|
+
promise.then do
|
183
|
+
db.get("nasty_nested")
|
184
|
+
end.then do |document|
|
185
|
+
run_async do
|
186
|
+
expect(document["contents"]["foo"]["bar"]["baz"]).to eq(1)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "#all_docs" do
|
194
|
+
async "fetches every Document by default" do
|
195
|
+
with_new_database do |db|
|
196
|
+
db.bulk_docs(docs_with_ids).then do
|
197
|
+
db.all_docs
|
198
|
+
end.then do |rows|
|
199
|
+
run_async do
|
200
|
+
expect(rows.size).to eq(sorted_ids.size)
|
201
|
+
expect(rows.map(&:id).sort).to eq(sorted_ids)
|
202
|
+
expect(rows.first.document).to eq({})
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# dodecaphonic: This is non-exhaustive. I just want to check if
|
209
|
+
# passing options actually goes through.
|
210
|
+
describe "passing options along" do
|
211
|
+
async "allows for full Documents to come with a Row" do
|
212
|
+
with_new_database do |db|
|
213
|
+
db.bulk_docs(docs_with_ids).then do
|
214
|
+
db.all_docs(include_docs: true)
|
215
|
+
end.then do |rows|
|
216
|
+
run_async do
|
217
|
+
expect(rows.first.document["name"]).not_to be_empty
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
async "can limit the number of Rows to return" do
|
224
|
+
with_new_database do |db|
|
225
|
+
db.bulk_docs(docs_with_ids).then do
|
226
|
+
db.all_docs(key: "banana").then do |rows|
|
227
|
+
run_async do
|
228
|
+
expect(rows.size).to eq(1)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "#remove" do
|
238
|
+
let(:doc) { docs_with_ids.first }
|
239
|
+
|
240
|
+
describe "with a Document containing an _id and _rev" do
|
241
|
+
async "works correctly" do
|
242
|
+
with_new_database do |db|
|
243
|
+
db.put(doc).then do |created|
|
244
|
+
run_async do
|
245
|
+
to_remove = { _id: created["id"], _rev: created["rev"] }
|
246
|
+
db.remove(doc: to_remove)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end.then do |removed|
|
250
|
+
run_async do
|
251
|
+
expect(removed["ok"]).to be(true)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
async "fails if _id is missing" do
|
257
|
+
with_new_database(false) do |db|
|
258
|
+
db.put(doc).then do |created|
|
259
|
+
run_async do
|
260
|
+
to_remove = { _rev: created["rev"] }
|
261
|
+
db.remove(doc: to_remove)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end.fail do |error|
|
265
|
+
run_async do
|
266
|
+
expect(error.message).to match(/missing/)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
async "fails if _rev is missing" do
|
272
|
+
with_new_database(false) do |db|
|
273
|
+
db.put(doc).then do |created|
|
274
|
+
run_async do
|
275
|
+
to_remove = { _id: created["id"] }
|
276
|
+
db.remove(doc: to_remove)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end.fail do |error|
|
280
|
+
run_async do
|
281
|
+
expect(error.message).to match(/missing/)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
describe "passing its _id and _rev explicitly" do
|
288
|
+
async "works correctly" do
|
289
|
+
with_new_database do |db|
|
290
|
+
db.put(doc).then do |created|
|
291
|
+
run_async do
|
292
|
+
db.remove(doc_id: created["id"], doc_rev: created["rev"])
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end.then do |removed|
|
296
|
+
run_async do
|
297
|
+
expect(removed.ok).to be(true)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
async "fails if _id is missing" do
|
303
|
+
with_new_database(false) do |db|
|
304
|
+
db.put(doc).then do |created|
|
305
|
+
run_async do
|
306
|
+
db.remove(doc_rev: created["rev"])
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end.fail do |error|
|
310
|
+
run_async do
|
311
|
+
expect(error.message).to match(/missing/)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
async "fails if _rev is missing" do
|
317
|
+
with_new_database(false) do |db|
|
318
|
+
db.put(doc).then do |created|
|
319
|
+
run_async do
|
320
|
+
db.remove(doc_id: created["id"])
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end.fail do |error|
|
324
|
+
run_async do
|
325
|
+
expect(error.message).to match(/missing/)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|