...

Source file src/github.com/go-kivik/kivik/v4/pouchdb/pouchdb.go

Documentation: github.com/go-kivik/kivik/v4/pouchdb

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  //go:build js
    14  
    15  package pouchdb
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"net/http"
    23  	"net/url"
    24  	"strings"
    25  	"sync"
    26  
    27  	kivik "github.com/go-kivik/kivik/v4"
    28  	"github.com/go-kivik/kivik/v4/driver"
    29  	internal "github.com/go-kivik/kivik/v4/int/errors"
    30  	"github.com/go-kivik/kivik/v4/pouchdb/bindings"
    31  )
    32  
    33  type pouchDriver struct{}
    34  
    35  var _ driver.Driver = &pouchDriver{}
    36  
    37  func init() {
    38  	kivik.Register("pouch", &pouchDriver{})
    39  }
    40  
    41  // NewClient returns a PouchDB client handle. Provide a dsn only for remote
    42  // databases. Otherwise specify ""
    43  func (d *pouchDriver) NewClient(dsn string, options driver.Options) (driver.Client, error) {
    44  	var u *url.URL
    45  	var user *url.Userinfo
    46  	if dsn != "" {
    47  		var err error
    48  		u, err = url.Parse(dsn)
    49  		if err != nil {
    50  			return nil, fmt.Errorf("Invalid DSN URL '%s' provided: %s", dsn, err)
    51  		}
    52  		user = u.User
    53  		u.User = nil
    54  	}
    55  	pouch := bindings.GlobalPouchDB()
    56  	client := &client{
    57  		dsn:   u,
    58  		pouch: pouch,
    59  		opts:  make(map[string]Options),
    60  	}
    61  	if user != nil {
    62  		pass, _ := user.Password()
    63  		client.setAuth(user.Username(), pass)
    64  	}
    65  	options.Apply(client)
    66  	return client, nil
    67  }
    68  
    69  func (c *client) setAuth(username, password string) {
    70  	c.opts["authenticator"] = Options{
    71  		"auth": map[string]interface{}{
    72  			"username": username,
    73  			"password": password,
    74  		},
    75  	}
    76  }
    77  
    78  type client struct {
    79  	dsn   *url.URL
    80  	opts  map[string]Options
    81  	pouch *bindings.PouchDB
    82  
    83  	// This maintains a list of running replications
    84  	replications   []*replication
    85  	replicationsMU sync.RWMutex
    86  }
    87  
    88  var _ driver.Client = &client{}
    89  
    90  // AllDBs returns the list of all existing databases. This function depends on
    91  // the pouchdb-all-dbs plugin being loaded.
    92  func (c *client) AllDBs(ctx context.Context, _ driver.Options) ([]string, error) {
    93  	if c.dsn == nil {
    94  		return c.pouch.AllDBs(ctx)
    95  	}
    96  	return nil, errors.New("AllDBs() not implemented for remote PouchDB databases")
    97  }
    98  
    99  func (c *client) Version(context.Context) (*driver.Version, error) {
   100  	ver := c.pouch.Version()
   101  	return &driver.Version{
   102  		Version:     ver,
   103  		Vendor:      "PouchDB",
   104  		RawResponse: json.RawMessage(fmt.Sprintf(`{"version":"%s","vendor":{"name":"PouchDB"}}`, ver)),
   105  	}, nil
   106  }
   107  
   108  func (c *client) dbURL(db string) string {
   109  	if c.dsn == nil {
   110  		// No transformation for local databases
   111  		return db
   112  	}
   113  	myURL := *c.dsn // Make a copy
   114  	myURL.Path += strings.TrimLeft(db, "/")
   115  	return myURL.String()
   116  }
   117  
   118  // Options is a struct of options, as documented in the PouchDB API.
   119  type Options map[string]interface{}
   120  
   121  func (c *client) options(options ...Options) Options {
   122  	o := Options{}
   123  	for _, defOpts := range c.opts {
   124  		for k, v := range defOpts {
   125  			o[k] = v
   126  		}
   127  	}
   128  	for _, opts := range options {
   129  		for k, v := range opts {
   130  			o[k] = v
   131  		}
   132  	}
   133  	return o
   134  }
   135  
   136  func (c *client) isRemote() bool {
   137  	return c.dsn != nil
   138  }
   139  
   140  // DBExists returns true if the requested DB exists. This function only works
   141  // for remote databases. For local databases, it creates the database.
   142  // Silly PouchDB.
   143  func (c *client) DBExists(ctx context.Context, dbName string, options driver.Options) (bool, error) {
   144  	pouchOpts := map[string]interface{}{"skip_setup": true}
   145  	options.Apply(pouchOpts)
   146  	_, err := c.pouch.New(c.dbURL(dbName), pouchOpts).Info(ctx)
   147  	if err == nil {
   148  		return true, nil
   149  	}
   150  	if kivik.HTTPStatus(err) == http.StatusNotFound {
   151  		return false, nil
   152  	}
   153  	return false, err
   154  }
   155  
   156  func (c *client) CreateDB(ctx context.Context, dbName string, options driver.Options) error {
   157  	if c.isRemote() {
   158  		if exists, _ := c.DBExists(ctx, dbName, options); exists {
   159  			return &internal.Error{Status: http.StatusPreconditionFailed, Message: "database exists"}
   160  		}
   161  	}
   162  	pouchOpts := map[string]interface{}{}
   163  	options.Apply(pouchOpts)
   164  	_, err := c.pouch.New(c.dbURL(dbName), pouchOpts).Info(ctx)
   165  	return err
   166  }
   167  
   168  func (c *client) DestroyDB(ctx context.Context, dbName string, options driver.Options) error {
   169  	exists, err := c.DBExists(ctx, dbName, options)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	if !exists {
   174  		// This will only ever do anything for a remote database
   175  		return &internal.Error{Status: http.StatusNotFound, Message: "database does not exist"}
   176  	}
   177  	pouchOpts := map[string]interface{}{}
   178  	options.Apply(pouchOpts)
   179  	return c.pouch.New(c.dbURL(dbName), pouchOpts).Destroy(ctx, nil)
   180  }
   181  
   182  func (c *client) DB(dbName string, options driver.Options) (driver.DB, error) {
   183  	pouchOpts := map[string]interface{}{}
   184  	options.Apply(pouchOpts)
   185  	return &db{
   186  		// TODO: #68 Consider deferring this pouch.New call until the first use,
   187  		// so ctx can be used.
   188  		db:     c.pouch.New(c.dbURL(dbName), pouchOpts),
   189  		client: c,
   190  	}, nil
   191  }
   192  

View as plain text