@parempi/couchbase-lite
由 parempi 创建 | v1.0.0-alpha.5
NativeScript Couchbase Lite 插件
npm i --save @parempi/couchbase-lite

@parempi/couchbase-lite

这是一个 NativeScript 的 Couchbase Lite 插件。由于它处于 alpha 阶段,您现在最好使用 Osei Fortune 开发的更成熟和稳定的解决方案。我在几个项目中广泛使用了他的插件。尽管它提供了相当不错的 API,但它迫使我不得不在执行连接和 Couchbase 函数时采取一些手段。此外,它阻塞了主线程,从而显著降低了应用程序的响应速度。我相信,插件应该建立在本地库之上,而不是限制其功能。

免责声明:仍在开发中,API 可能会更改,不支持 iOS。

  1. Couchbase 是什么??
  2. 安装
  3. 用法
    1. 概述
    2. 演示应用程序
    3. 访问本地 API
    4. 插件 API
      1. 数据库
      2. 文档
      3. 事务
      4. 查询
  4. 许可证

介绍 Couchbase Lite

来源:https://docs.couchbase.com/couchbase-lite/2.8/index.html

Couchbase Lite 是一个嵌入式、NoSQL JSON 文档风格的数据库,适用于您的移动应用程序。

您可以使用 Couchbase Lite 在您的移动应用程序中作为独立的嵌入式数据库,或者与 Sync Gateway 和 Couchbase Server 一起使用,以提供完整的云到边缘同步解决方案,获得 SQL 的灵活性和 JSON 的适应性。

本地工作...

Couchbase Lite 旨在与存储在本地存储中的数据一起工作,并包括以下功能:

  • 使用基于 SQL 的语义编写查询的能力。
  • 对存储在本地存储中的文档进行全文搜索查询。
  • 存储文档附件(blob),例如图像或 PDF 文件的能力。

边缘同步...

它通过以下方式自动管理数据同步:

  • 在 WebSockets 上构建的复制协议,用于与 Sync Gateway 同步数据。
  • 一种对等同步实现,用于在 Couchbase Lite 客户端之间同步数据,无需依赖集中式控制。

安装

添加 ns 插件 @parempi/couchbase-lite
所需的最小 Android SDK 版本是 22(Lollipop)

如果您要与应用程序一起打包预构建的 zip 数据库,请在 webpack.config.js 中添加以下行
webpack.Utils.addCopyRule("**/*.zip");

用法

概述

import {open, cbl} from "@parempi/couchbase-lite";

const {Expression:{property, string, intValue}, Ordering:{property: orderingProperty}} = cbl;

open("getting-started").then(async (database) => {
const mutableDoc = database.createDocument({version: 2.0, type: "SDK"});

mutableDoc.languages = ['JavaScript', 'Java', 'Objective-C'];
database.save(mutableDoc);


const sdks = await database.fetch(
query => query
.where(property("type").equalTo(string("SDK")))
.orderBy([orderingProperty("version").descending()])
.limit(intValue(5))
)
.rows();

for (const sdk of sdks) {
sdk.manyReleases = true;
}
try {
await database.saveInBatch(sdks);
} catch (e) {
console.log(e.message);
}
});

演示应用程序

这是一个非常简单的纯 NativeScript 应用程序。需要改进。

访问本地 API

正如您可能知道的,NativeScript 通过 JavaScript 直接提供对本地 API 的访问。此插件为您提供 d.ts 类型定义,以启用 IntelliSense。到目前为止,仅支持 Android。JavaScript 的语法和 NativeScript 足以在不作任何修改的情况下在 官方 Android 指南 中重现示例。

例如,此 JavaScript 代码对应于以下 Java 代码
import {Utils} from "@nativescript/core";
import {knownFolders} from "@nativescript/core/file-system";



// (1) Initialize the Couchbase Lite system and enable IntelliSense
import "@parempi/couchbase-lite";
// or you can import any of exported names (see API reference)
import {cbl} from "@parempi/couchbase-lite";


// then IDEs will import these for you as you code
import CouchbaseLite = com.couchbase.lite.CouchbaseLite;
import DatabaseConfiguration = com.couchbase.lite.DatabaseConfiguration;
import Database = com.couchbase.lite.Database;
import MutableDocument = com.couchbase.lite.MutableDocument;
import SelectResult = com.couchbase.lite.SelectResult;
import QueryBuilder = com.couchbase.lite.QueryBuilder;
import DataSource = com.couchbase.lite.DataSource;
import Expression = com.couchbase.lite.Expression;

