1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package bindings
18
19 import (
20 "bytes"
21 "context"
22 "encoding/json"
23 "errors"
24 "io"
25 "net/http"
26 "reflect"
27 "time"
28
29 "github.com/gopherjs/gopherjs/js"
30 "github.com/gopherjs/jsbuiltin"
31
32 internal "github.com/go-kivik/kivik/v4/int/errors"
33 )
34
35
36 type DB struct {
37 *js.Object
38 }
39
40
41 type PouchDB struct {
42 *js.Object
43 }
44
45
46 func GlobalPouchDB() *PouchDB {
47 return &PouchDB{Object: js.Global.Get("PouchDB")}
48 }
49
50
51
52 func Defaults(options map[string]interface{}) *PouchDB {
53 return &PouchDB{Object: js.Global.Get("PouchDB").Call("defaults", options)}
54 }
55
56
57
58
59 func (p *PouchDB) New(dbName string, options map[string]interface{}) *DB {
60 db := &DB{Object: p.Object.New(dbName, options)}
61 if db.indexeddb() {
62
77 time.Sleep(0)
78 }
79 return db
80 }
81
82
83 func (p *PouchDB) Version() string {
84 return p.Get("version").String()
85 }
86
87 func setTimeout(ctx context.Context, options map[string]interface{}) map[string]interface{} {
88 if ctx == nil {
89 return options
90 }
91 deadline, ok := ctx.Deadline()
92 if !ok {
93 return options
94 }
95 if options == nil {
96 options = make(map[string]interface{})
97 }
98 if _, ok := options["ajax"]; !ok {
99 options["ajax"] = make(map[string]interface{})
100 }
101 ajax := options["ajax"].(map[string]interface{})
102 timeout := int(time.Until(deadline) * 1000)
103
104 ajax["timeout"] = timeout
105
106 options["timeout"] = timeout
107 return options
108 }
109
110 type caller interface {
111 Call(string, ...interface{}) *js.Object
112 }
113
114
115
116 func prepareArgs(args []interface{}) []interface{} {
117 for len(args) > 0 {
118 if !omitNil(args[len(args)-1]) {
119 break
120 }
121 args = args[:len(args)-1]
122 }
123 return args
124 }
125
126
127
128 func omitNil(a interface{}) bool {
129 if a == nil {
130
131 return false
132 }
133 v := reflect.ValueOf(a)
134 switch v.Kind() {
135 case reflect.Slice, reflect.Interface, reflect.Map, reflect.Ptr:
136
137
138
139 return v.IsNil()
140 }
141 return false
142 }
143
144
145
146
147
148 func callBack(ctx context.Context, o caller, method string, args ...interface{}) (r *js.Object, e error) {
149 defer RecoverError(&e)
150 resultCh := make(chan *js.Object)
151 var err error
152 o.Call(method, prepareArgs(args)...).Call("then", func(r *js.Object) {
153 go func() { resultCh <- r }()
154 }).Call("catch", func(e *js.Object) {
155 err = NewPouchError(e)
156 close(resultCh)
157 })
158 select {
159 case <-ctx.Done():
160 return nil, ctx.Err()
161 case result := <-resultCh:
162 return result, err
163 }
164 }
165
166
167 func (p *PouchDB) AllDBs(ctx context.Context) ([]string, error) {
168 if jsbuiltin.TypeOf(p.Get("allDbs")) != jsbuiltin.TypeFunction {
169 return nil, errors.New("pouchdb-all-dbs plugin not loaded")
170 }
171 result, err := callBack(ctx, p, "allDbs")
172 if err != nil {
173 return nil, err
174 }
175 if result == js.Undefined {
176 return nil, nil
177 }
178 allDBs := make([]string, result.Length())
179 for i := range allDBs {
180 allDBs[i] = result.Index(i).String()
181 }
182 return allDBs, nil
183 }
184
185
186 type DBInfo struct {
187 *js.Object
188 Name string `js:"db_name"`
189 DocCount int64 `js:"doc_count"`
190 UpdateSeq string `js:"update_seq"`
191 }
192
193
194 func (db *DB) Info(ctx context.Context) (*DBInfo, error) {
195 result, err := callBack(ctx, db, "info")
196 return &DBInfo{Object: result}, err
197 }
198
199
200
201 func (db *DB) Put(ctx context.Context, doc interface{}, opts map[string]interface{}) (rev string, err error) {
202 result, err := callBack(ctx, db, "put", doc, setTimeout(ctx, opts))
203 if err != nil {
204 return "", err
205 }
206 return result.Get("rev").String(), nil
207 }
208
209
210
211 func (db *DB) Post(ctx context.Context, doc interface{}, opts map[string]interface{}) (docID, rev string, err error) {
212 result, err := callBack(ctx, db, "post", doc, setTimeout(ctx, opts))
213 if err != nil {
214 return "", "", err
215 }
216 return result.Get("id").String(), result.Get("rev").String(), nil
217 }
218
219
220
221 func (db *DB) Get(ctx context.Context, docID string, opts map[string]interface{}) (doc []byte, rev string, err error) {
222 result, err := callBack(ctx, db, "get", docID, setTimeout(ctx, opts))
223 if err != nil {
224 return nil, "", err
225 }
226 resultJSON := js.Global.Get("JSON").Call("stringify", result).String()
227 return []byte(resultJSON), result.Get("_rev").String(), err
228 }
229
230
231
232 func (db *DB) Delete(ctx context.Context, docID, rev string, opts map[string]interface{}) (newRev string, err error) {
233 result, err := callBack(ctx, db, "remove", docID, rev, setTimeout(ctx, opts))
234 if err != nil {
235 return "", err
236 }
237 return result.Get("rev").String(), nil
238 }
239
240 func (db *DB) indexeddb() bool {
241 return db.Object.Get("__opts").Get("adapter").String() == "indexeddb"
242 }
243
244
245
246
247 func (db *DB) Purge(ctx context.Context, docID, rev string) ([]string, error) {
248 if db.Object.Get("purge") == js.Undefined {
249 return nil, &internal.Error{Status: http.StatusNotImplemented, Message: "kivik: purge supported by PouchDB 8 or newer"}
250 }
251 if !db.indexeddb() {
252 return nil, &internal.Error{Status: http.StatusNotImplemented, Message: "kivik: purge only supported with indexedDB adapter"}
253 }
254 result, err := callBack(ctx, db, "purge", docID, rev, setTimeout(ctx, nil))
255 if err != nil {
256 return nil, err
257 }
258 delRevs := result.Get("deletedRevs")
259 revs := make([]string, delRevs.Length())
260 for i := range revs {
261 revs[i] = delRevs.Index(i).String()
262 }
263 return revs, nil
264 }
265
266
267 func (db *DB) Destroy(ctx context.Context, options map[string]interface{}) error {
268 _, err := callBack(ctx, db, "destroy", setTimeout(ctx, options))
269 return err
270 }
271
272
273 func (db *DB) AllDocs(ctx context.Context, options map[string]interface{}) (*js.Object, error) {
274 return callBack(ctx, db, "allDocs", setTimeout(ctx, options))
275 }
276
277
278 func (db *DB) Query(ctx context.Context, ddoc, view string, options map[string]interface{}) (*js.Object, error) {
279 o := setTimeout(ctx, options)
280 return callBack(ctx, db, "query", ddoc+"/"+view, o)
281 }
282
283 const errFindPluginNotLoaded = internal.CompositeError("501 pouchdb-find plugin not loaded")
284
285
286
287
288
289
290 func (db *DB) Find(ctx context.Context, query interface{}) (*js.Object, error) {
291 if jsbuiltin.TypeOf(db.Object.Get("find")) != jsbuiltin.TypeFunction {
292 return nil, errFindPluginNotLoaded
293 }
294 queryObj, err := Objectify(query)
295 if err != nil {
296 return nil, err
297 }
298 return callBack(ctx, db, "find", queryObj)
299 }
300
301
302
303 func Objectify(i interface{}) (interface{}, error) {
304 var buf []byte
305 switch t := i.(type) {
306 case string:
307 buf = []byte(t)
308 case []byte:
309 buf = t
310 case json.RawMessage:
311 buf = t
312 default:
313 return i, nil
314 }
315 var x interface{}
316 err := json.Unmarshal(buf, &x)
317 if err != nil {
318 err = &internal.Error{Status: http.StatusBadRequest, Err: err}
319 }
320 return x, err
321 }
322
323
324
325 func (db *DB) Compact() error {
326 _, err := callBack(context.Background(), db, "compact")
327 return err
328 }
329
330
331
332 func (db *DB) ViewCleanup() error {
333 _, err := callBack(context.Background(), db, "viewCleanup")
334 return err
335 }
336
337 var jsJSON = js.Global.Get("JSON")
338
339
340
341 func (db *DB) BulkDocs(ctx context.Context, docs []interface{}, options map[string]interface{}) (result *js.Object, err error) {
342 defer RecoverError(&err)
343 jsDocs := make([]*js.Object, len(docs))
344 for i, doc := range docs {
345 jsonDoc, err := json.Marshal(doc)
346 if err != nil {
347 return nil, err
348 }
349 jsDocs[i] = jsJSON.Call("parse", string(jsonDoc))
350 }
351 if options == nil {
352 return callBack(ctx, db, "bulkDocs", jsDocs, setTimeout(ctx, nil))
353 }
354 return callBack(ctx, db, "bulkDocs", jsDocs, options, setTimeout(ctx, nil))
355 }
356
357
358
359
360 func (db *DB) Changes(ctx context.Context, options map[string]interface{}) (changes *js.Object, e error) {
361 defer RecoverError(&e)
362 return db.Call("changes", setTimeout(ctx, options)), nil
363 }
364
365
366
367
368 func (db *DB) PutAttachment(ctx context.Context, docID, filename, rev string, body io.Reader, ctype string) (*js.Object, error) {
369 att, err := attachmentObject(ctype, body)
370 if err != nil {
371 return nil, err
372 }
373 if rev == "" {
374 return callBack(ctx, db, "putAttachment", docID, filename, att, ctype)
375 }
376 return callBack(ctx, db, "putAttachment", docID, filename, rev, att, ctype)
377 }
378
379
380
381 func attachmentObject(contentType string, content io.Reader) (att *js.Object, err error) {
382 RecoverError(&err)
383 buf := new(bytes.Buffer)
384 if _, err := buf.ReadFrom(content); err != nil {
385 return nil, err
386 }
387 if buffer := js.Global.Get("Buffer"); jsbuiltin.TypeOf(buffer) == jsbuiltin.TypeFunction {
388
389 if jsbuiltin.TypeOf(buffer.Get("from")) == jsbuiltin.TypeFunction {
390
391 return buffer.Call("from", buf.String()), nil
392 }
393
394 return buffer.New(buf.String()), nil
395 }
396 if js.Global.Get("Blob") != js.Undefined {
397
398 return js.Global.Get("Blob").New([]interface{}{buf.Bytes()}, map[string]string{"type": contentType}), nil
399 }
400
401 return nil, errors.New("No Blob or Buffer support?!?")
402 }
403
404
405
406
407 func (db *DB) GetAttachment(ctx context.Context, docID, filename string, options map[string]interface{}) (*js.Object, error) {
408 return callBack(ctx, db, "getAttachment", docID, filename, setTimeout(ctx, options))
409 }
410
411
412
413
414 func (db *DB) RemoveAttachment(ctx context.Context, docID, filename, rev string) (*js.Object, error) {
415 return callBack(ctx, db, "removeAttachment", docID, filename, rev)
416 }
417
418
419
420
421
422
423 func (db *DB) CreateIndex(ctx context.Context, index interface{}) (*js.Object, error) {
424 if jsbuiltin.TypeOf(db.Object.Get("find")) != jsbuiltin.TypeFunction {
425 return nil, errFindPluginNotLoaded
426 }
427 return callBack(ctx, db, "createIndex", index)
428 }
429
430
431
432
433 func (db *DB) GetIndexes(ctx context.Context) (*js.Object, error) {
434 if jsbuiltin.TypeOf(db.Object.Get("find")) != jsbuiltin.TypeFunction {
435 return nil, errFindPluginNotLoaded
436 }
437 return callBack(ctx, db, "getIndexes")
438 }
439
440
441
442
443
444
445 func (db *DB) DeleteIndex(ctx context.Context, index interface{}) (*js.Object, error) {
446 if jsbuiltin.TypeOf(db.Object.Get("find")) != jsbuiltin.TypeFunction {
447 return nil, errFindPluginNotLoaded
448 }
449 return callBack(ctx, db, "deleteIndex", index)
450 }
451
452
453 const (
454 ReplicationEventChange = "change"
455 ReplicationEventComplete = "complete"
456 ReplicationEventPaused = "paused"
457 ReplicationEventActive = "active"
458 ReplicationEventDenied = "denied"
459 ReplicationEventError = "error"
460 )
461
462
463
464 func (p *PouchDB) Replicate(source, target interface{}, options map[string]interface{}) (result *js.Object, err error) {
465 defer RecoverError(&err)
466 return p.Call("replicate", source, target, options), nil
467 }
468
469
470
471
472 func (db *DB) Explain(ctx context.Context, query interface{}) (*js.Object, error) {
473 if jsbuiltin.TypeOf(db.Object.Get("find")) != jsbuiltin.TypeFunction {
474 return nil, errFindPluginNotLoaded
475 }
476 queryObj, err := Objectify(query)
477 if err != nil {
478 return nil, err
479 }
480 return callBack(ctx, db, "explain", queryObj)
481 }
482
483
484 func (db *DB) Close() error {
485
486
487 if jsbuiltin.TypeOf(db.Object.Get("close")) != jsbuiltin.TypeFunction {
488 return nil
489 }
490 _, err := callBack(context.Background(), db, "close")
491 return err
492 }
493
View as plain text