- 版本:1.0.0-alpha.5
- GitHub: https://github.com/Alexander-Bliznyuk/nativescript-plugins
- NPM: https://npmjs.net.cn/package/%40parempi%2Fcouchbase-lite
- 下载
- 昨日: 0
- 上周: 0
- 上个月: 0
@parempi/couchbase-lite
这是一个 NativeScript 的 Couchbase Lite 插件。由于它处于 alpha 阶段,您现在最好使用 Osei Fortune 开发的更成熟和稳定的解决方案。我在几个项目中广泛使用了他的插件。尽管它提供了相当不错的 API,但它迫使我不得不在执行连接和 Couchbase 函数时采取一些手段。此外,它阻塞了主线程,从而显著降低了应用程序的响应速度。我相信,插件应该建立在本地库之上,而不是限制其功能。
免责声明:仍在开发中,API 可能会更改,不支持 iOS。
介绍 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