// here's an example of another style
const {URLEndpoint, ReplicatorConfiguration, BasicAuthenticator, Replicator, ReplicatorChangeListener} = cbl;


// (2) Get the database (and create it if it doesn’t exist).
const config = new DatabaseConfiguration();

config.setDirectory(knownFolders.currentApp().parent.path);

const database = new cbl.Database("getting-started", config);


// (3) Create a new document (i.e. a record) in the database.
let mutableDoc = new MutableDocument()
.setFloat("version", 2.0)
.setString("type", "SDK");

// (4) Save it to the database.
database.save(mutableDoc);

// (5) Update a document.
mutableDoc = database.getDocument(mutableDoc.getId()).toMutable();
mutableDoc.setString("language", "JavaScript");
database.save(mutableDoc);
const document = database.getDocument(mutableDoc.getId());
// (6) Log the document ID (generated by the database) and properties
console.log( "Document ID :: " + document.getId());
console.log("Learning " + document.getString("language"));

// (7) Create a query to fetch documents of type SDK.
const query = QueryBuilder.select([SelectResult.all()])
.from(DataSource.database(database))
.where(Expression.property("type").equalTo(Expression.string("SDK")));
const result = query.execute();
console.log("Number of rows :: " + result.allResults().size());

// (8) Create replicators to push and pull changes to and from the cloud.
const targetEndpoint = new URLEndpoint(new java.net.URI("ws://localhost:4984/getting-started-db"));
const replConfig = new ReplicatorConfiguration(database, targetEndpoint);
replConfig.setReplicatorType(ReplicatorConfiguration.ReplicatorType.PUSH_AND_PULL);

// (9) Add authentication.
replConfig.setAuthenticator(new BasicAuthenticator("sync-gateway", "password"));

// (10) Create replicator (be sure to hold a reference somewhere that will prevent the Replicator from being GCed)
const replicator = new Replicator(replConfig);

// (11) Listen to replicator change events.
replicator.addChangeListener(new ReplicatorChangeListener({
changed: change => {
if (change.getStatus().getError() !== null) {
console.log("Error code :: " + change.getStatus().getError().getCode());
}
}
}));

// (12) Start replication.
replicator.start();
对应于以下 Java 代码
// (1) Initialize the Couchbase Lite system
CouchbaseLite.init(context);

// (2) Get the database (and create it if it doesn’t exist).
DatabaseConfiguration config = new DatabaseConfiguration();

config.setDirectory(context.getFilesDir().

getAbsolutePath());

Database database = new Database("getting-started", config);


// (3) Create a new document (i.e. a record) in the database.
MutableDocument mutableDoc = new MutableDocument()
.setFloat("version", 2.0F)
.setString("type", "SDK");

// (4) Save it to the database.
database.save(mutableDoc);

// (5) Update a document.
mutableDoc = database.getDocument(mutableDoc.getId()).toMutable();
mutableDoc.setString("language", "Java");
database.save(mutableDoc);
Document document = database.getDocument(mutableDoc.getId());
// (6) Log the document ID (generated by the database) and properties
Log.i(TAG, "Document ID :: " + document.getId());
Log.i(TAG, "Learning " + document.getString("language"));

// (7) Create a query to fetch documents of type SDK.
Query query = QueryBuilder.select(SelectResult.all())
.from(DataSource.database(database))
.where(Expression.property("type").equalTo(Expression.string("SDK")));
ResultSet result = query.execute();
Log.i(TAG, "Number of rows :: " + result.allResults().size());

// (8) Create replicators to push and pull changes to and from the cloud.
Endpoint targetEndpoint = new URLEndpoint(new URI("ws://localhost:4984/getting-started-db"));
ReplicatorConfiguration replConfig = new ReplicatorConfiguration(database, targetEndpoint);
replConfig.setReplicatorType(ReplicatorConfiguration.ReplicatorType.PUSH_AND_PULL);

// (9) Add authentication.
replConfig.setAuthenticator(new BasicAuthenticator("sync-gateway", "password"));

// (10) Create replicator (be sure to hold a reference somewhere that will prevent the Replicator from being GCed)
Replicator replicator = new Replicator(replConfig);

// (11) Listen to replicator change events.
replicator.addChangeListener(change -> {
if (change.getStatus().getError() != null) {
Log.i(TAG, "Error code :: " + change.getStatus().getError().getCode());
}
});

// (12) Start replication.
replicator.start();

免责声明:当然,iOS Couchbase Lite 本地库略有不同,因此我必须编写一个适配器,以便相同的 JavaScript 代码可以在两个平台上无缝运行。目前,此插件不支持 iOS。

插件 API

由于 NativeScript,我们可以通过简单地调用本地库来完成很多事情——这非常令人印象深刻。然而,与 Couchbase Lite 编写的某些低级语言相比,JavaScript 更为动态和简洁。JavaScript 的缺点是它缺乏多线程。

@parempi/couchbase-lite 两种世界的优点。

以下各节将带您了解我至今开发的本地 Couchbase Lite 的便利扩展。

强烈建议您在 https://docs.couchbase.com/couchbase-lite/current/index.html 上学习更多。这样您就能设置同步网关、故障排除、使用 cblite 命令行工具,并充分利用 Couchbase Lite。

数据库

您可以使用 open 便利函数创建一个新的数据库或打开现有的数据库。只需传递一个数据库名称或一个 PrebuiltDatabase 对象。

const db = await open('exampe');

如果数据库 "example" 不存在,该函数将解压缩并安装 ${knownFolders.currentApp().path}/example.cblite2.zip 归档,如果它存在的话。

open 在后台线程中运行。

您可能需要更详细地指定预构建数据库。

import PrebuiltDbFolder = com.parempi.couchbase.PrebuiltDbFolder;
import PrebuiltDbZip = com.parempi.couchbase.PrebuiltDbZip;


// a db bundled as a folder ${knownFolders.currentApp().path}/assets/example.cblite2
open(new PrebuiltDbFolder('example', knownFolders.currentApp().getFolder('assets').path));

// You can implement your own PrebuiltDb.
// Wouldn't it be fancy to install a database from cdn?
open(new MyFancyCdnPrebuiltDb('
example', 'https://cdn.example.com/'));

您可以在 /data/data/${APPLICATION_ID}/files/${DATABASE_NAME}.cblite2(即 knownFolders.currentApp().parent.path))中找到并 adb pull 安装的数据库。

当数据库不再需要时,强烈建议关闭数据库。

database.close();

文档


// Plain Old JavaScript Object will do
const POJODocument = {
type: 'task',
owner: 'todo',
title: 'create readme.md',
createdAt: new Date(),
// "id" is the only property that bears a special meaning
// if ommited Couchbase Lite will generate an id for you upon persisting in the database
id: 'task_readme',
};

db.save(POJODocument);
import {toNativeDate} from "@parempi/couchbase-lite/toNativeCblConverter";

// here is a native document
const nativeDocument = new MutableDocument('task_readme'); // again, it's possible to omit id

// for those who prefer verbose syntax and explicit types or aim for extra performace
nativeDocument
.setString('
type', 'task')
.setString('
owner', 'todo')
.setString('
title', 'create readme.md')
.setDate('
createdAt', toNativeDate(new Date()));

db.save(nativeDocument);

现在让我们来认识代理。

// queries a document from the database or initializes a new one; returns its proxy
const proxyDocument = db.getDocument('task_readme'); // here an id is mandatory

// this works
proxyDocument
.setString('type', 'task')
.setString('owner', 'todo')
.setString('title', 'create readme.md')
.setDate('createdAt', toNativeDate(new Date()))
.setArray('tags',
new MutableArray()
.addString('open source')
.addString('hobby')
);

// as well as this
proxyDocument.type = 'task';
proxyDocument['owner'] = 'todo';
proxyDocument.title = 'create readme.md';
proxyDocument.createdAt = new Date();
proxyDocument.tags = ['open sorce', 'hobby',];

db.save(proxyDocument);

为什么需要代理?它们持有原生对象的引用,并模仿熟悉的普通旧JavaScript对象的行为,同时消除从/到原生类型的转换。然而,由于它们的转换是在需要时进行的,因此访问属性是最慢的。

因此,当保存大量文档或查询数据库成为瓶颈时,这种方法将节省一些CPU周期。

// saves a new document and returns its proxy
const proxyDocument = db.createDocument({
type: 'task',
owner: 'todo',
title: 'create readme.md',
createdAt: new Date(),
});
console.log(proxyDocument.title, proxyDocument.createdAt);

console.assert(proxyDocument.title === db.getDocument(proxyDocument.getId()).title)

// update the document
proxyDocument.owner = 'todo1';
proxyDocument.lastModified = new Date();
db.save(proxyDocument);

可能有多个更新来源。同一文档可能同时由复制器和本地 Database.save 调用更新。有关详细信息,请参阅官方 Couchbase Lite 指南。您可以选择一种解决此类情况的战略。

import ConcurrencyControl = com.couchbase.lite.ConcurrencyControl;

db.save({foo: 'bar',}, ConcurrencyControl.FAIL_ON_CONFLICT);

删除文档。

db.deleteDocument('docId');

事务

如果您同时要对数据库进行多项更改,将它们分组在一起会更快。 Database.saveInBatch 保存了多次数据库提交的开销,大大提高了性能。它在后台线程中运行,以防止UI被阻塞。以下示例演示了如何批量持久化几个文档。

const docs = [];
for (let i = 0; i < 10; i++) {
docs.push({
type: "user",
name: "user" + i,
admin: false,
});
}
try {
database.saveInBatch(docs);
} catch (e) {
console.log(e.message);
}

在本地级别,此操作仍然是事务性的:在块执行期间,其他数据库实例(包括复制器管理的实例)不能进行更改,其他实例将不会看到部分更改。

查询

db[ .setAlias(dbAlias: string) ]
.fetch( queryBuilder?: (query: From) => AbstractQuery )
[ .distinct() ]
[ .plain() | .json() ]
( .rows(...props: SelectResult[]) | column(prop: SelectResult) | value(prop: SelectResult) )

import {cbl} from "@parempi/couchbase-lite";

const {Expression: {property, string}, SelectResult: {expression: selectExpression}, ArrayExpression: {every}} = cbl;

// get list of all document types in the database
await database
.fetch()
.distinct()
.plain() // POJO
.column( selectExpression(property('type')) ); // select only one field




// count airports in San Diego
await database.fetch(
query => query
.where(
property("type").equalTo(string("airport"))
.and(
property('city').equalTo(string('San Diego'))
)
)
)
// select the first field of the first row
.value( selectExpression( Function.count(string("*")) ) );



// get hotels that welcome guests with pets
await database.fetch(
query => query
.where(
property("pets_ok").equalTo(booleanValue(true))
)
)
.documents(); // proxified documents


// find how many top-rated accomodation options are in each city
const countExpression = Function.count(Expression.all()),
reviewVar = ArrayExpression.variable("review"),
overallVar = ArrayExpression.variable("review.ratings.Overall");


await dbdatabase.fetch(
query => query
.where(
property("type").equalTo(string("hotel"))
.and(
property('city').notNullOrMissing())
.and(
every(reviewVar).in(property('reviews'))
.satisfies( overallVar.greaterThanOrEqualTo(intValue(4)) )
)
)
.groupBy( [property("city")] )
.having( countExpression.greaterThanOrEqualTo(intValue(10)) )
.orderBy( [Ordering.expression(countExpression).descending()] )
)
.rows(
selectExpression(property('country')),
selectExpression(property('state')),
selectExpression(property('city')),
selectExpression(countExpression).as('hotels_qty')
); // proxified dictionaries of specified fields; all fields + meta.id by default (i.e.if nothing selected)




// get all flights departing from Riga airport
const results = await database.setAlias('airline') /* set alias for join clause */ .fetch(
query => query
.join([
Join.join(DataSource.database(database).as("route"))
.on(
Meta.id.from("airline").equalTo(property("airlineid").from("route"))
)
])
.where(
property("type").from("route").equalTo(string("route"))
.and(
property("type").from("airline").equalTo(string("airline"))
)
.and(
property("sourceairport").from("route").equalTo(string("RIX"))
)
)
)
.json()
.rows(
selectExpression(Function.upper( property("name").from("airline") )),
selectExpression(property("callsign").from("airline")),
selectExpression(Function.upper( property("destinationairport").from("route") )),
selectExpression(property("stops").from("route")),
selectExpression(Function.upper( property("airline").from("route") ))
);

许可证

Apache License 版本 2.0