diff --git a/README.md b/README.md index ba1c060..b71a3a4 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ If you are using GitHub pages for hosting, this command is a convenient way to b We truly appreciate your interest in this project! This project is **community-maintained**, which means it's **not officially supported** by our support team. -If you need help, have found a bug, or want to contribute improvements, the best place to do that is right here — by [opening a GitHub issue](https://github.com/Couchbase-Ecosystem/cbl-reactnative-docs/issues). +If you need help, have found a bug, or want to contribute improvements, the best place to do that is right here - by [opening a GitHub issue](https://github.com/couchbase/cbl-reactnative-docs/issues). Our support portal is unable to assist with requests related to this project, so we kindly ask that all inquiries stay within GitHub. -Your collaboration helps us all move forward together — thank you! +Your collaboration helps us all move forward together - thank you! diff --git a/docs/DataSync/remote-sync-gateway.md b/docs/DataSync/remote-sync-gateway.md index 8986995..74c4233 100644 --- a/docs/DataSync/remote-sync-gateway.md +++ b/docs/DataSync/remote-sync-gateway.md @@ -5,7 +5,7 @@ sidebar_position: 2 # Remote Sync -> Description - _Couchbase Lite for Ionic — Synchronizing data changes between local and remote databases using Sync Gateway or Capella App Services_ +> Description - _Couchbase Lite for Ionic - Synchronizing data changes between local and remote databases using Sync Gateway or Capella App Services_ :::note All code examples are indicative only. They demonstrate the basic concepts and approaches to using a feature. Use them as inspiration and adapt these examples to best practice when developing applications for your platform. @@ -56,7 +56,7 @@ Couchbase Mobile uses a replication protocol based on WebSockets for replication #### Incompatibilities -Couchbase Lite’s replication protocol is incompatible with CouchDB-based databases. And since Couchbase Lite 2.x+ only supports the new protocol, you will need to run a version of Sync Gateway that supports it — see: [Compatibility](../ProductNotes/compatibility.md). +Couchbase Lite’s replication protocol is incompatible with CouchDB-based databases. And since Couchbase Lite 2.x+ only supports the new protocol, you will need to run a version of Sync Gateway that supports it - see: [Compatibility](../ProductNotes/compatibility.md). ### Ordering @@ -86,7 +86,7 @@ For backward compatibility with the code prior to >= 3.1, when you set up the re
-![Cbl Replication Scopes Collections 1](../../static/img/cbl-replication-scopes-collections-1.png) +![Cbl Replication Scopes Collections 1](/img/cbl-replication-scopes-collections-1.png)
@@ -94,7 +94,7 @@ For backward compatibility with the code prior to >= 3.1, when you set up the re
-![Cbl Replication Scopes Collections 2](../../static/img/cbl-replication-scopes-collections-2.png) +![Cbl Replication Scopes Collections 2](/img/cbl-replication-scopes-collections-2.png)
@@ -106,7 +106,7 @@ The user-defined collections specified in the Couchbase Lite replicator setup mu
-![Cbl Replication Scopes Collections 3](../../static/img/cbl-replication-scopes-collections-3.png) +![Cbl Replication Scopes Collections 3](/img/cbl-replication-scopes-collections-3.png)
@@ -114,7 +114,7 @@ The user-defined collections specified in the Couchbase Lite replicator setup mu
-![Cbl Replication Scopes Collections 4](../../static/img/cbl-replication-scopes-collections-4.png) +![Cbl Replication Scopes Collections 4](/img/cbl-replication-scopes-collections-4.png)
@@ -137,7 +137,7 @@ import { BasicAuthenticator, Replicator, ListenerToken -} from 'cbl-reactnative'; +} from '@couchbase/couchbase-lite-react-native'; // Create endpoint and authenticator const endpoint = new URLEndpoint('ws://localhost:4984/projects'); @@ -202,7 +202,7 @@ The `addCollection()` method is deprecated. It remains available for backward co The new API allows each collection to have its own replication settings: ```typescript -import { CollectionConfiguration } from 'cbl-reactnative'; +import { CollectionConfiguration } from '@couchbase/couchbase-lite-react-native'; // Configure users collection const usersConfig = new CollectionConfiguration(usersCollection) @@ -259,7 +259,7 @@ config.addCollection(collectionName); ``` :::note -Note use of the scheme prefix (`wss://` to ensure TLS encryption — strongly recommended in production — or `ws://`) +Note use of the scheme prefix (`wss://` to ensure TLS encryption - strongly recommended in production - or `ws://`) ::: ### Sync Mode @@ -298,7 +298,7 @@ In the event it detects a transient error, the replicator will attempt to reconn On each retry the interval between attempts is increased exponentially (exponential backoff) up to the maximum wait time limit (5 minutes). -The REST API provides configurable control over this replication retry logic using a set of configiurable properties — see: [Table 1](#table-1-replication-retry-configuration-properties). +The REST API provides configurable control over this replication retry logic using a set of configiurable properties - see: [Table 1](#table-1-replication-retry-configuration-properties). #### Table 1. Replication Retry Configuration Properties @@ -308,7 +308,7 @@ The REST API provides configurable control over this replication retry logic usi | `maxAttempts()` | Change this to limit or extend the number of retry attempts. | The maximum number of retry attempts.

Set to zero (0) to use default values.

Set to one (1) to prevent any retry attempt.

The retry attempt count is reset when the replicator is able to connect and replicate.

Default values are:
- Single-shot replication = 9
- Continuous replication = maximum integer value.

Negative values generate a Couchbase exception `InvalidArgumentException`. | | `maxAttemptWaitTime()` | Change this to adjust the interval between retries. | The maximum interval between retry attempts.

While you can configure the maximum permitted wait time, the replicator’s exponential backoff algorithm calculates each individual interval which is not configurable.

Default value: 300 seconds (5 minutes).

Zero sets the maximum interval between retries to the default of 300 seconds.

300 sets the maximum interval between retries to the default of 300 seconds.

A negative value generates a Couchbase exception, `InvalidArgumentException`. | -When necessary you can adjust any or all of those configurable values — see: [Example 5](#example-5-configuring-replication-retries) for how to do this. +When necessary you can adjust any or all of those configurable values - see: [Example 5](#example-5-configuring-replication-retries) for how to do this. #### Example 5. Configuring Replication Retries @@ -462,7 +462,7 @@ await replicator.start(); Custom headers can be set on the configuration object. The replicator will then include those headers in every request. -This feature is useful in passing additional credentials, perhaps when an authentication or authorization step is being done by a proxy server (between Couchbase Lite and Sync Gateway) — see [Example 10](#example-10-setting-custom-headers). +This feature is useful in passing additional credentials, perhaps when an authentication or authorization step is being done by a proxy server (between Couchbase Lite and Sync Gateway) - see [Example 10](#example-10-setting-custom-headers). #### Example 10. Setting custom headers @@ -506,6 +506,10 @@ await replicator.start(); 1. The callback should follow the semantics of a [pure function](https://en.wikipedia.org/wiki/Pure_function). Otherwise, long running functions would slow down the replicator considerably. +:::note Version 1.1 +Android replication filters now support JavaScript arrow functions in the V8 evaluation path. +::: + ##### Pull Filter The pull filter gives an app the ability to validate documents being pulled, and skip ones that fail. This is an important security mechanism in a peer-to-peer topology with peers that are not fully trusted. @@ -585,7 +589,7 @@ Users may lose access to channels in a number of ways: * User is removed from a role * A channel is removed from a role the user is assigned to -By default, when a user loses access to a channel, the next Couchbase Lite Pull replication auto-purges all documents in the channel from local Couchbase Lite databases (on devices belonging to the user) unless they belong to any of the user’s other channels — see: [Table 2](#table-2-behavior-following-access-revocation). +By default, when a user loses access to a channel, the next Couchbase Lite Pull replication auto-purges all documents in the channel from local Couchbase Lite databases (on devices belonging to the user) unless they belong to any of the user’s other channels - see: [Table 2](#table-2-behavior-following-access-revocation). Documents that exist in multiple channels belonging to the user (even if they are not actively replicating that channel) are not auto-purged unless the user loses access to all channels. @@ -604,7 +608,7 @@ Users will be receive an `AccessRemoved` notification from the DocumentListener | | Sync Function includes `requireAccess(revokedChannel)` | Local changes continue to be pushed to remote but are rejected by Sync Gateway | -If a user subsequently regains access to a lost channel, then any previously auto-purged documents still assigned to any of their channels are automatically pulled down by the active Sync Gateway when they are next updated — see behavior summary in [Table 3](#table-3-behavior-if-access-is-regained). +If a user subsequently regains access to a lost channel, then any previously auto-purged documents still assigned to any of their channels are automatically pulled down by the active Sync Gateway when they are next updated - see behavior summary in [Table 3](#table-3-behavior-if-access-is-regained). #### Table 3. Behavior if access is regained @@ -639,7 +643,7 @@ This is an [Enterprise Edition](https://www.couchbase.com/products/editions/) fe With Delta Sync, only the changed parts of a Couchbase document are replicated. This can result in significant savings in bandwidth consumption as well as throughput improvements, especially when network bandwidth is typically constrained. -Replications to a Server (for example, a Sync Gateway, or passive listener) automatically use delta sync if the property is enabled at database level by the server — see: [databases.$db.delta_sync.enabled](https://docs.couchbase.com/sync-gateway/current/configuration-properties-legacy.html#databases-foo_db-delta_sync). +Replications to a Server (for example, a Sync Gateway, or passive listener) automatically use delta sync if the property is enabled at database level by the server - see: [databases.$db.delta_sync.enabled](https://docs.couchbase.com/sync-gateway/current/configuration-properties-legacy.html#databases-foo_db-delta_sync). Intra-Device replications automatically disable delta sync, whilst Peer-to-Peer replications automatically enable delta sync. @@ -669,7 +673,7 @@ Replicators use `checkpoints` to keep track of documents sent to the target data Without `checkpoints`, Couchbase Lite would replicate the entire database content to the target database on each connection, even though previous replications may already have replicated some or all of that content. -This functionality is generally not a concern to application developers. However, if you do want to force the replication to start again from zero, use the `checkpoint` reset argument when starting the replicator — as shown in Example 13. +This functionality is generally not a concern to application developers. However, if you do want to force the replication to start again from zero, use the `checkpoint` reset argument when starting the replicator - as shown in Example 13. #### Example 13. Resetting checkpoints @@ -691,7 +695,7 @@ The default `false` is shown here for completeness only; it is unlikely you woul You can monitor a replication’s status by using a combination of `Change Listeners` and the `Replicator.getStatus()` property. This enables you to know, for example, when the replication is actively transferring data and when it has stopped. -You can also choose to monitor document changes — see: [Monitor Document Changes](#monitor-document-changes). +You can also choose to monitor document changes - see: [Monitor Document Changes](#monitor-document-changes). ### Change Listeners @@ -701,28 +705,28 @@ Use this to monitor changes and to inform on sync progress; this is an optional Don’t forget to save the token so you can remove the listener later ::: -Use the `Replicator` class to add a change listener as a callback to the Replicator (`addChangeListener()`) — see: [Example 14](#example-14-monitor-replication). You will then be asynchronously notified of state changes. +Use the `Replicator` class to add a change listener as a callback to the Replicator (`addChangeListener()`) - see: [Example 14](#example-14-monitor-replication). You will then be asynchronously notified of state changes. You can remove a change listener with `removeChangeListener(token)`. ### Replicator Status -You can use the `Replicator.getStatus()` property to check the replicator status. That is, whether it is actively transferring data or if it has stopped — see: [Example 14](#example-14-monitor-replication). +You can use the `Replicator.getStatus()` property to check the replicator status. That is, whether it is actively transferring data or if it has stopped - see: [Example 14](#example-14-monitor-replication). The returned *ReplicationStatus* structure comprises: -* `ActivityLevel` — stopped, offline, connecting, idle or busy — see states described in: [Table 5](#table-5-replicator-activity-levels) +* `ActivityLevel` - stopped, offline, connecting, idle or busy - see states described in: [Table 5](#table-5-replicator-activity-levels) * `Progress` - * completed — the total number of changes completed - * total — the total number of changes to be processed + * completed - the total number of changes completed + * total - the total number of changes to be processed * `Error` - the current error, if any. #### Example 14. Monitor replication ```typescript -import { ListenerToken } from 'cbl-reactnative'; +import { ListenerToken } from '@couchbase/couchbase-lite-react-native'; const token: ListenerToken = await replicator.addChangeListener((change) => { const status = change.status; @@ -760,7 +764,7 @@ interface ReplicatorStatusChange { #### Example 14b. Advanced Replication Status Monitoring ```typescript -import { ListenerToken, ReplicatorActivityLevel } from 'cbl-reactnative'; +import { ListenerToken, ReplicatorActivityLevel } from '@couchbase/couchbase-lite-react-native'; const token: ListenerToken = await replicator.addChangeListener((change) => { const status = change.status; @@ -804,7 +808,7 @@ await token.remove(); The following diagram describes the status changes when the application starts a replication, and when the application is being backgrounded or foregrounded by the OS. It applies to iOS only. -![Replicator States](../../static/img/replicator-states.png) +![Replicator States](/img/replicator-states.png) Additionally, on iOS, an app already in the background may be terminated. In this case, the `Database` and `Replicator` instances will be `null` when the app returns to the foreground. Therefore, as preventive measure, it is recommended to do a `null` check when the app enters the foreground, and to re-initialize the database and replicator if any of those is `null`. @@ -847,7 +851,7 @@ For example, the code snippet in [Example 15](#example-15-register-a-document-li #### Example 15. Register a document listener ```typescript -import { ListenerToken } from 'cbl-reactnative'; +import { ListenerToken } from '@couchbase/couchbase-lite-react-native'; const token: ListenerToken = await replicator.addDocumentChangeListener((replication) => { const direction = replication.isPush ? "Push" : "Pull"; @@ -964,12 +968,12 @@ By default, the WebSocket protocol uses compression to optimize for speed and ba ### Logs -As always, when there is a problem with replication, logging is your friend. You can increase the log output for activity related to replication with Sync Gateway — see [Example 21](#example-21-set-logging-verbosity). +As always, when there is a problem with replication, logging is your friend. You can increase the log output for activity related to replication with Sync Gateway - see [Example 21](#example-21-set-logging-verbosity). #### Example 21. Set logging verbosity ```typescript -import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; +import { LogSinks, LogLevel, LogDomain } from '@couchbase/couchbase-lite-react-native'; // Verbose / Replicator and Network await LogSinks.setConsole({ @@ -986,7 +990,7 @@ For more on troubleshooting with logs, see: [Using Logs](../Troubleshooting/usin ### Authentication Errors -If Sync Gateway is configured with a self signed certificate but your app points to a `ws` scheme instead of `wss` you will encounter an error with status code 11006 — see: [Example 22](#example-22-protocol-mismatch) +If Sync Gateway is configured with a self signed certificate but your app points to a `ws` scheme instead of `wss` you will encounter an error with status code 11006 - see: [Example 22](#example-22-protocol-mismatch) #### Example 22. Protocol Mismatch @@ -994,7 +998,7 @@ If Sync Gateway is configured with a self signed certificate but your app points CouchbaseLite Replicator ERROR: {Repl#2} Got LiteCore error: WebSocket error 1006 "connection closed abnormally" ``` -If Sync Gateway is configured with a self signed certificate, and your app points to a `wss` scheme but the replicator configuration isn’t using the certificate you will encounter an error with status code `5011` — see: [Example 23](#example-23-certificate-mismatch-or-not-found) +If Sync Gateway is configured with a self signed certificate, and your app points to a `wss` scheme but the replicator configuration isn’t using the certificate you will encounter an error with status code `5011` - see: [Example 23](#example-23-certificate-mismatch-or-not-found) #### Example 23. Certificate Mismatch or Not Found diff --git a/docs/Guides/Migration/v1.1.md b/docs/Guides/Migration/v1.1.md new file mode 100644 index 0000000..e034353 --- /dev/null +++ b/docs/Guides/Migration/v1.1.md @@ -0,0 +1,97 @@ +--- +id: migration-guide-v1-1 +sidebar_position: 2 +--- + +# Version 1.1 + +> Description - _Quick reference for customer-facing changes in @couchbase/couchbase-lite-react-native version 1.1_ +> Related Content - [Release Notes](../../ProductNotes/release-notes.md) | [Using Logs](../../Troubleshooting/using-logs.md) + +## At a Glance + +Version 1.1 is focused on React Native New Architecture support and better troubleshooting: + +1. **TurboModules** - iOS and Android modules now support the React Native New Architecture. +2. **Improved file logging** - React Native wrapper diagnostics can be written to file and custom log sinks. +3. **LogSinks.write()** - application code can write messages into configured Couchbase Lite log sinks. +4. **Reliability fixes** - listener cleanup, document expiration, and conflict-aware deletes are improved. +5. **Official npm package** - published as `@couchbase/couchbase-lite-react-native`, replacing the legacy `cbl-reactnative` package. + +## NPM Package Rename + +Version 1.1 is published on npm as [`@couchbase/couchbase-lite-react-native`](https://www.npmjs.com/package/@couchbase/couchbase-lite-react-native). The legacy unscoped package `cbl-reactnative` (1.0.1 and earlier) is superseded. New installs and upgrades should use the scoped package. + +| What to update | Before (`cbl-reactnative`) | After (`@couchbase/couchbase-lite-react-native`) | +|---|---|---| +| Install | `npm install cbl-reactnative` | `npm install @couchbase/couchbase-lite-react-native` | +| TypeScript imports | `from 'cbl-reactnative'` | `from '@couchbase/couchbase-lite-react-native'` | +| Android Gradle path | `node_modules/cbl-reactnative/android/build.gradle` | `node_modules/@couchbase/couchbase-lite-react-native/android/build.gradle` | +| Expo plugin podspec path | `node_modules/cbl-reactnative/cbl-reactnative.podspec` | `node_modules/@couchbase/couchbase-lite-react-native/cbl-reactnative.podspec` | + +**Upgrade checklist:** + +1. Remove `cbl-reactnative` from `package.json` +2. Install `@couchbase/couchbase-lite-react-native` +3. Update all import paths and native wiring paths +4. Rebuild native projects (`pod install`, Gradle clean build) + +## TurboModule Support + +Version 1.1 supports [React Native New Architecture](https://reactnative.dev/architecture/overview) through TurboModules. Existing application-level Couchbase Lite APIs remain largely compatible, but New Architecture should be enabled to use the TurboModule implementation. + +For Expo development builds, enable New Architecture in `app.json`: + +```json +{ + "expo": { + "newArchEnabled": true + } +} +``` + +For React Native Android projects, make sure `newArchEnabled=true` is set in the Android project configuration. + +## Logging Changes + +### React Native Logs in File and Custom Sinks + +When a file or custom log sink is enabled, version 1.1 can forward React Native wrapper diagnostics into the same logging pipeline as native Couchbase Lite logs. + +React Native-originated lines are prefixed with `RN`: + +```text +RN ::DEBUG:: database_Open +RN ::WARNING:: query_RemoveChangeListener rejected: no listener for token +RN ::ERROR:: collection_Save failed +``` + +This helps diagnose issues that cross the JavaScript/native boundary while keeping sensitive payloads out of forwarded logs. + +### Writing Application Logs + +Use `LogSinks.write()` to write application messages into configured Couchbase Lite sinks: + +```typescript +import { LogSinks, LogLevel, LogDomain } from '@couchbase/couchbase-lite-react-native'; + +await LogSinks.write( + LogLevel.WARNING, + LogDomain.DATABASE, + 'Retrying database open after transient failure' +); +``` + +## Smaller Behavior Improvements + +- Passing `null` to `setDocumentExpiration()` clears a document expiration. +- Delete operations using `ConcurrencyControl.FAIL_ON_CONFLICT` reject stale revisions more consistently. +- Query, collection, and replicator listener routing is more consistent on New Architecture builds. +- Android replication filters support JavaScript arrow functions. +- `FileSystem.getFilesInDirectory(path)` can list generated log files. + +## Repository Links + +- [@couchbase/couchbase-lite-react-native on npm](https://www.npmjs.com/package/@couchbase/couchbase-lite-react-native) +- [couchbase/couchbase-lite-react-native](https://github.com/couchbase/couchbase-lite-react-native) +- [couchbase/couchbase-lite-js-common](https://github.com/couchbase/couchbase-lite-js-common) diff --git a/docs/Guides/Migration/v1.md b/docs/Guides/Migration/v1.md index 62f8970..f39d523 100644 --- a/docs/Guides/Migration/v1.md +++ b/docs/Guides/Migration/v1.md @@ -5,8 +5,8 @@ sidebar_position: 1 # Version 1.0 -> Description — _Quick reference for all API changes in cbl-reactnative version 1.0_ -> Related Content — [Release Notes](../../ProductNotes/release-notes.md) | [Using Logs](../../Troubleshooting/using-logs.md) +> Description - _Quick reference for all API changes in @couchbase/couchbase-lite-react-native version 1.0_ +> Related Content - [Release Notes](../../ProductNotes/release-notes.md) | [Using Logs](../../Troubleshooting/using-logs.md) :::important AT A GLANCE Version 1.0 introduces several API changes: @@ -54,7 +54,7 @@ await Database.setLogLevel(LogDomain.DATABASE, LogLevel.INFO); **NEW (Required in 1.0):** ```typescript -import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; +import { LogSinks, LogLevel, LogDomain } from '@couchbase/couchbase-lite-react-native'; await LogSinks.setConsole({ level: LogLevel.VERBOSE, @@ -85,7 +85,7 @@ const replicator = await Replicator.create(config); **NEW (Recommended in 1.0):** ```typescript -import { CollectionConfiguration } from 'cbl-reactnative'; +import { CollectionConfiguration } from '@couchbase/couchbase-lite-react-native'; const collectionConfig = new CollectionConfiguration(collection); const config = new ReplicatorConfiguration([collectionConfig], endpoint); diff --git a/docs/ProductNotes/compatibility.md b/docs/ProductNotes/compatibility.md index 1f8668a..23e8b25 100644 --- a/docs/ProductNotes/compatibility.md +++ b/docs/ProductNotes/compatibility.md @@ -9,7 +9,11 @@ sidebar_position: 2 Supported iOS and Android versions are dependent on React Native. See the [React Native Documentation](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) documentation for more information. ::: -The cbl-reactnative library is build against the native SDK for iOS and Android. The current version of the native SDK is 3.2.0. To see the compatibility notes for the native SDK, see the following documentation: +The @couchbase/couchbase-lite-react-native library is built against Couchbase Lite Enterprise for iOS and Android. Version 1.1 uses Couchbase Lite Android Enterprise 3.3.3 and Couchbase Lite Swift Enterprise 3.3.3. + +Version 1.1 supports [React Native New Architecture](https://reactnative.dev/architecture/overview) through TurboModules. Apps should use React Native 0.76.3 or higher and enable New Architecture for the TurboModule path. + +To see the compatibility notes for the native SDK, see the following documentation: - [Couchbase Mobile Compatibility Guide - iOS](https://docs.couchbase.com/couchbase-lite/current/swift/supported-os.html). diff --git a/docs/ProductNotes/release-notes.md b/docs/ProductNotes/release-notes.md index b0514ca..0ab1b65 100644 --- a/docs/ProductNotes/release-notes.md +++ b/docs/ProductNotes/release-notes.md @@ -5,6 +5,38 @@ sidebar_position: 1 # Release Notes +**1.1.0** (June 2026) + +New Features: +- Official React Native New Architecture / TurboModule support for iOS and Android native modules. +- Improved file logging: React Native wrapper diagnostics can now be forwarded into configured file and custom log sinks. +- React Native-originated log lines are prefixed with `RN ::LEVEL::` so they are easy to distinguish from native Couchbase Lite log output. +- `LogSinks.write()` API for writing app-authored messages into the Couchbase Lite logging pipeline. +- `FileSystem.getFilesInDirectory(path)` API for listing files in a directory, useful when discovering generated log files. + +Improvements and Fixes: +- Updated Couchbase Lite Android and iOS Enterprise SDKs to 3.3.3. +- Improved listener reliability for collection, query, and replicator events on the New Architecture event path. +- Replicator event payloads now include error fields where applicable. +- Document expiration handling now supports clearing expiration with `null` and uses stricter UTC ISO-8601 parsing. +- Delete operations using `ConcurrencyControl.FAIL_ON_CONFLICT` now honor revision IDs more consistently. +- Android replicator filters can use JavaScript arrow functions in the V8 evaluation path. + +Repository Updates: +- Official npm package: [@couchbase/couchbase-lite-react-native](https://www.npmjs.com/package/@couchbase/couchbase-lite-react-native) +- The legacy `cbl-reactnative` package is superseded; new installs and upgrades should use the scoped package +- Main React Native repository: [couchbase/couchbase-lite-react-native](https://github.com/couchbase/couchbase-lite-react-native) +- Shared JavaScript library repository: [couchbase/couchbase-lite-js-common](https://github.com/couchbase/couchbase-lite-js-common) + +Migration from 1.0.x: +- Existing application-level APIs remain largely compatible. +- Enable React Native New Architecture to use the TurboModule implementation. +- Review logging setup if you want React Native wrapper logs included in file or custom log sinks. + +See [Migration Guide](../Guides/Migration/v1.1.md) for detailed instructions. + +--- + **1.0.0** (December 2025) New Features: @@ -46,14 +78,14 @@ See [Migration Guide](../Guides/Migration/v1.md) for detailed instructions. --- **0.6.3** -- Array handling and improve blob data validation in DataAdapter [null-pointer issue](https://github.com/Couchbase-Ecosystem/cbl-reactnative/pull/73) +- Array handling and improve blob data validation in DataAdapter [null-pointer issue](https://github.com/couchbase/couchbase-lite-react-native/pull/73) - Fix a crash caused by improper handling of encryption key **0.6.1** -- Implemented [Collection Change Listeners](https://github.com/Couchbase-Ecosystem/cbl-reactnative/pull/54) on Android -- Implemented [Query Change Listeners](https://github.com/Couchbase-Ecosystem/cbl-reactnative/pull/55) on Android +- Implemented [Collection Change Listeners](https://github.com/couchbase/couchbase-lite-react-native/pull/54) on Android +- Implemented [Query Change Listeners](https://github.com/couchbase/couchbase-lite-react-native/pull/55) on Android - Fixed data adapter issues and improved testing -- Fixed [issue](https://github.com/Couchbase-Ecosystem/cbl-reactnative/issues/38) related to collection `getDocument` always pulling blob content +- Fixed [issue](https://github.com/couchbase/couchbase-lite-react-native/issues/38) related to collection `getDocument` always pulling blob content **0.5.0** - Implemented Collection Document Change diff --git a/docs/Queries/live-queries.md b/docs/Queries/live-queries.md index 6f2059f..e06c88e 100644 --- a/docs/Queries/live-queries.md +++ b/docs/Queries/live-queries.md @@ -10,7 +10,7 @@ sidebar_position: 6 ## Activating a Live Query -A live query is a query that, once activated, remains active and monitors the database for changes; refreshing the result set whenever a change occurs. As such, it is a great way to build reactive user interfaces — especially table/list views — that keep themselves up to date. +A live query is a query that, once activated, remains active and monitors the database for changes; refreshing the result set whenever a change occurs. As such, it is a great way to build reactive user interfaces - especially table/list views - that keep themselves up to date. **So, a simple use case may be:** A replicator running and pulling new data from a server, whilst a live-query-driven UI automatically updates to show the data without the user having to manually refresh. This helps your app feel quick and responsive. @@ -23,7 +23,7 @@ Each time you start watching a live query, the query is executed and an initial #### Example 1. Starting a Live Query - Change Listener ```typescript -import { ListenerToken } from 'cbl-reactnative'; +import { ListenerToken } from '@couchbase/couchbase-lite-react-native'; // Register a change listener const token: ListenerToken = await query.addChangeListener((change) => { @@ -44,6 +44,10 @@ const token: ListenerToken = await query.addChangeListener((change) => { Change listeners now return a `ListenerToken` object with a `remove()` method for cleanup. ::: +:::note Version 1.1 +Live query listeners use the TurboModule typed event path on New Architecture builds. This improves listener routing and cleanup consistency across iOS and Android. +::: + #### Example 2. Stopping a Live Query - Change Listener ```typescript @@ -78,7 +82,7 @@ interface QueryChange { #### Example 3. Complete Live Query with Error Handling ```typescript -import { ListenerToken } from 'cbl-reactnative'; +import { ListenerToken } from '@couchbase/couchbase-lite-react-native'; const query = database.createQuery( 'SELECT META().id, name, email FROM _default.users WHERE isActive = true' @@ -104,7 +108,7 @@ await token.remove(); ```typescript import { useEffect, useState } from 'react'; -import { ListenerToken } from 'cbl-reactnative'; +import { ListenerToken } from '@couchbase/couchbase-lite-react-native'; function ActiveUsersScreen({ database }) { const [users, setUsers] = useState([]); diff --git a/docs/Queries/query-troubleshooeting.md b/docs/Queries/query-troubleshooeting.md index 4162092..d88244a 100644 --- a/docs/Queries/query-troubleshooeting.md +++ b/docs/Queries/query-troubleshooeting.md @@ -42,7 +42,7 @@ cblite .cblite2 ### Output -The output from explain() remains the same whether invoked by an app, or cblite — see [Example 3](#example-3-queryexplain-output) for an example of how it looks. +The output from explain() remains the same whether invoked by an app, or cblite - see [Example 3](#example-3-queryexplain-output) for an example of how it looks. #### Example 3. Query.explain() Output @@ -66,7 +66,7 @@ This output ([Example 3](#example-3-queryexplain-output)) comprises three main e ### Format -The query plan section of the output displays a tabular form of the translated query's execution plan. It primarily shows how the data will be retrieved and, where appropriate, how it will be sorted for navigation and-or presentation purposes. For more on SQLite's Explain Query Plan — see: https://www.sqlite.org/eqp.html. +The query plan section of the output displays a tabular form of the translated query's execution plan. It primarily shows how the data will be retrieved and, where appropriate, how it will be sorted for navigation and-or presentation purposes. For more on SQLite's Explain Query Plan - see: https://www.sqlite.org/eqp.html. #### Example 4. A Query Plan @@ -76,22 +76,22 @@ The query plan section of the output displays a tabular form of the translated q 52|0|0| USE TEMP B-TREE FOR ORDER BY ``` -1. **Retrieval method** — This line shows the retrieval method being used for +1. **Retrieval method** - This line shows the retrieval method being used for the query; here a sequential read of the database. Something you may well be - looking to optimize — see [Retrieval Method](#retrieval-method) for more. + looking to optimize - see [Retrieval Method](#retrieval-method) for more. 2. **Grouping method** --- This line shows that the `GROUP BY` clause used in the query requires the data to be sorted and that a b-tree will be used for - temporary storage — see [Order and Group](#order-and-group). -3. **Ordering method** — This line shows that the `ORDER BY` clause used in the + temporary storage - see [Order and Group](#order-and-group). +3. **Ordering method** - This line shows that the `ORDER BY` clause used in the query requires the data to be sorted and that a b-tree will be used for - temporary storage — see [Order and Group](#order-and-group). + temporary storage - see [Order and Group](#order-and-group). ### Retrieval Method {#retrieval-method} The query optimizer will attempt to retrieve the requested data items as efficiently as possible, which generally will be by using one or more of the available indexes. The retrieval method shows the approach decided upon by the -optimizer — see [Table 1](#). +optimizer - see [Table 1](#). #### Table 1. Retrieval Methods @@ -104,7 +104,7 @@ optimizer — see [Table 1](#). When looking to optimize a query's retrieval method, consider whether: - Providing an additional index makes sense. -- You could use an existing index — perhaps by restructuring the query to +- You could use an existing index - perhaps by restructuring the query to minimize wildcard use, or the reliance on functions that modify the query's interpretation of index keys (for example, 'lower'). - You could reduce the data set being requested to minimize the query's @@ -164,14 +164,14 @@ after certain events, such as: - On a database close - When running a database compact. -So, if your analysis of the [Query Explain output](#example-3) indicates a +So, if your analysis of the [Query Explain output](#example-3-queryexplain-output) indicates a sub-optimal query and your rewrites fail to sufficiently optimize it, consider compacting the database. Then re-generate the Query Explain and note any improvements in optimization. They may not, in themselves, resolve the issue entirely; but they can provide a uesful guide toward further optimizing changes you could make. -## Wildcard and Like-based Queries +## Wildcard and Like-based Queries {#wildcard-queries} Like-based searches can use the index(es) only if: @@ -188,7 +188,7 @@ query plan decides on a retrieval method of `SCAN TABLE`. :::tip -For more on indexes — see: [Indexing](../indexes.md). +For more on indexes - see: [Indexing](../indexes.md). ::: @@ -323,5 +323,5 @@ for your next batch and-so-on. Optimize document size in design. Smaller documents load more quickly. Break your data into logical linked units. -Consider Using Full Text Search instead of complex `LIKE` or `REGEX` patterns — +Consider Using Full Text Search instead of complex `LIKE` or `REGEX` patterns - see [Full Text Search](../full-text-search.md). \ No newline at end of file diff --git a/docs/Queries/sqlplusplus-mobile-and-server-differences.md b/docs/Queries/sqlplusplus-mobile-and-server-differences.md index b708a9b..4fa7d59 100644 --- a/docs/Queries/sqlplusplus-mobile-and-server-differences.md +++ b/docs/Queries/sqlplusplus-mobile-and-server-differences.md @@ -62,7 +62,7 @@ SQL++ for Mobile's boolean logic rules are based on SQLite's, so: - Numbers `0` or `0.0` are `FALSE` - Arrays and dictionaries are `FALSE` - String and Blob are `TRUE` if the values are casted as a non-zero or `FALSE` - if the values are casted as `0` or `0.0` — see: + if the values are casted as `0` or `0.0` - see: [SQLITE's CAST and Boolean expressions](https://sqlite.org/lang_expr.html) for more details. - `NULL` is `FALSE` @@ -77,7 +77,7 @@ Logical operations with the `MISSING` value could result in `TRUE` or `FALSE` if the result can be determined regardless of the missing value, otherwise the result will be `MISSING`. -In SQL++ for Mobile — unlike SQL++ for Server — `NULL` is implicitly converted +In SQL++ for Mobile - unlike SQL++ for Server - `NULL` is implicitly converted to `FALSE` before evaluating logical operations. [Table 2](#table-2-logical-operations-comparison) summarizes the result of logical operations with different operand values and also shows where the Couchbase Server behavior differs. diff --git a/docs/Queries/sqlplusplus.md b/docs/Queries/sqlplusplus.md index 61e0ec8..90d6542 100644 --- a/docs/Queries/sqlplusplus.md +++ b/docs/Queries/sqlplusplus.md @@ -14,7 +14,7 @@ N1QL is Couchbase's implementation of the developing SQL++ standard. As such the ## Introduction -Developers using Couchbase Lite for React Native can provide SQL++ query strings using the SQL++ Query API. This API uses query statements of the form shown in [Example 1](#example-1-running-a-sql-query). The structure and semantics of the query format are based on that of Couchbase Server's SQL++ query language — see [SQL++ Reference Guide](https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/index.html) and [SQL++ Data Model](https://docs.couchbase.com/server/current/learn/data/n1ql-versus-sql.html). +Developers using Couchbase Lite for React Native can provide SQL++ query strings using the SQL++ Query API. This API uses query statements of the form shown in [Example 1](#example-1-running-a-sql-query). The structure and semantics of the query format are based on that of Couchbase Server's SQL++ query language - see [SQL++ Reference Guide](https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/index.html) and [SQL++ Data Model](https://docs.couchbase.com/server/current/learn/data/n1ql-versus-sql.html). ## Running @@ -93,9 +93,9 @@ When using the `*` expression, the column name is one of: * The alias name, if one was specified. * The data source name(or its alias if provided) as specified in the [FROM clause](https://cbl-dart.dev/queries/sqlplusplus-mobile/#from-clause). -This behavior is inline with that of SQL++ for Server — see example in [Table 1](#table-1-example-column-names-for-select). +This behavior is inline with that of SQL++ for Server - see example in [Table 1](#table-1-example-column-names-for-select). -#### Table 1. Example Column Names for SELECT * +#### Table 1. Example Column Names for SELECT * {#table-1-example-column-names-for-select} | Query | Column Name | |-------------------------------|---------------------| @@ -160,7 +160,7 @@ SELECT user.name FROM _default._default user; ### Purpose -The `JOIN` clause enables you to select data from multiple data sources linked by criteria specified in the ON constraint. Currently only self-joins are supported. For example to combine airline details with route details, linked by the airline id — see Example 7. +The `JOIN` clause enables you to select data from multiple data sources linked by criteria specified in the ON constraint. Currently only self-joins are supported. For example to combine airline details with route details, linked by the airline id - see Example 7. ### Syntax @@ -256,7 +256,7 @@ having = HAVING _ expression 1. The `GROUP BY` clause starts with the `GROUP BY` keyword followed by one or more expressions. 2. The `GROUP BY` clause is normally used together with aggregate functions (e.g. `COUNT`, `MAX`, `MIN`, `SUM`, `AVG`). -3. The `HAVING` clause allows you to filter the results based on aggregate functions — for example, `HAVING COUNT(airlineId) > 100`. +3. The `HAVING` clause allows you to filter the results based on aggregate functions - for example, `HAVING COUNT(airlineId) > 100`. ### Example @@ -1341,11 +1341,11 @@ for the features supported by SQL++ for Mobile but not by `QueryBuilder`. You can provide runtime parameters to your SQL++ query to make it more flexible. To specify substitutable parameters within your query string prefix the name -with `$` — see: [Example 51](#example-51-running-a-sql-query). +with `$` - see: [Example 51](#example-51-running-a-sql-query). #### Example 51. Running a SQL++ Query -You can provide runtime parameters to your SQL++ query to make it more flexible. To specify substitutable parameters within your query string prefix the name with $ — see: [Example 51](#example-51-running-a-sql-query). +You can provide runtime parameters to your SQL++ query to make it more flexible. To specify substitutable parameters within your query string prefix the name with $ - see: [Example 51](#example-51-running-a-sql-query). ```typescript const query = await database.createQuery( diff --git a/docs/StartHere/build-run.md b/docs/StartHere/build-run.md index b8eadc2..c5a2bb2 100644 --- a/docs/StartHere/build-run.md +++ b/docs/StartHere/build-run.md @@ -25,10 +25,11 @@ import { BasicAuthenticator, Replicator, ReplicatorActivityLevel, + CollectionConfiguration, ReplicatorConfiguration, ReplicatorType, URLEndpoint -} from 'cbl-reactnative'; +} from '@couchbase/couchbase-lite-react-native'; async function runDbSample() : Promise { @@ -77,8 +78,8 @@ async function runDbSample() : Promise { //running app services, replace enpoint with proper url and creditentials const target = new URLEndpoint('ws://localhost:4984/projects'); const auth = new BasicAuthenticator('demo@example.com', 'P@ssw0rd12'); - const config = new ReplicatorConfiguration(target); - config.addCollection(this.collection); + const collectionConfig = new CollectionConfiguration(collection); + const config = new ReplicatorConfiguration([collectionConfig], target); config.setAuthenticator(auth); const replicator = await Replicator.create(config); @@ -102,7 +103,7 @@ async function runDbSample() : Promise { //remember you must clean up the replicator when done with it by //doing the following lines - //await replicator.removeChangeListener(token); + //await token.remove(); //await replicator.stop(); } catch (e) { diff --git a/docs/StartHere/example-apps.md b/docs/StartHere/example-apps.md index e2730a9..fa8322a 100644 --- a/docs/StartHere/example-apps.md +++ b/docs/StartHere/example-apps.md @@ -9,7 +9,7 @@ Several example apps are available for developers to review. These apps are des ## React Native Module Example App -The cbl-reactnative module has an example app in the repositories [expo-example](https://github.com/Couchbase-Ecosystem/cbl-reactnative/tree/main/expo-example) folder. This example app is designed to show off all the various APIs and is used to test the Native Module during the development process. +The @couchbase/couchbase-lite-react-native module has an example app in the repository's [expo-example](https://github.com/couchbase/couchbase-lite-react-native/tree/main/expo-example) folder. This example app is designed to show off all the various APIs and is used to test the Native Module during the development process. ## Expo Travel Sample App diff --git a/docs/StartHere/install.md b/docs/StartHere/install.md index 27e9281..5490071 100644 --- a/docs/StartHere/install.md +++ b/docs/StartHere/install.md @@ -5,27 +5,35 @@ sidebar_position: 2 # Install :::note -This Native Module is currently under active development. If you find problems, please open an issue on the [GitHub repo](https://github.com/Couchbase-Ecosystem/cbl-reactnative/issues). +This Native Module is currently under active development. If you find problems, please open an issue on the [GitHub repo](https://github.com/couchbase/couchbase-lite-react-native/issues). ::: :::note -The version of this Native Module is based on supporting Couchbase Lite Enterprise for iOS and Android. This Native Module is not compatible with Couchbase Lite Community Edition. A [license](https://www.couchbase.com/pricing/) is required to use Couchbase Lite Enterprise edition. +The version of this Native Module is based on supporting Couchbase Lite Enterprise for iOS and Android. A [license](https://www.couchbase.com/pricing/) is required to use Couchbase Lite Enterprise edition. ::: ## Get Started The setup for using the Couchbase Lite React Native Native Module is a bit more involved than a typical React Native project. This is because the Native Module is a wrapper around the Couchbase Lite SDKs for iOS and Android. The Couchbase Lite SDKs are written in Swift and Kotlin, respectively, and are not directly compatible with JavaScript. The Native Module provides a bridge between the two languages. +Version 1.1 supports [React Native New Architecture](https://reactnative.dev/architecture/overview) through TurboModules. In React Native and Expo development builds, enable New Architecture when you want to use the TurboModule implementation. + The installation for the Native Module is provided in two sections: one section for standard React Native apps and one for Expo based apps. ### React Native Based Apps -To use the Couchbase Lite React Native Native Module in a standard React Native app, you will need to install the npm package. From the root of your applications project directory (the directory containing your `package.json` file), run the following command: +To use the Couchbase Lite React Native Native Module in a standard React Native app, you will need to install the npm package [@couchbase/couchbase-lite-react-native](https://www.npmjs.com/package/@couchbase/couchbase-lite-react-native). From the root of your applications project directory (the directory containing your `package.json` file), run one of the following commands: ```bash -npm install cbl-reactnative +npm install @couchbase/couchbase-lite-react-native +# or +yarn add @couchbase/couchbase-lite-react-native ``` +:::note +New projects should install the scoped package above. Upgrading from the legacy `cbl-reactnative` package (1.0.1 and earlier)? See the [Version 1.1 Migration Guide](../Guides/Migration/v1.1.md#npm-package-rename). +::: + Once installed, you will want to build each native project (iOS and Android) to link the native module to your project. #### iOS @@ -40,17 +48,19 @@ cd .. #### Android -In the current beta release, the Android Gradle file needs to be updated manually to include the native module. This can be done by editing the build.gradle file in the android directory of your React native app and adding the following line below the apply plugin line for the com.facebook.react.rootproject: +For React Native New Architecture builds, make sure `newArchEnabled=true` is set in your Android project configuration. + +If your app needs manual Gradle wiring, update the Android Gradle file to include the native module. This can be done by editing the build.gradle file in the android directory of your React native app and adding the following line below the apply plugin line for the com.facebook.react.rootproject: ```kotlin -apply from: "../node_modules/cbl-reactnative/android/build.gradle" +apply from: "../node_modules/@couchbase/couchbase-lite-react-native/android/build.gradle" ``` when completed the last two lines of the file should look something like this: ```kotlin apply plugin: "com.facebook.react.rootproject" -apply from: "../node_modules/cbl-reactnative/android/build.gradle" +apply from: "../node_modules/@couchbase/couchbase-lite-react-native/android/build.gradle" ``` Now you can install the gradle dependencies and build the Android project: @@ -81,7 +91,7 @@ npm run start For developers using Expo, you must make sure you have the dev-client installed in your app. Expo Go is not compatible with custom React Native Native Module. To install the dev-client, review the [Expo documentation](https://docs.expo.dev/develop/development-builds/introduction/#what-is-expo-dev-client) along with the [Local App develompent documentation](https://docs.expo.dev/guides/local-app-development/). These directions assume dev-client is setup and you can build locally your app on iOS and Android. -The expo environment dynamically builds both the Cocoapod file for iOS and Gradle file for Android. Because of this you will need to register the cbl-reactnative package with the expo environment via the Expo plugin api. +The expo environment dynamically builds both the Cocoapod file for iOS and Gradle file for Android. Because of this you will need to register the @couchbase/couchbase-lite-react-native package with the expo environment via the Expo plugin api. This can be done by creating a new file in the root of your project called `plugin.config.js` and adding the following code: @@ -94,7 +104,7 @@ const { // Function to modify Android build.gradle function modifyAndroidBuildGradle(config) { - const lineToAdd = ` apply from: "../node_modules/cbl-reactnative/android/build.gradle"`; + const lineToAdd = ` apply from: "../node_modules/@couchbase/couchbase-lite-react-native/android/build.gradle"`; if (!config.modResults.contents.includes(lineToAdd)) { config.modResults.contents += `\n${lineToAdd}`; console.debug(config.modResults.contents); @@ -113,7 +123,7 @@ function modifyXcodeProject(config) { // Function to modify Podfile properties to include the native module podspec function includeNativeModulePod(config) { return withPodfileProperties(config, async (podConfig) => { - const podspecPath = `../node_modules/cbl-reactnative/cbl-reactnative.podspec`; + const podspecPath = `../node_modules/@couchbase/couchbase-lite-react-native/cbl-reactnative.podspec`; if (podConfig.modResults.podfileProperties !== undefined && podConfig.modResults.podfileProperties.pod !== undefined) { podConfig.modResults.podfileProperties.pod( `'cbl-reactnative', :path => '${podspecPath}'` @@ -154,6 +164,16 @@ When completed, the `expo` section of your app.json file should look something l You can now build your expo app locally on iOS and Android. The native module will be included in the build. +For Expo development builds using version 1.1, enable New Architecture in `app.json`: + +```json +{ + "expo": { + "newArchEnabled": true + } +} +``` + iOS: ```bash npx expo run:ios diff --git a/docs/StartHere/prerequisties.md b/docs/StartHere/prerequisties.md index 30a7e08..ff79a2a 100644 --- a/docs/StartHere/prerequisties.md +++ b/docs/StartHere/prerequisties.md @@ -5,24 +5,26 @@ sidebar_position: 1 # Prerequisites -Couchbase Lite for React Native is provided as a [Native Module](https://reactnative.dev/docs/legacy/native-modules-intro). +Couchbase Lite for React Native is provided as a [Native Module](https://reactnative.dev/docs/legacy/native-modules-intro). Version 1.1 supports [React Native New Architecture](https://reactnative.dev/architecture/overview) through TurboModules on iOS and Android. -The Native Module can be found at the following repository [Couchbase Lite for React Native](https://github.com/Couchbase-Ecosystem/cbl-reactnative). This plugin is actively developed and maintained by the community. It is not an official Couchbase product. +The Native Module can be found at the following repository: [Couchbase Lite for React Native](https://github.com/couchbase/couchbase-lite-react-native). npm package: [@couchbase/couchbase-lite-react-native](https://www.npmjs.com/package/@couchbase/couchbase-lite-react-native). Shared TypeScript and JavaScript code lives in [cblite-js](https://github.com/couchbase/couchbase-lite-js-common). A developer using this plugin should have a basic understanding of the following technologies: - [React Native](https://reactnative.dev/) - [React Native - Native Modules](https://reactnative.dev/docs/legacy/native-modules-intro) +- [React Native - New Architecture](https://reactnative.dev/architecture/overview) - [Expo Framework](https://docs.expo.dev/) - [Couchbase Lite](https://docs.couchbase.com/couchbase-lite/current/index.html) ## Expo Note -React Native's recommmendation is to use [Expo](https://reactnative.dev/blog/2024/06/25/use-a-framework-to-build-react-native-apps) for development . The example app that comes with the cbl-reactnative repo is an Expo based app, thus this Native Module can work in Expo apps. Note using Expo Go is not supported due to Expo Go not supporting loading 3rd party Native Modules. You will need to be familiar with the [Expo Dev Client](https://docs.expo.dev/guides/local-app-development/#local-builds-with-expo-dev-client) process to use this Native Module in an Expo app. +React Native's recommmendation is to use [Expo](https://reactnative.dev/blog/2024/06/25/use-a-framework-to-build-react-native-apps) for development . The example app that comes with the repository is an Expo based app, thus this Native Module can work in Expo apps. Note using Expo Go is not supported due to Expo Go not supporting loading 3rd party Native Modules. You will need to be familiar with the [Expo Dev Client](https://docs.expo.dev/guides/local-app-development/#local-builds-with-expo-dev-client) process to use this Native Module in an Expo app. ## Supported Platforms - The React Native - Native Module is supported on iOS and Android platforms. MacOS, Windows, and Web support is not available at this time. ## React Native Version - The plugin is built using React Native 0.76.3. Support for older versions of React Native is not guaranteed and apps should be based on 0.76.3 or higher. +- Version 1.1 supports TurboModules through [React Native New Architecture](https://reactnative.dev/architecture/overview). Apps should enable New Architecture when using the TurboModule implementation. Please review the React Native [Support documentation](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) for a full listed of supported platform versions. @@ -31,12 +33,12 @@ Please review the React Native [Support documentation](https://github.com/reactw - [Node >= 20](https://formulae.brew.sh/formula/node@18) - React Native - [React Native Docs](https://reactnative.dev/) - - [Understanding React Native - Native Modules ](https://reactnative.dev/docs/native-modules-intro) + - [Understanding React Native - Native Modules ](https://reactnative.dev/docs/legacy/native-modules-intro) - Expo (if you choose to use Expo, not required but recommended) - [Expo Docs](https://docs.expo.dev/) - [Expo Dev Client](https://docs.expo.dev/guides/local-app-development/#local-builds-with-expo-dev-client) - [Expo Mono Repos](https://docs.expo.dev/guides/monorepos/) - - [Expo Plugin and mods](https://docs.expo.dev/config-plugins/plugins-and-mods/) + - [Expo Plugin and mods](https://docs.expo.dev/config-plugins/introduction/) - IDEs - [Visual Studio Code](https://code.visualstudio.com/download) - [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) diff --git a/docs/Troubleshooting/using-logs.md b/docs/Troubleshooting/using-logs.md index fdf5cd0..1814d92 100644 --- a/docs/Troubleshooting/using-logs.md +++ b/docs/Troubleshooting/using-logs.md @@ -5,8 +5,8 @@ sidebar_position: 1 # Using Logs for Troubleshooting -> Description — _Couchbase Lite on React Native — Using Logs for Troubleshooting_ -> Related Content — [Troubleshooting Queries](troubeshoot-queries.md) | [Troubleshooting Crashes](troubleshoot-crashes.md) +> Description - _Couchbase Lite on React Native - Using Logs for Troubleshooting_ +> Related Content - [Troubleshooting Queries](../Queries/query-troubleshooeting.md) | [Troubleshooting Crashes](troubleshoot-crashes.md) :::note * The retrieval of logs from the device is out of scope of this feature. @@ -14,7 +14,7 @@ sidebar_position: 1 ## Introduction -Couchbase Lite provides a robust Logging API — see: API References for Logging classes — which make debugging and troubleshooting easier during development and in production. It delivers flexibility in terms of how logs are generated and retained, whilst also maintaining the level of logging required by Couchbase Support for investigation of issues. +Couchbase Lite provides a robust Logging API - see: API References for Logging classes - which make debugging and troubleshooting easier during development and in production. It delivers flexibility in terms of how logs are generated and retained, whilst also maintaining the level of logging required by Couchbase Support for investigation of issues. Log output is split into the following streams: @@ -34,6 +34,8 @@ Log output is split into the following streams: Version 1.0 introduces the Log Sink API which provides three types of log sinks for flexible logging control. +Version 1.1 improves file logging by forwarding React Native wrapper diagnostics into configured file and custom log sinks. These wrapper-originated lines are prefixed with `RN ::LEVEL::`, for example `RN ::DEBUG:: database_Open`, so they can be distinguished from native Couchbase Lite SDK logs. + ### Log Levels | Level | Value | Description | @@ -63,7 +65,7 @@ Console based logging outputs logs to the system console (stdout/stderr), useful #### Example 1. Enable Console Logging ```typescript -import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; +import { LogSinks, LogLevel, LogDomain } from '@couchbase/couchbase-lite-react-native'; // Enable verbose logging for all domains await LogSinks.setConsole({ @@ -123,6 +125,30 @@ When a log file reaches `maxFileSize`, it's closed and a new one is created. Old await LogSinks.setFile(null); ``` +### React Native Wrapper Logs in File Logging + +When file logging is enabled, version 1.1 can include diagnostics from the React Native wrapper in the same log files as native Couchbase Lite logs. This helps troubleshoot issues that cross the JavaScript/native boundary, such as listener registration, database open/close calls, query execution, and replication operations. + +Wrapper log lines use the `RN` marker: + +```text +RN ::DEBUG:: database_Open +RN ::WARNING:: query_RemoveChangeListener rejected: no listener for token +RN ::ERROR:: collection_Save failed +``` + +The wrapper avoids forwarding sensitive payloads such as document bodies, blob contents, encryption keys, and raw filesystem paths. + +You can use `FileSystem.getFilesInDirectory(path)` to list generated log files in the log directory: + +```typescript +import { FileSystem } from '@couchbase/couchbase-lite-react-native'; + +const fileSystem = new FileSystem(); +const files = await fileSystem.getFilesInDirectory(logDirectory); +console.log('Log files:', files); +``` + ## Custom Log Sink Custom logging allows you to implement your own logging logic with a callback function. @@ -148,6 +174,22 @@ await LogSinks.setCustom({ await LogSinks.setCustom(null); ``` +## Writing App Logs to Couchbase Lite Sinks + +Version 1.1 adds `LogSinks.write()` for writing your own application log messages into the configured Couchbase Lite logging pipeline. These messages are delivered to enabled sinks such as file, console, and custom sinks. + +```typescript +import { LogSinks, LogLevel, LogDomain } from '@couchbase/couchbase-lite-react-native'; + +await LogSinks.write( + LogLevel.WARNING, + LogDomain.DATABASE, + 'Retrying database open after transient failure' +); +``` + +`LogSinks.write()` accepts concrete domains such as `DATABASE`, `QUERY`, `REPLICATOR`, `NETWORK`, and `LISTENER`. `LogDomain.ALL` is for sink configuration and is not accepted for a single written log line. + ## Using Multiple Log Sinks You can enable multiple log sinks simultaneously for different purposes. diff --git a/docs/blobs.md b/docs/blobs.md index da35749..bb12766 100644 --- a/docs/blobs.md +++ b/docs/blobs.md @@ -5,8 +5,8 @@ sidebar_position: 8 # Blobs -> Description — _Couchbase Lite database data model concepts - blobs_ -> Related Content — [Databases](databases.md) | [Documents](documents.md) | [Indexing](indexing.md) +> Description - _Couchbase Lite database data model concepts - blobs_ +> Related Content - [Databases](databases.md) | [Documents](documents.md) | [Indexing](indexes.md) ## Introduction @@ -14,15 +14,15 @@ Couchbase Lite for React Native uses blobs to store the contents of images, othe The blob itself is not stored in the document. It is held in a separate content-addressable store indexed from the document and retrieved only on-demand. -When a document is synchronized, the Couchbase Lite replicator adds an `_attachments` dictionary to the document's properties if it contains a blob — see [Figure 1](#figure-1-sample-blob-document). +When a document is synchronized, the Couchbase Lite replicator adds an `_attachments` dictionary to the document's properties if it contains a blob - see [Figure 1](#figure-1-sample-blob-document). ## Blob Objects -The blob as an object appears in a document as dictionary property — see, for example avatar in [Figure 1](#figure-1-sample-blob-document). +The blob as an object appears in a document as dictionary property - see, for example avatar in [Figure 1](#figure-1-sample-blob-document). Other properties include `length` (the length in bytes), and optionally `content_type` (typically, its MIME type). -The blob's data (an image, audio or video content) is not stored in the document, but in a separate content-addressable store, indexed by the `digest` property — see [Using Blobs](#using-blobs). +The blob's data (an image, audio or video content) is not stored in the document, but in a separate content-addressable store, indexed by the `digest` property - see [Using Blobs](#using-blobs). ### Constraints diff --git a/docs/database-prebuilt.md b/docs/database-prebuilt.md index d477ea8..cc35985 100644 --- a/docs/database-prebuilt.md +++ b/docs/database-prebuilt.md @@ -5,8 +5,8 @@ sidebar_position: 5 # Pre-built Database -> Description — _How to Handle Pre-Built Couchbase Lite Databases in Your App_ -> Abstract — _This content explains how to include a snapshot of a pre-built database in your package to shorten initial sync time and reduce bandwidth use._ +> Description - _How to Handle Pre-Built Couchbase Lite Databases in Your App_ +> Abstract - _This content explains how to include a snapshot of a pre-built database in your package to shorten initial sync time and reduce bandwidth use._ ## Overview @@ -50,7 +50,7 @@ Ensure the replication used to populate Couchbase Lite database uses the exact s Don’t, for instance, create a pre-built database against a staging Sync Gateway server and use it within a production app that syncs against a production Sync Gateway. ::: -You can use the cblite tool (cblite cp) for this — see: [cblite cp (export, import, push, pull)](https://github.com/couchbaselabs/couchbase-mobile-tools/blob/master/Documentation.md#cp-aka-export-import-push-pull) on GitHub. +You can use the cblite tool (cblite cp) for this - see: [cblite cp (export, import, push, pull)](https://github.com/couchbaselabs/couchbase-mobile-tools/blob/master/Documentation.md#cp-aka-export-import-push-pull) on GitHub. 3. Create the same indexes the app will use (wait for the replication to finish before doing this). @@ -87,7 +87,7 @@ To remove encryption on the copy, provide a null encryption-key. 2. Copy the pre-packaged database to the required location -Use the API's Database.copy method — see: [Example 1](#example-1-decompress-and-copy-database-using-api). This ensures that a unique UUID is generated for each copy. +Use the API's Database.copy method - see: [Example 1](#example-1-decompress-and-copy-database-using-api). This ensures that a unique UUID is generated for each copy. :::important Do not copy the database using any other method. diff --git a/docs/databases.md b/docs/databases.md index 7f85300..0e9a5d1 100644 --- a/docs/databases.md +++ b/docs/databases.md @@ -5,8 +5,8 @@ sidebar_position: 4 # Databases -> Description — _Working with Couchbase Lite Databases_ -> Related Content — [Blobs](blobs.md) | [Documents](documents.md) | [Indexing](indexing.md) +> Description - _Working with Couchbase Lite Databases_ +> Related Content - [Blobs](blobs.md) | [Documents](documents.md) | [Indexing](indexes.md) ## Database Concepts @@ -46,8 +46,8 @@ _Figure 2. Couchbase Lite Examples_ You may not need to sync all the data related to a particular application. You can set up a scope that syncs data, and a second scope that doesn’t. One reason for doing this is to store local configuration data (such as the preferred screen orientation or keyboard layout). Since this information only relates to a particular device, there is no need to sync it: -- **local data scope** — Contains information pertaining to the device. -- **syncing data scope** — Contains information pertaining to the user, which can be synced back to the cloud for use on the web or another device. +- **local data scope** - Contains information pertaining to the device. +- **syncing data scope** - Contains information pertaining to the user, which can be synced back to the cloud for use on the web or another device. ## Managing Couchbase Lite Databases in React Native @@ -57,7 +57,7 @@ In a React Native application using Couchbase Lite, begin by initializing the Re **Example: Initializing React Native Engine and Database Context** ```typescript -import { CblReactNativeEngine } from 'cbl-reactnative'; +import { CblReactNativeEngine } from '@couchbase/couchbase-lite-react-native'; const engine = new CblReactNativeEngine(); // Initialize once, early in your app ``` @@ -66,12 +66,12 @@ This configuration ensures seamless interaction between your React Native app an ### Create or Open a Database -To create or open a database, use the Database class from the cbl-reactnative package, specifying the database name and optionally, a DatabaseConfiguration for custom settings like the database directory or encryption. +To create or open a database, use the Database class from the @couchbase/couchbase-lite-react-native package, specifying the database name and optionally, a DatabaseConfiguration for custom settings like the database directory or encryption. **Example 1. Creating/Opening a Database** ```javascript -import { Database, DatabaseConfiguration } from 'cbl-reactnative'; //import the package +import { Database, DatabaseConfiguration } from '@couchbase/couchbase-lite-react-native'; //import the package ``` ```javascript @@ -179,7 +179,7 @@ You can download and build it from the couchbaselabs [GitHub repository](https:/ You should use console logs as your first source of diagnostic information. If the default logging level is insufficient, you can enable verbose logging scoped specifically to database operations. ```typescript -import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; +import { LogSinks, LogLevel, LogDomain } from '@couchbase/couchbase-lite-react-native'; try { await LogSinks.setConsole({ diff --git a/docs/documents.md b/docs/documents.md index 3cda1e5..40e8c46 100644 --- a/docs/documents.md +++ b/docs/documents.md @@ -5,8 +5,8 @@ sidebar_position: 7 # Documents -> Description — _Couchbase Lite Concepts - Data Model - Documents_ -> Related Content — [Databases](databases.md) | [Blobs](blobs.md) | [Indexing](indexing.md) +> Description - _Couchbase Lite Concepts - Data Model - Documents_ +> Related Content - [Databases](databases.md) | [Blobs](blobs.md) | [Indexing](indexes.md) ## Overview @@ -60,7 +60,7 @@ In addition to these basic data types Couchbase Lite provides for the following: ### JSON -Couchbase Lite also provides for the direct handling of JSON data implemented in most cases by the provision of a ToJSON() method on appropriate API classes (for example, on MutableDocument, Dictionary, Blob and Array) — see Working with JSON Data. +Couchbase Lite also provides for the direct handling of JSON data implemented in most cases by the provision of a ToJSON() method on appropriate API classes (for example, on MutableDocument, Dictionary, Blob and Array) - see Working with JSON Data. ## Constructing a Document @@ -97,7 +97,7 @@ First open your database. If the database does not already exist, Couchbase Lite ```typescript const myDatabase = new Database('myDatabaseName', config); ``` -See [Databases](https://cbl-reactnative.dev/databases) for more information. +See [Databases](databases.md) for more information. ### Create a Document @@ -362,7 +362,7 @@ Unlike collection listeners, document change listeners DO have a direct `databas #### Example 7. Monitor Specific Document ```typescript -import { ListenerToken } from 'cbl-reactnative'; +import { ListenerToken } from '@couchbase/couchbase-lite-react-native'; const token: ListenerToken = await collection.addDocumentChangeListener( 'user-123', @@ -442,6 +442,12 @@ const expirationDate = new Date('2024-12-31T23:59:59'); await collection.setDocumentExpiration('doc123', expirationDate); ``` +In version 1.1, passing `null` clears a document's expiration. Expiration values are parsed as strict UTC ISO-8601 dates when crossing the native bridge. + +```typescript +await collection.setDocumentExpiration('doc123', null); +``` + ## Purge a Document Documents can be purged from the local database using the `purge` method on the collection they are stored in. @@ -468,12 +474,14 @@ await collection.delete(doc); ``` Note that document deletion are replicated to Sync Gateway or Capella App Services. +When using `ConcurrencyControl.FAIL_ON_CONFLICT`, version 1.1 checks the document revision ID before deleting. A delete using a stale document revision rejects instead of deleting a newer revision. + ## Document Constraints Couchbase Lite APIs do not explicitly disallow the use of attributes with the underscore prefix at the top level of document. This is to facilitate the creation of documents for use either in local only mode where documents are not synced. :::note -`_id`, `_rev` and `_sequence` are reserved keywords and must not be used as top-level attributes — see [Example 13](#example-13-reserved-keys-list). +`_id`, `_rev` and `_sequence` are reserved keywords and must not be used as top-level attributes - see [Example 13](#example-13-reserved-keys-list). ::: #### Example 13. Reserved Keys List diff --git a/docs/full-text-search.md b/docs/full-text-search.md index d10e9ba..d341ab0 100644 --- a/docs/full-text-search.md +++ b/docs/full-text-search.md @@ -5,7 +5,7 @@ sidebar_position: 10 # Using Full Text Search -> Description - _Couchbase Lite database data querying concepts — full text search_ +> Description - _Couchbase Lite database data querying concepts - full text search_ > Related Content - [Indexing](indexes.md) ## Overview diff --git a/docs/indexes.md b/docs/indexes.md index aab3209..1d7330e 100644 --- a/docs/indexes.md +++ b/docs/indexes.md @@ -5,8 +5,8 @@ sidebar_position: 11 # Indexing -> Description — _Couchbase mobile database indexes and indexing concepts_ -> Related Content — [Databases](databases.md) | [Documents](documents.md) | [Indexing](indexes.md) +> Description - _Couchbase mobile database indexes and indexing concepts_ +> Related Content - [Databases](databases.md) | [Documents](documents.md) | [Indexing](indexes.md) ## Introduction diff --git a/docs/intro.md b/docs/intro.md index 37de9b7..e0f6a07 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -14,17 +14,17 @@ Couchbase Lite is an embedded, document-style NoSQL database that is syncable an Couchbase Lite for React Native is a Native Module implementation of Couchbase Lite for React Native using Typescript. It has feature parity with Couchbase Lite implementations for other platforms, with a few exceptions. -More information on React Native - Native Modules can be found here: [React Native Docs](https://reactnative.dev/docs/native-modules-intro) +Version 1.1 adds official React Native New Architecture support through TurboModules. + +More information on React Native - Native Modules can be found here: [React Native Docs](https://reactnative.dev/docs/legacy/native-modules-intro) :::note -This plugin only works with iOS and Android platforms. Web, Windows, and MacOS support is not available. +Couchbase Lite for React Native has officially graduated from a community project to a fully Enterprise-Supported offering ::: -The version of this Native Module is based on supporting Couchbase Lite Enterprise for iOS and Android. This Native Module is not compatible with Couchbase Lite Community Edition. A [license](https://www.couchbase.com/pricing/) is required to use Couchbase Lite Enterprise edition. +Install via npm: [@couchbase/couchbase-lite-react-native](https://www.npmjs.com/package/@couchbase/couchbase-lite-react-native) — see [Install](StartHere/install.md) for full setup. -:::note -Couchbase Lite for React Native is a community provided solution that is actively developed and maintained by the community. It is not an official Couchbase product. -::: +The version of this Native Module is based on supporting Couchbase Lite Enterprise for iOS and Android. A [license](https://www.couchbase.com/pricing/) is required to use Couchbase Lite Enterprise edition. ## Features * Offline First @@ -49,10 +49,25 @@ Couchbase Lite for React Native is a community provided solution that is activel - Replication * Encryption - Full Database +* React Native New Architecture + - TurboModule support for iOS and Android +* Logging + - Console, file, and custom log sinks + - React Native wrapper logs can be forwarded to file and custom sinks + +:::note +This plugin only works with iOS and Android platforms. Web, Windows, and MacOS support is not available. +::: + +## Upgrading? -## Upgrading from 0.6.x? +If you are upgrading from 1.0.x to 1.1, see the [Version 1.1 Migration Guide](Guides/Migration/v1.1.md). -See the [Migration Guide](Guides/Migration/v1.md) for detailed instructions on upgrading to version 1.0. +If you are upgrading from 0.6.x, see the [Version 1.0 Migration Guide](Guides/Migration/v1.md) for detailed instructions on upgrading to version 1.0. + +:::note +Migrating from 0.6.x directly to 1.1? Follow both guides in order — complete the [Version 1.0 Migration Guide](Guides/Migration/v1.md) first, then the [Version 1.1 Migration Guide](Guides/Migration/v1.1.md). +::: ## Limitations Some of the features supported by other platform implementations of Couchbase Lite are currently not supported: diff --git a/docs/scopes-collections.md b/docs/scopes-collections.md index e276d93..643d03a 100644 --- a/docs/scopes-collections.md +++ b/docs/scopes-collections.md @@ -5,7 +5,7 @@ sidebar_position: 6 # Scopes and Collections -> Description — _Scopes and collections allow you to organize your documents within a database._ +> Description - _Scopes and collections allow you to organize your documents within a database._ :::important AT A GLANCE **Use collections to organize your content in a database** @@ -157,7 +157,7 @@ There is NO direct `database` property. Access it via `change.collection.databas #### Example 6. Basic Collection Listener ```typescript -import { Collection, ListenerToken } from 'cbl-reactnative'; +import { Collection, ListenerToken } from '@couchbase/couchbase-lite-react-native'; const collection = await database.createCollection('users'); @@ -181,7 +181,7 @@ await token.remove(); ```typescript import { useEffect } from 'react'; -import { ListenerToken } from 'cbl-reactnative'; +import { ListenerToken } from '@couchbase/couchbase-lite-react-native'; function UserListScreen({ collection }) { useEffect(() => { diff --git a/docusaurus.config.js b/docusaurus.config.js index 95ab291..621b490 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -9,7 +9,7 @@ const config = { favicon: 'img/favicon.ico', // Set the production url of your site here - url: 'http://cbl-reactnative.dev', + url: 'https://cbl-reactnative.dev', // Set the // pathname under which your site is served // For GitHub pages deployment, it is often '//' @@ -17,7 +17,7 @@ const config = { // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. - organizationName: 'Couchbase-Ecosystem', // Usually your GitHub org/user name. + organizationName: 'couchbase', // Usually your GitHub org/user name. projectName: 'cbl-reactnative-docs', // Usually your repo name. deploymentBranch: 'gh-pages', trailingSlash: false, @@ -25,7 +25,8 @@ const config = { onBrokenLinks: 'warn', onBrokenMarkdownLinks: 'warn', - // OneTrust Cookies Consent Notice for couchbase.com + // OneTrust cookie consent (Couchbase script; see static/js/onetrust-wrapper.js + // for cbl-reactnative.dev persistence workaround). headTags: [ { tagName: 'script', @@ -33,15 +34,17 @@ const config = { src: 'https://cdn.cookielaw.org/scripttemplates/otSDKStub.js', type: 'text/javascript', charset: 'UTF-8', - 'data-domain-script': '748511ff-10bf-44bf-88b8-36382e5b5fd9', + 'data-domain-script': + process.env.ONETRUST_DOMAIN_SCRIPT || + '748511ff-10bf-44bf-88b8-36382e5b5fd9', }, }, { tagName: 'script', attributes: { + src: '/js/onetrust-wrapper.js', type: 'text/javascript', }, - innerHTML: 'function OptanonWrapper() {}', }, { tagName: 'script', @@ -66,15 +69,29 @@ const config = { docs: { sidebarPath: './sidebars.js', routeBasePath: '/', + lastVersion: 'current', + versions: { + current: { + label: 'v1.1', + path: '', + banner: 'none', + }, + '1.0': { + label: 'v1.0', + path: '1.0', + banner: 'none', + }, + }, // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: 'https://github.com/Couchbase-Ecosystem/cbl-reactnative-docs', + editUrl: 'https://github.com/couchbase/cbl-reactnative-docs/tree/main/', }, blog: { showReadingTime: true, // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: 'https://github.com/Couchbase-Ecosystem/cbl-reactnative-docs', }, + editUrl: 'https://github.com/couchbase/cbl-reactnative-docs/tree/main/', + }, theme: { customCss: './src/css/custom.css', }, @@ -103,7 +120,7 @@ const config = { }, image: 'img/couchbase-social-card.jpg', navbar: { - title: 'cbl-reactnative', + title: 'Couchbase Lite React Native', logo: { alt: 'Couchbase Logo', src: 'img/couchbase.svg', @@ -111,8 +128,7 @@ const config = { items: [ {to: 'blog', label: 'Blog', position: 'left'}, { - href: 'https://github.com/Couchbase-Ecosystem/cbl-reactnative', - label: 'GitHub', + type: 'docsVersionDropdown', position: 'right', }, ], @@ -131,7 +147,7 @@ const config = { items: [ { label: 'Overview', - to: 'docs/intro', + to: '/', }, ], }, @@ -169,7 +185,11 @@ const config = { }, { label: 'GitHub', - href: 'https://github.com/Couchbase-Ecosystem/cbl-reactnative', + href: 'https://github.com/couchbase/couchbase-lite-react-native', + }, + { + label: 'npm', + href: 'https://www.npmjs.com/package/@couchbase/couchbase-lite-react-native', }, ], }, diff --git a/package.json b/package.json index c82d398..f87ad7b 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,14 @@ "name": "cbl-reactnative", "version": "1.0.1", "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/couchbase/cbl-reactnative-docs.git" + }, + "homepage": "https://cbl-reactnative.dev/", + "bugs": { + "url": "https://github.com/couchbase/cbl-reactnative-docs/issues" + }, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", diff --git a/static/js/onetrust-wrapper.js b/static/js/onetrust-wrapper.js new file mode 100644 index 0000000..937cf0f --- /dev/null +++ b/static/js/onetrust-wrapper.js @@ -0,0 +1,70 @@ +/** + * OneTrust callback for cbl-reactnative.dev. + * + * The Couchbase OneTrust production script (748511ff-…) is registered for + * couchbase.com, so OneTrust cannot write OptanonAlertBoxClosed on this + * domain. Persist consent cookies on the current host when the SDK records + * a choice but fails to store them. + */ +function OptanonWrapper() { + if (typeof OneTrust === 'undefined') { + return; + } + + var host = window.location.hostname; + var isCouchbaseDomain = + host === 'couchbase.com' || host.endsWith('.couchbase.com'); + + function hasConsentGroups() { + return ( + typeof OptanonActiveGroups !== 'undefined' && + OptanonActiveGroups.replace(/,/g, '').length > 0 + ); + } + + function hasAlertCookie() { + return document.cookie.indexOf('OptanonAlertBoxClosed=') !== -1; + } + + function persistConsentCookies() { + if (isCouchbaseDomain || hasAlertCookie()) { + return; + } + + if (!hasConsentGroups() && OneTrust.IsAlertBoxClosed && OneTrust.IsAlertBoxClosed()) { + return; + } + + if (!hasConsentGroups()) { + return; + } + + var expiry = new Date(); + expiry.setFullYear(expiry.getFullYear() + 1); + var expiryStr = 'expires=' + expiry.toUTCString(); + var secure = window.location.protocol === 'https:' ? '; Secure' : ''; + + document.cookie = + 'OptanonAlertBoxClosed=' + + new Date().toISOString() + + '; path=/; ' + + expiryStr + + '; SameSite=Lax' + + secure; + + document.cookie = + 'OptanonConsent=' + + 'isGpcEnabled=0&datestamp=' + + encodeURIComponent(new Date().toString()) + + '&version=202504.1.0&groups=' + + encodeURIComponent(OptanonActiveGroups) + + '&hosts=&consentId=&interactionCount=1&landingPath=NotLandingPage&AwaitingReconsent=false' + + '; path=/; ' + + expiryStr + + '; SameSite=Lax' + + secure; + } + + OneTrust.OnConsentChanged(persistConsentCookies); + persistConsentCookies(); +} diff --git a/versioned_docs/version-1.0/DataSync/_category_.json b/versioned_docs/version-1.0/DataSync/_category_.json new file mode 100644 index 0000000..446fdfd --- /dev/null +++ b/versioned_docs/version-1.0/DataSync/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Data Sync", + "position": 12, + "link": { + "type": "generated-index", + "description": "Learn about how to Sync Data between your React Native app and Couchbase Capella App Services or Sync Gateway." + } +} diff --git a/versioned_docs/version-1.0/DataSync/remote-sync-gateway.md b/versioned_docs/version-1.0/DataSync/remote-sync-gateway.md new file mode 100644 index 0000000..716e007 --- /dev/null +++ b/versioned_docs/version-1.0/DataSync/remote-sync-gateway.md @@ -0,0 +1,1003 @@ +--- +id: remote-sync-gateway +sidebar_position: 2 +--- + +# Remote Sync + +> Description - _Couchbase Lite for Ionic - Synchronizing data changes between local and remote databases using Sync Gateway or Capella App Services_ + +:::note +All code examples are indicative only. They demonstrate the basic concepts and approaches to using a feature. Use them as inspiration and adapt these examples to best practice when developing applications for your platform. +::: + +## Introduction + +Couchbase Lite for React Native provides API support for secure, bi-directional, synchronization of data changes between mobile applications and a central server database. It does so by using a *replicator* to interact with Sync Gateway or Capella App Services. + +The *replicator* is designed to manage replication of documents and-or document changes between a source and a target database. For example, between a local Couchbase Lite database and Capella App Services endpoint, which is ultimately mapped to a bucket in a Couchbase Capella Couchbase cluster instance in the cloud. + +This page shows sample code and configuration examples covering the implementation of a replication using Sync Gateway or Capella App Services. + +Your application runs a replicator (also referred to here as a client), which will initiate connection with a Sync Gateway or Capella App Services endpoint (also referred to here as a server) and participate in the replication of database changes to bring both local and remote databases into sync. + +Subsequent sections provide additional details and examples for the main configuration options. + +## Couchbase Capella Free Tier + +Couchbase Capella offers a free tier that allows you to get started with Couchbase Capella and Couchbase Capella App Services. The free tier includes a single node Capella cluster. + +To get started with the free tier, see the documentation on [Couchbase Capella Free Tier](https://docs.couchbase.com/cloud/get-started/create-account.html). For new developers to the platform, Couchbase Capella free tier is the fastest way to get started trying out sync within your mobile application. + + +## Replication Concepts + +Couchbase Lite allows for one database for each application running on the mobile device. This database can contain one or more scopes. Each scope can contain one or more collections. + +To learn about Scopes and Collections, see [Databases](../databases.md). + +You can set up a replication scheme across these data levels: + +#### Database + + The `_default` collection is synced. + +#### Collection + + A specific collection or a set of collections is synced. + +As part of the syncing setup, the Gateway has to map the Couchbase Lite database to the database being synced on Capella. + +## Replication Protocol + +### Scheme + +Couchbase Mobile uses a replication protocol based on WebSockets for replication. To use this protocol the replication URL should specify WebSockets as the URL scheme (see the [Configure Target](#configure-target) section below). + +#### Incompatibilities + +Couchbase Lite’s replication protocol is incompatible with CouchDB-based databases. And since Couchbase Lite 2.x+ only supports the new protocol, you will need to run a version of Sync Gateway that supports it - see: [Compatibility](../ProductNotes/compatibility.md). + +### Ordering + +To optimize for speed, the replication protocol doesn’t guarantee that documents will be received in a particular order. So we don’t recommend to rely on that when using the replication or database change listeners for example. + +## Scopes and Collections + +Scopes and Collections allow you to organize your documents in Couchbase Lite. + +When syncing, you can configure the collections to be synced. + +The collections specified in the Couchbase Lite replicator setup must exist (both scope and collection name must be identical) on the Sync Gateway side, otherwise starting the Couchbase Lite replicator will result in an error. + +During replication: + + 1. If Sync Gateway config (or server) is updated to remove a collection that is being synced, the client replicator will be offline and will be stopped after the first retry. An error will be reported. + + 2. If Sync Gateway config is updated to add a collection to a scope that is being synchronized, the replication will ignore the collection. The added collection will not automatically sync until the Couchbase Lite replicator’s configuration is updated. + +### Default Collection + +When upgrading Couchbase Lite to >= 3.1, the existing documents in the database will be automatically migrated to the default collection. + +For backward compatibility with the code prior to >= 3.1, when you set up the replicator with the database, the default collection will be set up to sync with the default collection on the server. + +#### Sync Couchbase Lite database with the default collection on Sync Gateway + +
+ +![Cbl Replication Scopes Collections 1](/img/cbl-replication-scopes-collections-1.png) + +
+ +#### Sync Couchbase Lite default collection with default collection on Sync Gateway + +
+ +![Cbl Replication Scopes Collections 2](/img/cbl-replication-scopes-collections-2.png) + +
+ +### User-Defined Collections + +The user-defined collections specified in the Couchbase Lite replicator setup must exist (and be identical) on the Sync Gateway side to sync. + +#### Syncing scope with user-defined collections. + +
+ +![Cbl Replication Scopes Collections 3](/img/cbl-replication-scopes-collections-3.png) + +
+ +#### Syncing scope with user-defined collections. Couchbase Lite has more collections than the Sync Gateway configuration (with collection filters) + +
+ +![Cbl Replication Scopes Collections 4](/img/cbl-replication-scopes-collections-4.png) + +
+ +## Configuration Summary + +You should configure and initialize a replicator for each Couchbase Lite database instance you want to sync. [Example 1](#example-1-replication-configuration-and-initialization) shows the configuration and initialization process. + +:::note +You need Couchbase Lite 3.1+ and Sync Gateway 3.1+ or Capella App Services to use `custom` Scopes and Collections. +If you’re using a Sync Gateway release that is older than version 3.1, you won’t be able to access `custom` Scopes and Collections. To use Couchbase Lite 3.1+ with these older versions, you can use the `default` Collection as a backup option. +::: + +#### Example 1. Replication configuration and initialization + +```typescript +import { + ReplicatorConfiguration, + CollectionConfiguration, + URLEndpoint, + BasicAuthenticator, + Replicator, + ListenerToken +} from 'cbl-reactnative'; + +// Create endpoint and authenticator +const endpoint = new URLEndpoint('ws://localhost:4984/projects'); +const auth = new BasicAuthenticator('demo@example.com', 'P@ssw0rd12'); + +// NEW API: Create collection configuration +const collectionConfig = new CollectionConfiguration(collection); + +// Pass collection configurations in constructor +const config = new ReplicatorConfiguration( + [collectionConfig], // Collections passed during initialization + endpoint +); + +config.setAuthenticator(auth); + +// Create replicator +const replicator = await Replicator.create(config); + +// Listen to replicator change events +const token: ListenerToken = await replicator.addChangeListener((change) => { + // Check for errors + const error = change.status.getError(); + if (error) { + console.error('Replication error:', error); + } + + // Check activity level + if (change.status.getActivityLevel() === 3) { // IDLE + console.log('Replication is idle'); + } +}); + +// Start replication +await replicator.start(false); + +// Remember to clean up when done: +// await token.remove(); +// await replicator.stop(); +``` + +:::important Version 1.0 API Change +Collections are now passed during `ReplicatorConfiguration` construction using `CollectionConfiguration` objects. + +**NEW API (Recommended):** +```typescript +const collectionConfig = new CollectionConfiguration(collection); +const config = new ReplicatorConfiguration([collectionConfig], endpoint); +``` + +**OLD API (Deprecated):** +```typescript +const config = new ReplicatorConfiguration(endpoint); +config.addCollection(collection); // Deprecated +``` + +The `addCollection()` method is deprecated. It remains available for backward compatibility, but new applications should use the new constructor pattern. Existing applications are strongly encouraged to migrate. +::: + +#### Example 1b. Multiple Collections + +The new API allows each collection to have its own replication settings: + +```typescript +import { CollectionConfiguration } from 'cbl-reactnative'; + +// Configure users collection +const usersConfig = new CollectionConfiguration(usersCollection) + .setChannels(['public', 'users']); + +// Configure orders collection with different settings +const ordersConfig = new CollectionConfiguration(ordersCollection) + .setChannels(['orders', 'admin']) + .setDocumentIDs(['order-1', 'order-2']); // Only specific documents + +// Pass both configurations during initialization +const config = new ReplicatorConfiguration( + [usersConfig, ordersConfig], + endpoint +); + +config.setAuthenticator(auth); +const replicator = await Replicator.create(config); +``` + +## Configure + +##### In this section + +[Configure Target](#configure-target) | [Sync Mode](#sync-mode) | [Retry Configuration](#retry-configuration) | [User Authorization](#user-authorization) | [Server Authentication](#server-authentication) | [Client Authentication](#client-authentication) | [Monitor Document Changes](#monitor-document-changes) | [Custom Headers](#custom-headers) | [Checkpoint Starts](#checkpoint-starts) | [Channels](#channels) | [Auto-purge on Channel Access Revocation](#auto-purge-on-channel-access-revocation) | [Delta Sync](#delta-sync) + + +### Configure Target + +Use the Initialize and define the replication configuration with local and remote database locations using the ReplicatorConfiguration object. + +The constructor provides: + + * the name of the local database to be sync’d + * the server’s URL (including the port number and the name of the remote database to sync with) + + It is expected that the app will identify the IP address and URL and append the remote database name to the URL endpoint, producing for example: `wss://10.0.2.2:4984/travel-sample` + The URL scheme for web socket URLs uses `ws:` (non-TLS) or `wss:` (SSL/TLS) prefixes. + +#### Example 2. Add Target to Configuration + +```typescript +// Define the target URL for the replication endpoint +const targetURL = 'wss://10.1.1.12:4984/travel-sample'; + +// Initialize the URLEndpoint with the target URL +const targetEndpoint = new URLEndpoint(targetURL); + +// Create the ReplicatorConfiguration with the target endpoint +const config = new ReplicatorConfiguration(targetEndpoint); + +// Add the already initialised collection to the replicator configuration +config.addCollection(collectionName); +``` + +:::note +Note use of the scheme prefix (`wss://` to ensure TLS encryption - strongly recommended in production - or `ws://`) +::: + +### Sync Mode + +Here we define the direction and type of replication we want to initiate. + +We use `ReplicatorConfiguration` class’s `replicatorType` and `continuous` parameters, to tell the replicator: + + * The type (or direction) of the replication: `PUSH_AND_PULL`; `PULL`; `PUSH` + * The replication mode, that is either of: + * Continuous - remaining active indefinitely to replicate changed documents (`continuous=true`). + * Ad-hoc - a one-shot replication of changed documents (`continuous=false`) + +#### Example 4. Configure replicator type and mode + +```typescript +config.replicatorType = ReplicatorType.PUSH_AND_PULL; + +// Configure Sync Mode +config.continuous = true +``` + +:::tip +Unless there is a solid use-case not to, always initiate a single `PUSH_AND_PULL` replication rather than identical separate `PUSH` and `PULL` replications. + +This prevents the replications generating the same checkpoint `docID` resulting in multiple conflicts. +::: + +### Retry Configuration + +Couchbase Lite for React Native's replication retry logic assures a resilient connection. + +Couchbase Lite for Swift’s replication retry logic assures a resilient connection. + +In the event it detects a transient error, the replicator will attempt to reconnect, stopping only when the connection is re-established, or the number of retries exceeds the retry limit (9 times for a single-shot replication and unlimited for a continuous replication). + +On each retry the interval between attempts is increased exponentially (exponential backoff) up to the maximum wait time limit (5 minutes). + +The REST API provides configurable control over this replication retry logic using a set of configiurable properties - see: [Table 1](#table-1-replication-retry-configuration-properties). + +#### Table 1. Replication Retry Configuration Properties + +| Property | Use cases | Description | +|------------------------|-----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `heartbeat()` | Reduce to detect connection errors sooner

Align to load-balancer or proxy `keep-alive` interval - see Sync Gateway’s topic [Load Balancer - Keep Alive](https://docs.couchbase.com/sync-gateway/current/load-balancer.html#websocket-connection) | The interval (in seconds) between the heartbeat pulses.

Default: The replicator pings the Sync Gateway every 300 seconds. | +| `maxAttempts()` | Change this to limit or extend the number of retry attempts. | The maximum number of retry attempts.

Set to zero (0) to use default values.

Set to one (1) to prevent any retry attempt.

The retry attempt count is reset when the replicator is able to connect and replicate.

Default values are:
- Single-shot replication = 9
- Continuous replication = maximum integer value.

Negative values generate a Couchbase exception `InvalidArgumentException`. | +| `maxAttemptWaitTime()` | Change this to adjust the interval between retries. | The maximum interval between retry attempts.

While you can configure the maximum permitted wait time, the replicator’s exponential backoff algorithm calculates each individual interval which is not configurable.

Default value: 300 seconds (5 minutes).

Zero sets the maximum interval between retries to the default of 300 seconds.

300 sets the maximum interval between retries to the default of 300 seconds.

A negative value generates a Couchbase exception, `InvalidArgumentException`. | + +When necessary you can adjust any or all of those configurable values - see: [Example 5](#example-5-configuring-replication-retries) for how to do this. + +#### Example 5. Configuring Replication Retries + +```typescript +// Create the target endpoint +const target = new URLEndpoint('ws://foo.couchbase.com/db'); + +// Create the replicator configuration +const config = new ReplicatorConfiguration(target); + +// Add the collection to the replicator configuration +config.addCollection(collection); + +// Set the replicator type +config.setReplicatorType(ReplicatorType.PUSH_AND_PULL); + +// Set continuous replication +config.setContinuous(true); + +// Set heartbeat interval (in seconds) +config.setHeartbeat(150); + +// Set the maximum number of retry attempts +config.setMaxAttempts(20); + +// Set the maximum wait time between retry attempts (in seconds) +config.setMaxAttemptWaitTime(600); + +// Create the replicator with the configuration +const replicator = await Replicator.create(config); +``` + +1. Here we use `setHeartbeat()` to set the required interval (in seconds) between the heartbeat pulses +2. Here we use `setMaxAttempts()` to set the required number of retry attempts +3. Here we use `setMaxAttemptWaitTime()` to set the required interval between retry attempts. + +### User Authorization + +By default, Sync Gateway does not enable user authorization. This makes it easier to get up and running with synchronization. + +You can enable authorization in the sync gateway configuration file, as shown in [Example 6](#example-6-enable-authorization). + +#### Example 6. Enable Authorization + +```json +{ + "databases": { + "mydatabase": { + "users": { + "GUEST": {"disabled": true} + } + } + } +} +``` +To authorize with Sync Gateway, an associated user must first be created. Sync Gateway users can be created through the [`POST /{tkn-db}/_user`](https://docs.couchbase.com/sync-gateway/current/rest-api-admin.html#/user/post__db___user_) endpoint on the Admin REST API. + + +### Server Authentication + +Define the credentials your app (the client) is expecting to receive from the Sync Gateway (the server) in order to ensure it is prepared to continue with the sync. + +Note that the client cannot authenticate the server if TLS is turned off. When TLS is enabled (Sync Gateway’s default) the client *must* authenticate the server. If the server cannot provide acceptable credentials then the connection will fail. + +Use `ReplicatorConfiguration` property `acceptOnlySelfSignedServerCertificate` to tell the replicator how to verify server-supplied TLS server certificates. + + * If `acceptOnlySelfSignedServerCertificate` is `true` then any self-signed certificate is accepted. Certificates that are not self signed are rejected, no matter who signed them. + * If `acceptOnlySelfSignedServerCertificate` is `false` (default), the client validates the server’s certificates against the system CA certificates. The server must supply a chain of certificates whose root is signed by one of the certificates in the system CA bundle. + +#### Example 7. Set Server TLS security + +##### CA Cert + +Set the client to expect and accept only CA attested certificates. + +```typescript +// Configure Server Security -- only accept CA Certs +config.acceptOnlySelfSignedServerCertificate = false +``` + +This is the default. Only certificate chains with roots signed by a trusted CA are allowed. Self signed certificates are not allowed. + +##### Self Signed Cert + +Set the client to expect and accept only self-signed certificates + +```typescript +// Configure Server Security -- only accept self-signed certs +config.acceptOnlySelfSignedServerCertificate = true; +``` + +Set this to true to accept any self signed cert. Any certificates that are not self-signed are rejected. + +This all assumes that you have configured the Sync Gateway to provide the appropriate SSL certificates, and have included the appropriate certificate in your app bundle. + +### Client Authentication + +There are two ways to authenticate from a Couchbase Lite client: `Basic Authentication` or `Session Authentication`. + +#### Basic Authentication + +You can provide a user name and password to the basic authenticator class method. Under the hood, the replicator will send the credentials in the first request to retrieve a `SyncGatewaySession` cookie and use it for all subsequent requests during the replication. This is the recommended way of using basic authentication. [Example 8](#example-8-basic-authentication) shows how to initiate a one-shot replication as the user **username** with the password **password**. + +#### Example 8. Basic Authentication + +```typescript +const url = "ws://localhost:4984/mydatabase"; +const target = new URLEndpoint(url); +const auth = new BasicAuthenticator("john", "pass"); +const config = new ReplicatorConfiguration(target); + +// Assuming collection is already defined and initialized +config.addCollection(collectionName); +config.setAuthenticator(auth); + +const replicator = new Replicator(config); +await replicator.start(); +``` + +#### Session Authentication + +Session authentication is another way to authenticate with Sync Gateway. + +A user session must first be created through the [`POST /{tkn-db}/_session`](https://docs.couchbase.com/sync-gateway/current/rest-api.html#/session/post__db___session) endpoint on the Public REST API. + +The HTTP response contains a session ID which can then be used to authenticate as the user it was created for. + +See [Example 9](#example-9-session-authentication), which shows how to initiate a one-shot replication with the session ID returned from the `POST /{tkn-db}/_session` endpoint. + +#### Example 9. Session Authentication + +```typescript +const url = new URL("ws://localhost:4984/mydatabase"); +const target = new URLEndpoint(url); +const config = new ReplicatorConfiguration(target); +const sessionID = 'your-session-id'; +const cookieName = 'your-cookie-name'; + +// Assuming collection is already defined and initialized +config.addCollection(collectionName); + +// Here cookieName is optional, if not passed it will default to the default value +const sessionAuthenticator = new SessionAuthenticator(sessionID, cookieName); +config.setAuthenticator(sessionAuthenticator); + +let replicator = new Replicator(config); +await replicator.start(); +``` + +### Custom Headers + +Custom headers can be set on the configuration object. The replicator will then include those headers in every request. + +This feature is useful in passing additional credentials, perhaps when an authentication or authorization step is being done by a proxy server (between Couchbase Lite and Sync Gateway) - see [Example 10](#example-10-setting-custom-headers). + +#### Example 10. Setting custom headers + +```typescript +const config = new ReplicatorConfiguration(target); +config.addCollection(collection); + +// Setting headers +config.setHeaders({ "CustomHeaderName": "Value" }); +``` + +### Replication filters + +Replication Filters allow you to have quick control over the documents stored as the result of a push and/or pull replication. + +##### Push Filter + +The push filter allows an app to push a subset of a database to the server. This can be very useful. For instance, high-priority documents could be pushed first, or documents in a "draft" state could be skipped. + +```typescript +const targetUrl = new URLEndpoint('"ws://localhost:4984/mydatabase"'); + +const config = new ReplicatorConfiguration(targetUrl); +const colConfig = new CollectionConfig(null, null); + +colConfig.pushFilter((documents, flags) => { + // (1) + "show source"; // directive needed to persist function body as string + + if (document?.["type"] === "draft") { + return false; + } + return true; +}); + +config.addCollection(collection, colConfig); + +const replicator = await Replicator.create(config); +await replicator.start(); +``` + +1. The callback should follow the semantics of a [pure function](https://en.wikipedia.org/wiki/Pure_function). Otherwise, long running functions would slow down the replicator considerably. + +##### Pull Filter + +The pull filter gives an app the ability to validate documents being pulled, and skip ones that fail. This is an important security mechanism in a peer-to-peer topology with peers that are not fully trusted. + +:::note +Pull replication filters are not a substitute for [channels](https://docs.couchbase.com/sync-gateway/current/channels.html). Sync Gateway channels are designed to be scalable (documents are filtered on the server) whereas a pull replication filter is applied to a document once it has been downloaded. +::: + +```typescript +const targetUrl = new URLEndpoint('"ws://localhost:4984/mydatabase"'); + +const config = new ReplicatorConfiguration(targetUrl); +const colConfig = new CollectionConfig(null, null); + +colConfig.pullFilter((documents, flags) => { + // (1) + "show source"; // directive needed to persist function body as string + + if (flags.includes(ReplicatedDocumentFlag.DELETED)) { + return false; + } + return true; +}); + +config.addCollection(collection, colConfig); + +const replicator = await Replicator.create(config); +await replicator.start(); +``` + +1. The callback should follow the semantics of a [pure function](https://en.wikipedia.org/wiki/Pure_function). Otherwise, long running functions would slow down the replicator considerably. + +:::caution +For now, pull filters can handle up to 100 documents per replication. If you exceed this number, the replicator may freeze. This will be fixed in the next version. +::: + +:::info Losing access to a document via the Sync Function + +Losing access to a document (via the Sync Function) also triggers the pull replication filter. + +Filtering out such an event would retain the document locally. + +As a result, there would be a local copy of the document disjointed from the one that resides on Couchbase Server. + +Further updates to the document stored on Couchbase Server would not be received in pull replications and further local edits could be pushed but the updated versions will not be visible. +::: + +### Channels + +By default, Couchbase Lite gets all the channels to which the configured user account has access. + +This behavior is suitable for most apps that rely on `user authentication` and the `sync function` to specify which data to pull for each user. + +Optionally, it’s also possible to specify a string array of channel names on Couchbase Lite’s replicator configuration object. In this case, the replication from Sync Gateway will only pull documents tagged with those channels. + +### Auto-purge on Channel Access Revocation + +:::caution +This is a Breaking Change at 3.0 +::: + +#### New outcome + +By default, when a user loses access to a channel all documents in the channel (that do not also belong to any of the user’s other channels) are auto-purged from the local database (in devices belonging to the user). + +#### Prior outcome + +*Previously these documents remained in the local database* + +Prior to this release, CBL auto-purged only in the case when the user loses access to a document by removing the doc from all of the channels belong to the user. Now, in addition to 2.x auto purge, Couchbase Lite will also auto-purges the docs when the user loses access to the doc via channel access revocation. This feature is enabled by default, but an opt-out is available. + +#### Behaviour + +Users may lose access to channels in a number of ways: + + * User loses direct access to channel + * User is removed from a role + * A channel is removed from a role the user is assigned to + +By default, when a user loses access to a channel, the next Couchbase Lite Pull replication auto-purges all documents in the channel from local Couchbase Lite databases (on devices belonging to the user) unless they belong to any of the user’s other channels - see: [Table 2](#table-2-behavior-following-access-revocation). + +Documents that exist in multiple channels belonging to the user (even if they are not actively replicating that channel) are not auto-purged unless the user loses access to all channels. + +Users will be receive an `AccessRemoved` notification from the DocumentListener if they lose document access due to channel access revocation; this is sent regardless of the current auto-purge setting. + +#### Table 2. Behavior following access revocation + +| | System State | Impact on Sync | +|----------------------|------------------------------------------------------------------|-------------------------------------------------------------------| +| **Replication Type** | **Access Control on Sync Gateway** | **Expected behavior when** *enable_auto_purge=true* | +| **Pull only** | User revoked access to channel. | Previously synced documents are auto purged on local | +| | Sync Function includes `requireAccess(revokedChannel)` | | +| **Push only** | User revoked access to channel. | No impact of auto-purge | +| | Sync Function includes `requireAccess(revokedChannel)` | Documents get pushed but are rejected by Sync Gateway | +| **Push-pull** | User revoked access to channel | Previously synced documents are auto purged on Couchbase Lite. | +| | Sync Function includes `requireAccess(revokedChannel)` | Local changes continue to be pushed to remote but are rejected by Sync Gateway | + + +If a user subsequently regains access to a lost channel, then any previously auto-purged documents still assigned to any of their channels are automatically pulled down by the active Sync Gateway when they are next updated - see behavior summary in [Table 3](#table-3-behavior-if-access-is-regained). + +#### Table 3. Behavior if access is regained + +| | System State | Impact on Sync | +|----------------------|-----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Replication Type** | **Access Control on Sync Gateway** | **Expected behavior when enable_auto_purge=true** | +| **Pull only** | User REASSIGNED access to channel | Previously purged documents that are still in the channel are automatically pulled by Couchbase Lite when they are next updated | +| **Push only** | User REASSIGNED access to channel | Local changes previously rejected by Sync Gateway will not be automatically pushed to remote unless resetCheckpoint is involved on CBL. Document changes subsequent to the channel reassignment will be pushed up as usual. | +| | Sync Function includes `requireAccess(reassignedChannel)` No impact of auto-purge | | +| **Push-pull** | User REASSIGNED access to channel | Previously purged documents are automatically pulled by Couchbase Lite | +| | Sync Function includes `requireAccess(reassignedChannel)` | Local changes previously rejected by Sync Gateway will not be automatically pushed to remote unless resetCheckpoint is involved. Document changes subsequent to the channel reassignment will be pushed up as usual. | + + +#### Config + +Auto-purge behavior is controlled primarily by the ReplicationConfiguration option `enableAutoPurge`. Changing the state of this will impact only future replications; the replicator will not attempt to sync revisions that were auto purged on channel access removal. Clients wishing to sync previously removed documents must use the resetCheckpoint API to resync from the start. + +#### Example 11. Setting auto-purge + +```typescript +// set auto-purge behavior (here we override default) +config.enableAutoPurge = false; +``` + +Here we have opted to turn off the auto purge behavior. By default auto purge is enabled. + +### Delta Sync + +:::important +This is an [Enterprise Edition](https://www.couchbase.com/products/editions/) feature. +::: + +With Delta Sync, only the changed parts of a Couchbase document are replicated. This can result in significant savings in bandwidth consumption as well as throughput improvements, especially when network bandwidth is typically constrained. + +Replications to a Server (for example, a Sync Gateway, or passive listener) automatically use delta sync if the property is enabled at database level by the server - see: [databases.$db.delta_sync.enabled](https://docs.couchbase.com/sync-gateway/current/configuration-properties-legacy.html#databases-foo_db-delta_sync). + +Intra-Device replications automatically disable delta sync, whilst Peer-to-Peer replications automatically enable delta sync. + +## Initialize + +##### In this section + +[Start Replicator](#start-replicator) | [Checkpoint Starts](#checkpoint-starts) + +### Start Replicator + +Use the `Replicator.create()` method to initialize the replicator with the configuration you have defined. You can optionally add a change listener (see [Monitor](#monitor)) before starting the replicator using `start()`. + +#### Example 12. Initialize and run replicator + +```typescript +// Apply configuration settings to the replicator +const replicator = await Replicator.create(config); + +// start the replicator without making a new checkpoint +await replicator.start(false); +``` + +### Checkpoint Starts + +Replicators use `checkpoints` to keep track of documents sent to the target database. + +Without `checkpoints`, Couchbase Lite would replicate the entire database content to the target database on each connection, even though previous replications may already have replicated some or all of that content. + +This functionality is generally not a concern to application developers. However, if you do want to force the replication to start again from zero, use the `checkpoint` reset argument when starting the replicator - as shown in Example 13. + +#### Example 13. Resetting checkpoints + +```typescript +if (doResetCheckpointRequired) { + this.replicator.start(true); +} else { + this.replicator.start(false); +} +``` +The default `false` is shown here for completeness only; it is unlikely you would explicitly use it in practice. + + +## Monitor + +##### In this section + + [Change Listeners](#change-listeners) | [Replicator Status](#replicator-status) | [Monitor Document Changes](#monitor-document-changes) | [Documents Pending Push](#documents-pending-push) + +You can monitor a replication’s status by using a combination of `Change Listeners` and the `Replicator.getStatus()` property. This enables you to know, for example, when the replication is actively transferring data and when it has stopped. + +You can also choose to monitor document changes - see: [Monitor Document Changes](#monitor-document-changes). + +### Change Listeners + +Use this to monitor changes and to inform on sync progress; this is an optional step. You can add and a replicator change listener at any point; it will report changes from the point it is registered. + +:::tip +Don’t forget to save the token so you can remove the listener later +::: + +Use the `Replicator` class to add a change listener as a callback to the Replicator (`addChangeListener()`) - see: [Example 14](#example-14-monitor-replication). You will then be asynchronously notified of state changes. + +You can remove a change listener with `removeChangeListener(token)`. + +### Replicator Status + +You can use the `Replicator.getStatus()` property to check the replicator status. That is, whether it is actively transferring data or if it has stopped - see: [Example 14](#example-14-monitor-replication). + +The returned *ReplicationStatus* structure comprises: + +* `ActivityLevel` - stopped, offline, connecting, idle or busy - see states described in: [Table 5](#table-5-replicator-activity-levels) + +* `Progress` + * completed - the total number of changes completed + * total - the total number of changes to be processed + +* `Error` - the current error, if any. + +#### Example 14. Monitor replication + +```typescript +import { ListenerToken } from 'cbl-reactnative'; + +const token: ListenerToken = await replicator.addChangeListener((change) => { + const status = change.status; + const activityLevel = status.getActivityLevel(); + const progress = status.getProgress(); + + const levelNames = ['stopped', 'offline', 'connecting', 'idle', 'busy']; + console.log(`Status: ${levelNames[activityLevel]}`); + console.log(`Progress: ${progress.getCompleted()}/${progress.getTotal()}`); +}); + +// Remove listener when done +await token.remove(); +``` + +### Replicator Status Data Structure + +When replication status changes, your callback receives a `ReplicatorStatusChange` object: + +```typescript +interface ReplicatorStatusChange { + status: ReplicatorStatus; +} +``` + +**ReplicatorStatus Methods:** +- `getActivityLevel()` - Returns 0-4 (see activity levels table below) +- `getProgress()` - Returns ReplicatorProgress object +- `getError()` - Returns error message string or undefined + +**ReplicatorProgress Methods:** +- `getCompleted()` - Returns number of changes completed +- `getTotal()` - Returns total number of changes + +#### Example 14b. Advanced Replication Status Monitoring + +```typescript +import { ListenerToken, ReplicatorActivityLevel } from 'cbl-reactnative'; + +const token: ListenerToken = await replicator.addChangeListener((change) => { + const status = change.status; + const level = status.getActivityLevel(); + const progress = status.getProgress(); + + console.log(`Activity: ${level}`); + console.log(`Progress: ${progress.getCompleted()}/${progress.getTotal()}`); + + if (status.getError()) { + console.error('Replication error:', status.getError()); + } + + // Check specific states + if (level === ReplicatorActivityLevel.IDLE) { + console.log('Sync complete'); + } else if (level === ReplicatorActivityLevel.BUSY) { + console.log('Syncing data...'); + } +}); + +// Remove when done +await token.remove(); +``` + +### Replication States + +[Table 5](#table-5-replicator-activity-levels) shows the different states, or activity levels, reported in the API; and the meaning of each. + +#### Table 5. Replicator activity levels + +| **State** | **Meaning** | +|----------------------|------------------------------------------------------------------| +| `STOPPED` | The replication is finished or hit a fatal error. | +| `OFFLINE` | The replicator is offline as the remote host is unreachable | +| `CONNECTING` | The replicator is connecting to the remote host | +| `IDLE` | The replication caught up with all the changes available from the server. The `IDLE` state is only used in continuous replications. | +| `BUSY` | The replication is actively transferring data. | + +### Replication Status and App Life Cycle + +The following diagram describes the status changes when the application starts a replication, and when the application is being backgrounded or foregrounded by the OS. It applies to iOS only. + +![Replicator States](/img/replicator-states.png) + +Additionally, on iOS, an app already in the background may be terminated. In this case, the `Database` and `Replicator` instances will be `null` when the app returns to the foreground. Therefore, as preventive measure, it is recommended to do a `null` check when the app enters the foreground, and to re-initialize the database and replicator if any of those is `null`. + +On other platforms, Couchbase Lite doesn’t react to OS backgrounding or foregrounding events and replication(s) will continue running as long as the remote system does not terminate the connection and the app does not terminate. It is generally recommended to stop replications before going into the background otherwise socket connections may be closed by the OS and this may interfere with the replication process. + +### Monitor Document Changes + +You can choose to register for document updates during a replication. + +#### When to Use Document Listeners + +- Track which specific documents are syncing +- Detect replication conflicts +- Handle document-level replication errors +- Monitor deleted documents during sync + +#### Document Replication Data Structure + +```typescript +interface DocumentReplicationRepresentation { + isPush: boolean; // true = push, false = pull + documents: ReplicatedDocument[]; +} + +interface ReplicatedDocument { + id: string; // Document ID + scopeName: string; // Scope name + collectionName: string; // Collection name + flags: string[]; // ['DELETED', 'ACCESS_REMOVED'] + error?: { message: string }; // Present if replication failed +} +``` + +**Document Flags:** +- `'DELETED'` - Document was deleted +- `'ACCESS_REMOVED'` - User lost access to document + +For example, the code snippet in [Example 15](#example-15-register-a-document-listener) registers a listener to monitor document replication performed by the replicator referenced by the variable `replicator`. It prints the document ID of each document received and sent. Stop the listener as shown in [Example 16](#example-16-stop-document-listener). + +#### Example 15. Register a document listener + +```typescript +import { ListenerToken } from 'cbl-reactnative'; + +const token: ListenerToken = await replicator.addDocumentChangeListener((replication) => { + const direction = replication.isPush ? "Push" : "Pull"; + console.log(`${direction}: ${replication.documents.length} documents`); + + for (const document of replication.documents) { + if (document.error === undefined) { + console.log(` Doc ID: ${document.id}`); + + if (document.flags.includes('DELETED')) { + console.log(" Successfully replicated a deleted document"); + } + } else { + console.error(` Error: ${document.error.message}`); + } + } +}); + +// Start the replicator +await replicator.start(false); +``` + +#### Example 16. Stop document listener + +```typescript +// Remove listener using new API +await token.remove(); +``` + +:::caution Deprecated +The `replicator.removeChangeListener(token)` method is deprecated. It remains available for backward compatibility, but new applications should use `token.remove()`. Existing applications are strongly encouraged to migrate. +::: + +### Document Access Removal Behavior + +When access to a document is removed on Sync Gateway (see: Sync Gateway’s [Sync Function](https://docs.couchbase.com/sync-gateway/current/sync-function-api.html)), the document replication listener sends a notification with the `AccessRemoved` flag set to `true` and subsequently purges the document from the database. + +## Documents Pending Push + +:::tip +`Replicator.isDocumentPending()` is quicker and more efficient. Use it in preference to returning a list of pending document IDs, where possible. +::: + +You can check whether documents are waiting to be pushed in any forthcoming sync by using either of the following API methods: + +* Use the `Replicator.pendingDocumentIds()` method, which returns a list of document IDs that have local changes, but which have not yet been pushed to the server. +* Use the `Replicator.isDocumentPending()` method to quickly check whether an individual document is pending a push. + +#### Example 17. Use Pending Document ID API + +```typescript +// Todo +``` + +## Stop + +Stopping a replication is straightforward. It is done using `stop()`. This initiates an asynchronous operation and so is not necessarily immediate. Your app should account for this potential delay before attempting any subsequent operations. + +You can find further information on database operations in [Databases](../databases.md). + +#### Example 18. Stop replicator + +```typescript +// Remove the change listener +await token.remove() + +// Stop the replicator +await replicator.stop() +``` + +Here we initiate the stopping of the replication using the `stop()` method. It will stop any active `change listener` once the replication is stopped. + +## Error Handling + +When *replicator* detects a network error it updates its status depending on the error type (permanent or temporary) and returns an appropriate HTTP error code. + +The following code snippet adds a `Change Listener`, which monitors a replication for errors and logs the the returned error code. + +#### Example 19. Monitoring for network errors + +```typescript +replicator.addChangeListener((change) => { + const error = change.status.getError(); + if (error) { + console.log(`Error code :: ${error.code}`); + } +}); +``` + +**For permanent network errors** (for example, `404` not found, or `401` unauthorized): *Replicator* will stop permanently, whether `setContinuous` is *true* or *false*. Of course, it sets its status to `STOPPED` + +**For recoverable or temporary errors:** *Replicator* sets its status to `OFFLINE`, then: + + * If `setContinuous=true` it retries the connection indefinitely + * If `setContinuous=false` (one-shot) it retries the connection a limited number of times. + +The following error codes are considered temporary by the Couchbase Lite replicator and thus will trigger a connection retry. + + * `408`: Request Timeout + * `429`: Too Many Requests + * `500`: Internal Server Error + * `502`: Bad Gateway + * `503`: Service Unavailable + * `504`: Gateway Timeout + * `1001`: DNS resolution error + +## Load Balancers + +Couchbase Lite uses WebSockets as the communication protocol to transmit data. Some load balancers are not configured for WebSocket connections by default (NGINX for example); so it might be necessary to explicitly enable them in the load balancer’s configuration (see [Load Balancers](https://docs.couchbase.com/sync-gateway/current/load-balancer.html)). + +By default, the WebSocket protocol uses compression to optimize for speed and bandwidth utilization. The level of compression is set on Sync Gateway and can be tuned in the configuration file ([replicator_compression](https://docs.couchbase.com/sync-gateway/current/configuration-properties-legacy.html#replicator_compression)). + +## Troubleshooting + +### Logs + +As always, when there is a problem with replication, logging is your friend. You can increase the log output for activity related to replication with Sync Gateway - see [Example 21](#example-21-set-logging-verbosity). + +#### Example 21. Set logging verbosity + +```typescript +import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; + +// Verbose / Replicator and Network +await LogSinks.setConsole({ + level: LogLevel.VERBOSE, + domains: [LogDomain.REPLICATOR, LogDomain.NETWORK] +}); +``` + +:::caution +Enable VERBOSE logging only during development or debugging. Avoid using it in production builds. +::: + +For more on troubleshooting with logs, see: [Using Logs](../Troubleshooting/using-logs.md). + +### Authentication Errors + +If Sync Gateway is configured with a self signed certificate but your app points to a `ws` scheme instead of `wss` you will encounter an error with status code 11006 - see: [Example 22](#example-22-protocol-mismatch) + +#### Example 22. Protocol Mismatch + +```console +CouchbaseLite Replicator ERROR: {Repl#2} Got LiteCore error: WebSocket error 1006 "connection closed abnormally" +``` + +If Sync Gateway is configured with a self signed certificate, and your app points to a `wss` scheme but the replicator configuration isn’t using the certificate you will encounter an error with status code `5011` - see: [Example 23](#example-23-certificate-mismatch-or-not-found) + +#### Example 23. Certificate Mismatch or Not Found + +```console +CouchbaseLite Replicator ERROR: {Repl#2} Got LiteCore error: Network error 11 "server TLS certificate is self-signed or has unknown root cert" +``` \ No newline at end of file diff --git a/versioned_docs/version-1.0/Guides/Migration/_category_.json b/versioned_docs/version-1.0/Guides/Migration/_category_.json new file mode 100644 index 0000000..a937a07 --- /dev/null +++ b/versioned_docs/version-1.0/Guides/Migration/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Migration", + "position": 1, + "collapsible": true, + "collapsed": false +} + diff --git a/versioned_docs/version-1.0/Guides/Migration/v1.md b/versioned_docs/version-1.0/Guides/Migration/v1.md new file mode 100644 index 0000000..a280703 --- /dev/null +++ b/versioned_docs/version-1.0/Guides/Migration/v1.md @@ -0,0 +1,259 @@ +--- +id: migration-guide-v1 +sidebar_position: 1 +--- + +# Version 1.0 + +> Description - _Quick reference for all API changes in cbl-reactnative version 1.0_ +> Related Content - [Release Notes](../../ProductNotes/release-notes.md) | [Using Logs](../../Troubleshooting/using-logs.md) + +:::important AT A GLANCE +Version 1.0 introduces several API changes: + +1. **Log Sink API** - Replaces `Database.setLogLevel()` with `LogSinks` API +2. **Listener Token API** - New `token.remove()` replaces `removeChangeListener()` +3. **New ReplicatorConfiguration** - Collections passed in constructor +::: + +## Breaking Changes + +### Listener Token Return Type (TypeScript) + +We changed all `addChangeListener` functions to return a `ListenerToken` instead of a `string`, enabling the new `token.remove()` API and aligning with other platforms. + +This is a breaking change **only for TypeScript code** that explicitly typed the token as `string`. JavaScript users and untyped TypeScript won't be affected. + +**TypeScript Migration Example:** + +```typescript +// BEFORE (TypeScript compilation error in 1.0) +const token: string = await collection.addChangeListener(listener); + +// AFTER +const token: ListenerToken = await collection.addChangeListener(listener); +token.remove(); +``` + +The existing `removeChangeListener(token)` methods remain available for backward compatibility but are deprecated. + +--- + +## Deprecated APIs + +### 1. Logging API + +The old `Database.setLogLevel()` API is deprecated and replaced with the new `LogSinks` API. + +**OLD (Deprecated):** +```typescript +// This API is deprecated +await db.setLogLevel(LogDomain.ALL, LogLevel.VERBOSE); +await Database.setLogLevel(LogDomain.DATABASE, LogLevel.INFO); +``` + +**NEW (Required in 1.0):** +```typescript +import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; + +await LogSinks.setConsole({ + level: LogLevel.VERBOSE, + domains: [LogDomain.ALL] +}); +``` + +:::caution Important +The old and new logging APIs cannot be used in tandem. If you are migrating to version 1.0, you must replace all instances of `Database.setLogLevel()` with `LogSinks.setConsole()`. +::: + +**Migration:** Search your code for `setLogLevel` and replace with `LogSinks.setConsole`. + +See: [Using Logs](../../Troubleshooting/using-logs.md) + +--- + +### 2. ReplicatorConfiguration API + +The old constructor pattern using `addCollection()` is deprecated. + +**OLD (Deprecated):** +```typescript +const config = new ReplicatorConfiguration(endpoint); +config.addCollection(collection); +const replicator = await Replicator.create(config); +``` + +**NEW (Recommended in 1.0):** +```typescript +import { CollectionConfiguration } from 'cbl-reactnative'; + +const collectionConfig = new CollectionConfiguration(collection); +const config = new ReplicatorConfiguration([collectionConfig], endpoint); +const replicator = await Replicator.create(config); +``` + +This method is deprecated. It remains available for backward compatibility, but new applications should use the new constructor pattern. Existing applications are strongly encouraged to migrate. + +See: [Remote Sync](../../DataSync/remote-sync-gateway.md#example-1-replication-configuration-and-initialization) + +--- + +### 3. Listener Removal Methods (Deprecated) + +**OLD (Deprecated in 1.0):** +```typescript +await collection.removeChangeListener(token); +await query.removeChangeListener(token); +await replicator.removeChangeListener(token); +``` + +**NEW (Recommended in 1.0):** +```typescript +await token.remove(); +``` + +This method is deprecated. It remains available for backward compatibility, but new applications should use `token.remove()`. Existing applications are strongly encouraged to migrate. + +--- + +## New Features in 1.0 + +### 1. Three Log Sink Types + +```typescript +// Console Sink - for development +await LogSinks.setConsole({ + level: LogLevel.VERBOSE, + domains: [LogDomain.ALL] +}); + +// File Sink - for production logging +await LogSinks.setFile({ + level: LogLevel.WARNING, + directory: logDir, + maxKeptFiles: 5, + maxFileSize: 1024 * 1024, + usePlaintext: true +}); + +// Custom Sink - for analytics +await LogSinks.setCustom({ + level: LogLevel.ERROR, + domains: [LogDomain.ALL], + callback: (level, domain, message) => { + Analytics.log({ level, domain, message }); + } +}); +``` + +See: [Using Logs](../../Troubleshooting/using-logs.md) + +--- + +### 2. All 5 Change Listener Types + +Now fully documented with examples: + +```typescript +// 1. Collection Change Listener +const token1 = await collection.addChangeListener((change) => { + console.log('Changed docs:', change.documentIDs); // Capital IDs! +}); + +// 2. Document Change Listener +const token2 = await collection.addDocumentChangeListener('doc-id', (change) => { + console.log('Doc changed:', change.documentId); // Lowercase Id! +}); + +// 3. Query Change Listener (Live Query) +const token3 = await query.addChangeListener((change) => { + console.log('Results:', change.results); +}); + +// 4. Replicator Status Listener +const token4 = await replicator.addChangeListener((change) => { + const status = change.status; + console.log('Level:', status.getActivityLevel()); + console.log('Progress:', status.getProgress().getCompleted()); +}); + +// 5. Replicator Document Listener +const token5 = await replicator.addDocumentChangeListener((change) => { + console.log('Direction:', change.isPush ? 'Push' : 'Pull'); +}); + +// All use same removal pattern +await token1.remove(); +``` + +See: [Scopes and Collections](../../scopes-collections.md#collection-change-listeners) | [Documents](../../documents.md#document-change-listeners) | [Live Queries](../../Queries/live-queries.md) | [Remote Sync](../../DataSync/remote-sync-gateway.md#change-listeners) + +--- + +### 3. Collection.fullName() + +```typescript +const fullName = await collection.fullName(); +console.log(fullName); // "production.users" +``` + +See: [Scopes and Collections](../../scopes-collections.md#get-collection-full-name) + +--- + +## Quick Migration Steps + +**Step 1: Update Logging (Required)** + +Find and replace in your code: + +```bash +# Search for old API +grep -r "setLogLevel" . + +# Replace with LogSinks.setConsole +``` + +**Step 2: Update ReplicatorConfiguration (Recommended)** + +```typescript +// Old (deprecated) +const config = new ReplicatorConfiguration(endpoint); +config.addCollection(collection); + +// New (recommended) +const collectionConfig = new CollectionConfiguration(collection); +const config = new ReplicatorConfiguration([collectionConfig], endpoint); +``` + +**Step 3: Update Listener Cleanup (Recommended)** + +```typescript +// Old (deprecated) +await collection.removeChangeListener(token); + +// New (recommended) +await token.remove(); +``` + +--- + +## Important Property Names + +| Listener Type | Property | Correct | Wrong | +|--------------|----------|---------|-------| +| Collection Change | Document IDs | `documentIDs` | `documentIds` | +| Document Change | Document ID | `documentId` | `documentID` | +| Collection Change | Database | `collection.database` | `database` | +| Document Change | Database | `database` | N/A | +| Replicator Progress | Completed | `getCompleted()` | `completed` | +| Replicator Progress | Total | `getTotal()` | `total` | + +--- + +## Need Help? + +- **Log Sink Examples:** [Using Logs](../../Troubleshooting/using-logs.md) +- **Replication Setup:** [Remote Sync](../../DataSync/remote-sync-gateway.md) +- **Full Release Notes:** [Version 1.0](../../ProductNotes/release-notes.md) + diff --git a/versioned_docs/version-1.0/Guides/_category_.json b/versioned_docs/version-1.0/Guides/_category_.json new file mode 100644 index 0000000..3ced3d2 --- /dev/null +++ b/versioned_docs/version-1.0/Guides/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Guides", + "position": 17, + "collapsible": true, + "collapsed": false +} + diff --git a/versioned_docs/version-1.0/ProductNotes/_category_.json b/versioned_docs/version-1.0/ProductNotes/_category_.json new file mode 100644 index 0000000..b1052d5 --- /dev/null +++ b/versioned_docs/version-1.0/ProductNotes/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Product Notes", + "position": 18, + "link": { + "type": "generated-index", + "description": "Learn more about releases, compatibility, and supported platforms." + } +} diff --git a/versioned_docs/version-1.0/ProductNotes/compatibility.md b/versioned_docs/version-1.0/ProductNotes/compatibility.md new file mode 100644 index 0000000..1f8668a --- /dev/null +++ b/versioned_docs/version-1.0/ProductNotes/compatibility.md @@ -0,0 +1,17 @@ +--- +id: compatibility +sidebar_position: 2 +--- + +# Compatibility + + :::note +Supported iOS and Android versions are dependent on React Native. See the [React Native Documentation](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) documentation for more information. + ::: + +The cbl-reactnative library is build against the native SDK for iOS and Android. The current version of the native SDK is 3.2.0. To see the compatibility notes for the native SDK, see the following documentation: + +- [Couchbase Mobile Compatibility Guide - iOS](https://docs.couchbase.com/couchbase-lite/current/swift/supported-os.html). + +- [Couchbase Mobile Compatibility Guide - Android](https://docs.couchbase.com/couchbase-lite/current/android/supported-os.html). + diff --git a/versioned_docs/version-1.0/ProductNotes/release-notes.md b/versioned_docs/version-1.0/ProductNotes/release-notes.md new file mode 100644 index 0000000..3fcae6f --- /dev/null +++ b/versioned_docs/version-1.0/ProductNotes/release-notes.md @@ -0,0 +1,78 @@ +--- +id: release-notes +sidebar_position: 1 +--- + +# Release Notes + +**1.0.0** (December 2025) + +New Features: +- Log Sink API - Console, File, and Custom log sinks with configurable levels and domains +- LogDomain.ALL - New domain to enable all log categories at once +- Listener Token Management - New `ListenerToken` class with `token.remove()` API +- Collection Change Listeners - Monitor all documents in a collection +- Document Change Listeners - Monitor specific documents by ID +- Query Change Listeners (Live Queries) - Real-time query results +- Replicator Status Change Listeners - Monitor replication state and progress +- Replicator Document Change Listeners - Track individual document replication +- New ReplicatorConfiguration API - Collections passed during initialization using CollectionConfiguration +- Collection.fullName() method - Get fully qualified collection name (scope.collection) +- Couchbase Lite 3.3.0 - Updated iOS and Android SDKs to Couchbase Lite 3.3.0 + +Breaking Changes: +- TypeScript: ListenerToken type changed from string to ListenerToken object (affects explicitly typed code only) + +Deprecated APIs (Remain available for backward compatibility): +- Database.setLogLevel() - Use LogSinks.setConsole() instead. Note: Old and new logging APIs cannot be used in tandem. +- config.addCollection(collection) - Pass CollectionConfiguration array in constructor instead +- removeChangeListener() methods - Use token.remove() instead +- ListenerToken type changed from string to ListenerToken object (TypeScript breaking change for explicitly typed code) + +Bug Fixes: +- Fixed encryption key crash when key not required +- Fixed Kotlin import paths and enhanced logging methods +- Improved blob data validation and array handling +- Fixed custom delete issues + +Migration from 0.6.x: +1. Replace Database.setLogLevel() with LogSinks.setConsole() (required - APIs cannot be mixed) +2. Update ReplicatorConfiguration to use new constructor pattern (recommended) +3. Update listener cleanup to use token.remove() (recommended) +4. Update TypeScript code that explicitly typed tokens as string to use ListenerToken (required for TypeScript) + +See [Migration Guide](../Guides/Migration/v1.md) for detailed instructions. + +--- + +**0.6.3** +- Array handling and improve blob data validation in DataAdapter [null-pointer issue](https://github.com/couchbase/couchbase-lite-react-native/pull/73) +- Fix a crash caused by improper handling of encryption key + +**0.6.1** +- Implemented [Collection Change Listeners](https://github.com/couchbase/couchbase-lite-react-native/pull/54) on Android +- Implemented [Query Change Listeners](https://github.com/couchbase/couchbase-lite-react-native/pull/55) on Android +- Fixed data adapter issues and improved testing +- Fixed [issue](https://github.com/couchbase/couchbase-lite-react-native/issues/38) related to collection `getDocument` always pulling blob content + +**0.5.0** +- Implemented Collection Document Change +- Implemented Query Change Listener (Live Query) +- Implemented Replicator Status Change and Replicator Document Change (iOS) +- Fixed issue related to creation of two database instances + +**0.2.3** +- Couchbase Lite 3.2.1 support +- React Native 0.76.3 support +- Updated Documents and Blob API to support new way of processing documents due to changes made in cbl-ionic to support nested arrays in Android +- Expo Example App + - Updated to Expo 52.0.11 + - Added Test Runners and support for cblite-js-tests + - Validated all implemented tests pass +- Started working on fixes for emitter problems with listeners + +Known Issues: +- Emitter problems with listeners, features like Live Query and Replicator Status are not working. + +**0.2.0** +- Initial Release diff --git a/versioned_docs/version-1.0/Queries/_category_.json b/versioned_docs/version-1.0/Queries/_category_.json new file mode 100644 index 0000000..23b5bb2 --- /dev/null +++ b/versioned_docs/version-1.0/Queries/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Queries", + "position": 9, + "link": { + "type": "generated-index", + "description": "Learn about how to Query using SQL++" + } +} diff --git a/versioned_docs/version-1.0/Queries/live-queries.md b/versioned_docs/version-1.0/Queries/live-queries.md new file mode 100644 index 0000000..5bdcfae --- /dev/null +++ b/versioned_docs/version-1.0/Queries/live-queries.md @@ -0,0 +1,175 @@ +--- +id: live-queries +sidebar_position: 6 +--- + +# Live Queries + +> Description - _Couchbase Lite Live Query Concepts_ +> Related Content - [SQL++ for Mobile](sqlplusplus.md) + +## Activating a Live Query + +A live query is a query that, once activated, remains active and monitors the database for changes; refreshing the result set whenever a change occurs. As such, it is a great way to build reactive user interfaces - especially table/list views - that keep themselves up to date. + +**So, a simple use case may be:** A replicator running and pulling new data from a server, whilst a live-query-driven UI automatically updates to show the data without the user having to manually refresh. This helps your app feel quick and responsive. + +With Couchbase Lite for React Native, live queries can be watched through: + + * Listener callbacks: `Query.addChangeListener` + +Each time you start watching a live query, the query is executed and an initial change notification is dispatched. The query is then kept active and further change notifications are dispatched whenever a change occurs. + +#### Example 1. Starting a Live Query - Change Listener + +```typescript +import { ListenerToken } from 'cbl-reactnative'; + +// Register a change listener +const token: ListenerToken = await query.addChangeListener((change) => { + if (change.error) { + console.error('Query error:', change.error); + return; + } + + const results = change.results; + // results is an array of result objects + for (const doc of results) { + console.log('Result:', doc); + } +}); +``` + +:::note Version 1.0 +Change listeners now return a `ListenerToken` object with a `remove()` method for cleanup. +::: + +#### Example 2. Stopping a Live Query - Change Listener + +```typescript +// Remove listener using new API +await token.remove(); +``` + +:::caution Deprecated +The `query.removeChangeListener(token)` method is deprecated. It remains available for backward compatibility, but new applications should use `token.remove()`. Existing applications are strongly encouraged to migrate. + +```typescript +// DEPRECATED +await query.removeChangeListener(token); + +// RECOMMENDED +await token.remove(); +``` +::: + +## Query Change Data Structure + +When a query result changes, your callback receives a `QueryChange` object: + +```typescript +interface QueryChange { + error: string; // Error message if query failed + query: Query; // Reference to the query + results: ResultSet; // Array of result objects +} +``` + +#### Example 3. Complete Live Query with Error Handling + +```typescript +import { ListenerToken } from 'cbl-reactnative'; + +const query = database.createQuery( + 'SELECT META().id, name, email FROM _default.users WHERE isActive = true' +); + +const token: ListenerToken = await query.addChangeListener((change) => { + if (change.error) { + console.error('Query error:', change.error); + return; + } + + console.log(`Found ${change.results.length} active users`); + change.results.forEach(result => { + console.log(`User: ${result.name} - ${result.email}`); + }); +}); + +// Remove when done +await token.remove(); +``` + +#### Example 4. React Hook Pattern for Live Queries + +```typescript +import { useEffect, useState } from 'react'; +import { ListenerToken } from 'cbl-reactnative'; + +function ActiveUsersScreen({ database }) { + const [users, setUsers] = useState([]); + + useEffect(() => { + if (!database) return; + + let token; + + const setup = async () => { + const query = database.createQuery( + 'SELECT META().id, name FROM _default.users WHERE isActive = true' + ); + + token = await query.addChangeListener((change) => { + if (!change.error) { + setUsers(change.results); + } + }); + }; + setup(); + + // Cleanup on unmount + return () => { + if (token && !token.isRemoved()) { + token.remove(); + } + }; + }, [database]); + + return ( + // Render users list + ); +} +``` + +## Best Practices + +**Always Remove Listeners** + +In React components, use the cleanup function to prevent memory leaks: + +```typescript +useEffect(() => { + let token; + + const setup = async () => { + token = await query.addChangeListener((change) => { + // Handle changes + }); + }; + setup(); + + return () => { + if (token && !token.isRemoved()) { + token.remove(); + } + }; +}, [query]); +``` + +**Check Before Removing** + +```typescript +if (!token.isRemoved()) { + await token.remove(); +} +``` \ No newline at end of file diff --git a/versioned_docs/version-1.0/Queries/query-result-set.md b/versioned_docs/version-1.0/Queries/query-result-set.md new file mode 100644 index 0000000..77ebe8c --- /dev/null +++ b/versioned_docs/version-1.0/Queries/query-result-set.md @@ -0,0 +1,56 @@ +--- +id: query-result-set +sidebar_position: 5 +--- + +# Query Result Sets + +When querying a database, the results are returned as an array of objects (`ResultSet`). Each object (`Result`) has keys based on the collection names used in the `FROM` statement of your query. If an alias is used, the key will be the alias name. This allows you to access the properties of the results easily and ensures that the structure of your results matches the query structure. + +#### Example 1. Query Result Sets + +```typescript +const query = database.createQuery('SELECT * FROM inventory.hotel AS hotelItems WHERE city="Medway"'); +const resultSet: ResultSet = await query.execute(); + +for (const result of resultSet) { + console.log(result['hotelItems'].propertyName); +} +``` + +In this example, `hotelItems` is the alias used for the collection in the query, and it serves as the key in the `Result` objects within the `ResultSet`. + +#### Example 2. Query Result Sets + +```typescript +const query = database.createQuery('SELECT hotelItems.*, META().id as docId FROM inventory.hotel AS hotelItems WHERE city="Medway"'); +const resultSet: ResultSet = await query.execute(); + +for (const result of resultSet) { + console.log(result['docId']); + console.log(result['hotelItems'].propertyName); +} +``` + +n this example, `hotelItems` is the alias used for the collection in the query, and it serves as the key in the `Result` objects within the `ResultSet`. Each `result` object also includes a `docId`, representing the document ID, allowing you to access both the document ID and the hotel item details. + + +#### Example 3. Query Result Sets + +```typescript +const query = database.createQuery(` + SELECT * + FROM route + JOIN airline + ON route.airlineid = META(airline).id + WHERE airline.country = "France" +`); +const resultSet: ResultSet = await query.execute(); + +for (const result of resultSet) { + console.log(result['route'].propertyName); // Access properties from the route collection + console.log(result['airline'].propertyName); // Access properties from the airline collection +} +``` + +In this example, `route` and `airline` are the collections being queried and joined based on the `airlineid`. The `Result` objects within the `ResultSet` contain keys corresponding to these collection names, allowing you to access properties from both the `route` and `airline` collections. The query filters the results to only include airlines from France. diff --git a/versioned_docs/version-1.0/Queries/query-troubleshooeting.md b/versioned_docs/version-1.0/Queries/query-troubleshooeting.md new file mode 100644 index 0000000..d88244a --- /dev/null +++ b/versioned_docs/version-1.0/Queries/query-troubleshooeting.md @@ -0,0 +1,327 @@ +--- +id: query-troubleshooting +sidebar_position: 7 +--- + +# Query Troubleshoooting + +> Description - _Couchbase Lite Queries - Troubleshooting_ +> Abstract - _This content describes how to use the Couchbase Lite for React Native Query API's exlplain method to examine a query. + +## Query Explain + +### Using + +The `Query.explain()` method can provide useful insight when you are trying to diagnose query performance issues and-or optimize queries. To examine how your query is working, either embed the call inside your app (see: Example 1), or use it interactively within a cblite shell (see: Example 2). + +#### Example 1. Using Query Explain in App + +```typescript +// Create the query +const query = database.createQuery('SELECT META().id AS thisId FROM inventory.hotel WHERE city="Medway"'); + +// Print the explanation of the query +const explanation = await query.explain(); +console.log(explanation); +``` + +1. Construct your query as normal. +2. Call the query's `explain` method and print it. + +#### Example 2. Using Query Explain in cblite + +```bash +cblite .cblite2 +(cblite) select --explain domains GROUP BY country ORDER BY country, name +(cblite) query --explain {"GROUP_BY":[[".country"]],"ORDER_BY":[[".country"],[".name"]],"WHAT":[[".domains"]]} +``` + +1. Within a terminal session open your database with cblite and enter your query. +2. Here the query is entered as an SQL++ query using select. +3. Here the query is entered as a JSON-string using query. + +### Output + +The output from explain() remains the same whether invoked by an app, or cblite - see [Example 3](#example-3-queryexplain-output) for an example of how it looks. + +#### Example 3. Query.explain() Output + +``` +SELECT fl_result(fl_value(_doc.body, 'domains')) FROM kv_default AS _doc WHERE (_doc.flags & 1 = 0) GROUP BY fl_value(_doc.body, 'country') ORDER BY fl_value(_doc.body, 'country'), fl_value(_doc.body, 'name') + +7|0|0| SCAN TABLE kv_default AS _doc +12|0|0| USE TEMP B-TREE FOR GROUP BY +52|0|0| USE TEMP B-TREE FOR ORDER BY + +{"GROUP_BY":[[".country"]],"ORDER_BY":[[".country"],[".name"]],"WHAT":[[".domains"]]} +``` + +This output ([Example 3](#example-3-queryexplain-output)) comprises three main elements: + +1. The translated SQL-query, which is not necessarily useful, being aimed more at Couchbase support and-or engineering teams. +2. The [SQLite query plan](https://www.sqlite.org/eqp.html), which gives a high-level view of how the SQL query will be implemented. You can use this to identify potential issues and so optimize problematic queries. +3. The query in JSON-string format, which you can copy-and-paste directly into the *cblite* tool. + +## The Query Plan + +### Format + +The query plan section of the output displays a tabular form of the translated query's execution plan. It primarily shows how the data will be retrieved and, where appropriate, how it will be sorted for navigation and-or presentation purposes. For more on SQLite's Explain Query Plan - see: https://www.sqlite.org/eqp.html. + +#### Example 4. A Query Plan + +``` +7|0|0| SCAN TABLE kv_default AS _doc +12|0|0| USE TEMP B-TREE FOR GROUP BY +52|0|0| USE TEMP B-TREE FOR ORDER BY +``` + +1. **Retrieval method** - This line shows the retrieval method being used for + the query; here a sequential read of the database. Something you may well be + looking to optimize - see [Retrieval Method](#retrieval-method) for more. +2. **Grouping method** --- This line shows that the `GROUP BY` clause used in + the query requires the data to be sorted and that a b-tree will be used for + temporary storage - see [Order and Group](#order-and-group). +3. **Ordering method** - This line shows that the `ORDER BY` clause used in the + query requires the data to be sorted and that a b-tree will be used for + temporary storage - see [Order and Group](#order-and-group). + +### Retrieval Method {#retrieval-method} + +The query optimizer will attempt to retrieve the requested data items as +efficiently as possible, which generally will be by using one or more of the +available indexes. The retrieval method shows the approach decided upon by the +optimizer - see [Table 1](#). + +#### Table 1. Retrieval Methods + +| Retrieval Method | Description | +| ---------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Search | Here the query is able to access the required data directly using keys into the index. Queries using the Search mode are the fastest. | +| Scan Index | Here the query is able to retrieve the data by scanning all or part-of the index (for example when seeking to match values within a range). This type of query is slower than search, but at least benefits from the compact and ordered form of the index. | +| Scan Table | Here the query must scan the database table(s) to retrieve the required data. It is the slowest of these methods and will benefit most from some form of optimization. | + +When looking to optimize a query's retrieval method, consider whether: + +- Providing an additional index makes sense. +- You could use an existing index - perhaps by restructuring the query to + minimize wildcard use, or the reliance on functions that modify the query's + interpretation of index keys (for example, 'lower'). +- You could reduce the data set being requested to minimize the query's + footprint on the database. + +### Order and Group + +The `USE TEMP B-TREE FOR` lines in the example indicate that the query requires +sorting to cater for grouping and then sorting again to present the output +results. Minimizing, if not eliminating, this ordering and re-ordering will +obviously reduce the amount of time taken to process your query. + +Ask "is the grouping and-or ordering absolutely necessary?": if it isn't, drop +it or modify it to minimize its impact. + +## Queries and Indexes + +Before we begin querying documents, let's briefly mention the importance of +having an appropriate and balanced approach to indexes. + +Creating indexes can speed up the performance of queries. A query will typically +return results more quickly if it can take advantage of an existing database +index to search, narrowing down the set of documents to be examined. + +:::note + +Couchbase Lite for React Native does not currently support partial value indexes; +indexes with non-property expressions. You should only index with properties +that you plan to use in the query. + +::: + +The Query optimizer converts your query into a parse tree that groups zero or +more _and-connected_ clauses together (as dictated by your where conditionals) +for effective query engine processing. + +Ideally a query will be be able to satisfy its requirements entirely by either +directly accessing the index or searching sequential index rows. Less good is if +the query must scan the whole index; although the compact nature of most indexes +means this is still much faster than the alternative of scanning the entire +database with no help from the indexes at all. + +Searches that begin with or rely upon an inequality with the primary key are +inherently less effective than those using a primary key equality. + +## Working with the Query Optimizer + +You may have noticed that sometimes a query runs faster on a second run, or +after re-opening the database, or after deleting and recreating an index. This +typically happens when SQL Query Optimizer has gathered sufficient stats to +recognize a means of optimizing a sub-optimal query. + +If only those stats were available from the start. In fact they are gathered +after certain events, such as: + +- Following index creation +- On a database close +- When running a database compact. + +So, if your analysis of the [Query Explain output](#example-3-queryexplain-output) indicates a +sub-optimal query and your rewrites fail to sufficiently optimize it, consider +compacting the database. Then re-generate the Query Explain and note any +improvements in optimization. They may not, in themselves, resolve the issue +entirely; but they can provide a uesful guide toward further optimizing changes +you could make. + +## Wildcard and Like-based Queries {#wildcard-queries} + +Like-based searches can use the index(es) only if: + +- The search-string doesn't start with a wildcard. +- The primary search expression uses a property that is an indexed key. +- The search-string is a constant known at run time (that is, not a value + derived during processing of the query). + +To illustrate this we can use a modified query; replacing a simple equality test +with a `LIKE`. + +In [Example 5](#) we use a wildcard prefix and suffix. You can see that the +query plan decides on a retrieval method of `SCAN TABLE`. + +:::tip + +For more on indexes - see: [Indexing](../indexes.md). + +::: + +#### Example 5. Like with Wildcard Prefix + +```typescript +const queryString = ` + SELECT * + FROM hotels AS item + WHERE type LIKE '%hotel%' +`; + +const query = database.createQuery(queryString); +console.log(await query.explain()); +``` + +1. The indexed property, `type`, cannot use its index because of the wildcard + prefix. + +#### Example 6. Resulting Query Plan + +```console +2|0|0| SCAN TABLE kv_default AS _doc +``` + +By contrast, by removing the wildcard prefix `%` (in [Example 7](#example-7-like-with-no-wildcard-prefix)), we see +that the query plan's retrieval method changes to become an index search. Where +practical, simple changes like this can make significant differences in query +performance. + +#### Example 7. Like with No Wildcard-prefix + +```typescript +const queryString = ` + SELECT * + FROM hotels AS item + WHERE type LIKE 'hotel%' AND name LIKE '%royal%' +`; + +const query = database.createQuery(queryString); +console.log(await query.explain()); +``` + +1. Simply removing the wildcard prefix enables the query optimizer to access the + `typeIndex`, which results in a more efficient search. + +#### Example 8. Resulting Query Plan + +```console +3|0|0| SEARCH TABLE kv_default AS _doc USING INDEX typeIndex (>? AND =?) +``` + +Knowing this, you can consider how you create the index; for example, using +`LOWER` when you create the index and then always using +lowercase comparisons. + +## Optimization Considerations + +Try to minimize the amount of data retrieved. Reduce it down to the few +properties you really do need to achieve the required result. + +Consider fetching details lazily. You could break complex queries into +components. Returning just the document IDs, then process the array of document +IDs using either the Document API or a query thats uses the array of document +IDs to return information. + +Consider using paging to minimize the data returned when the number of results +returned is expected to be high. Getting the whole lot at once will be slow and +resource intensive: Plus does anyone want to access them all in one go? Instead +retrieve batches of information at a time, perhaps using `LIMIT` and `OFFSET` +clauese to set a starting point for each subsequent batch. + +Although, note that using query offsets becomes increasingly less effective as +the overhead of skipping a growing number of rows each time increases. You can +work around this, by instead using ranges of search-key values. If the last +search-key value of batch one was 'x' then that could become the starting point +for your next batch and-so-on. + +Optimize document size in design. Smaller documents load more quickly. Break +your data into logical linked units. + +Consider Using Full Text Search instead of complex `LIKE` or `REGEX` patterns - +see [Full Text Search](../full-text-search.md). \ No newline at end of file diff --git a/versioned_docs/version-1.0/Queries/sqlplusplus-mobile-and-server-differences.md b/versioned_docs/version-1.0/Queries/sqlplusplus-mobile-and-server-differences.md new file mode 100644 index 0000000..4fa7d59 --- /dev/null +++ b/versioned_docs/version-1.0/Queries/sqlplusplus-mobile-and-server-differences.md @@ -0,0 +1,124 @@ +--- +id: sqlplusplus-mobile-and-server-differences +sidebar_position: 2 +--- + +# SQL++ for Mobile and Server Differences + +:::important + +N1QL is Couchbase's implementation of the developing SQL++ standard. As such the +terms N1QL and SQL++ are used interchangeably in all Couchbase documentation +unless explicitly stated otherwise. + +::: + +There are several minor but notable behavior differences between SQL++ for +Mobile queries and SQL++ for Server, as shown in [Table 1](#table-1-sql-query-comparison). + +In some instances, if required, you can force SQL++ for Mobile to work in the +same way as SQL++ for Server. These instances are noted in the content below. + +#### Table 1. SQL++ Query Comparison + +| Feature | SQL++ for Server | SQL++ for Mobile | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `USE KEYS` | `SELECT fname, email FROM tutorial USE KEYS ('dave', 'ian');` | `SELECT fname, email FROM tutorial WHERE META().id IN ('dave', 'ian');` | +| `ON KEYS` | `SELECT * FROM user u JOIN orders o ON KEYS ARRAY s.order_id FOR s IN u.order_history END;` | `SELECT * FROM user u, u.order_history s JOIN orders o ON s.order_id = Meta(o).id;` | +| `USE KEY` | `SELECT * FROM user u JOIN orders o ON KEY o.user_id FOR u;` | `SELECT * FROM user u JOIN orders o ON META(u).id = o.user_id;` | +| `NEST` | `SELECT * FROM user u NEST orders orders ON KEYS ARRAY s.order_id FOR s IN u.order_history END;` | `NEST`/`UNNEST` not supported | +| `LEFT OUTER NEST` | `SELECT * FROM user u LEFT OUTER NEST orders orders ON KEYS ARRAY s.order_id FOR s IN u.order_history END;` | `NEST`/`UNNEST` not supported | +| `ARRAY` | `ARRAY i FOR i IN [1, 2] END` | `(SELECT VALUE i FROM [1, 2] AS i)` | +| `ARRAY FIRST` | `ARRAY FIRST arr` | `arr[0]` | +| `LIMIT l OFFSET o` | Allows `OFFSET` without `LIMIT` | Allows `OFFSET` without `LIMIT` | +| `UNION`, `INTERSECT`, `EXCEPT` | All three are supported (with `ALL` and `DISTINCT` variants). | Not supported | +| `OUTER JOIN` | Both `LEFT` and `RIGHT OUTER JOIN` are supported. | Only `LEFT OUTER JOIN` supported (and necessary for query expressability). | +| `<`, `<=`, `=`, etc. operators | Can compare either complex values or scalar values. | Only scalar values may be compared. | +| `ORDER BY` | Result sequencing is based on specific rules described in [SQL++ for Server `ORDER BY` clause](https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/orderby.html). | Result sequencing is based on the SQLite ordering described in [SQLite select overview](https://sqlite.org/lang_select.html).

The ordering of Dictionary and Array objects is based on binary ordering. | +| `SELECT DISTINGCT` | Supported | `SELECT DISTINCT VALUE` is supported when the returned values are scalars. | +| `CREATE INDEX` | Supported | Not Supported | +| `INSERT`, `UPSERT`, `DELETE` | Supported | Not Supported | + + +## Boolean Logic Rules + +### SQL++ for Server + +Couchbase Server operates in the same way as Couchbase Lite, except: + +- `MISSING`, `NULL` and `FALSE` are `FALSE` +- Numbers `0` is `FALSE` +- Empty strings, arrays, and objects are `FALSE` +- All other values are `TRUE` + +You can choose to use Couchbase Server's SQL++ rules by using the +`TOBOOLEAN(expr)` function to convert a value to its boolean value. + +### SQL++ for Mobile + +SQL++ for Mobile's boolean logic rules are based on SQLite's, so: + +- `TRUE` is `TRUE`, and `FALSE` is `FALSE` +- Numbers `0` or `0.0` are `FALSE` +- Arrays and dictionaries are `FALSE` +- String and Blob are `TRUE` if the values are casted as a non-zero or `FALSE` + if the values are casted as `0` or `0.0` - see: + [SQLITE's CAST and Boolean expressions](https://sqlite.org/lang_expr.html) for + more details. +- `NULL` is `FALSE` +- `MISSING` is `MISSING` + +### Logical Operations + +In SQL++ for Mobile logical operations will return one of three possible values; +`TRUE`, `FALSE`, or `MISSING`. + +Logical operations with the `MISSING` value could result in `TRUE` or `FALSE` if +the result can be determined regardless of the missing value, otherwise the +result will be `MISSING`. + +In SQL++ for Mobile - unlike SQL++ for Server - `NULL` is implicitly converted +to `FALSE` before evaluating logical operations. [Table 2](#table-2-logical-operations-comparison) summarizes the +result of logical operations with different operand values and also shows where +the Couchbase Server behavior differs. + +#### Table 2. Logical Operations Comparison + +| Operand
a | Operand
b | SQL ++ for Mobile
a AND b | SQL ++ for Mobile
a OR b | SQL ++ for Server
a AND b | SQL ++ for Server
a OR b | +| ------------- | ------------- | ------------------------------ | ----------------------------- | ------------------------------ | ----------------------------- | +| `TRUE` | `TRUE` | `TRUE` | `TRUE` | - | - | +| | `FALSE` | `FALSE` | `TRUE` | - | - | +| | `NULL` | `FALSE` | `TRUE` | `NULL` | - | +| | `MISSING` | `MISSING` | `TRUE` | - | - | +| `FALSE` | `TRUE` | `FALSE` | `TRUE` | - | - | +| | `FALSE` | `FALSE` | `FALSE` | - | - | +| | `NULL` | `FALSE` | `FALSE` | - | `NULL` | +| | `MISSING` | `FALSE` | `MISSING` | - | - | +| `NULL` | `TRUE` | `FALSE` | `TRUE` | `NULL` | - | +| | `FALSE` | `FALSE` | `FALSE` | - | `NULL` | +| | `NULL` | `FALSE` | `FALSE` | `NULL` | `NULL` | +| | `MISSING` | `FALSE` | `MISSING` | `MISSING` | `NULL` | +| `MISSING` | `TRUE` | `MISSING` | `TRUE` | - | - | +| | `FALSE` | `FALSE` | `MISSING` | - | - | +| | `NULL` | `FALSE` | `MISSING` | `MISSING` | `NULL` | +| | `MISSING` | `MISSING` | `MISSING` | - | - | + + +## CRUD Operations + +- SQL++ for Mobile only supports Read or Query operations. +- SQL++ for Server fully supports CRUD operation. + +## Functions + +### Division Operator + +| SQL ++ for Server | SQL++ for Mobile | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| SQL++ for Server always performs float division regardless of the types of the operands.

You can force this behavior in SQL++ for Mobile by using the `DIV(x, y)` function. | The operand types determine the division operation performed.

If both are integers, integer division is used.

If one is a floating number, then float division is used. | + +### Round Function + +| SQL ++ for Server | SQL++ for Mobile | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| SQL++ for Server `ROUND()` uses the Rounding to Nearest Even convention (for example, `ROUND(1.85)` returns `1.8`).

You can force this behavior in Couchbase Lite by using the `ROUND_EVEN()` function. | The `ROUND()` function returns a value to the given number of integer digits to the right of the decimal point (left if digits is negative).

Digits are `0` if not given.

Midpoint values are handled using the Rounding Away From Zero convention, which rounds them to the next number away from zero (for example, `ROUND(1.85)` returns `1.9`). | \ No newline at end of file diff --git a/versioned_docs/version-1.0/Queries/sqlplusplus.md b/versioned_docs/version-1.0/Queries/sqlplusplus.md new file mode 100644 index 0000000..90d6542 --- /dev/null +++ b/versioned_docs/version-1.0/Queries/sqlplusplus.md @@ -0,0 +1,1362 @@ +--- +id: sqlplusplus +sidebar_position: 1 +--- + +# SQL++ for Mobile + +> Description - _How to use SQL++ Query Strings to build effective queries with Couchbase Lite for React Native_ +> Related Content - [Live Queries](live-queries.md) | [Indexes](../indexes.md) + +:::info +N1QL is Couchbase's implementation of the developing SQL++ standard. As such the terms N1QL and SQL++ are used interchangeably in all Couchbase documentation unless explicitly stated otherwise. +::: + +## Introduction + +Developers using Couchbase Lite for React Native can provide SQL++ query strings using the SQL++ Query API. This API uses query statements of the form shown in [Example 1](#example-1-running-a-sql-query). The structure and semantics of the query format are based on that of Couchbase Server's SQL++ query language - see [SQL++ Reference Guide](https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/index.html) and [SQL++ Data Model](https://docs.couchbase.com/server/current/learn/data/n1ql-versus-sql.html). + +## Running + +Use `Database.createQuery` to define a query through an SQL++ string. Then run the query using the `Query.execute()` method. + +#### Example 1. Running a SQL++ Query + +```typescript +const query = database.createQuery('SELECT META().id AS thisId FROM inventory.hotel WHERE city="Medway"'); +const resultSet = await query.execute(); +``` + +## Query Format + +The API uses query statements of the form shown in [Example 2](#example-2-query-format). + +#### Example 2. Query Format + +```SQL +SELECT ____ +FROM ____ +JOIN ____ +WHERE ____ +GROUP BY ____ +ORDER BY ____ +LIMIT ____ +OFFSET ____ +``` + +### Query Components + +1. The `SELECT` clause specifies the data to be returned in the result set. +2. The `FROM` clause specifies the collection to query the documents from. +3. The `JOIN` clause specifies the criteria for joining multiple documents. +4. The `WHERE` clause specifies the query criteria. The `SELECT`ed properties of documents matching this criteria will be returned in the result set. +5. The `GROUP BY` clause specifies the criteria used to group returned items in the result set. +6. The `ORDER BY` clause specifies the criteria used to order the items in the result set. +7. The `LIMIT` clause specifies the maximum number of results to be returned. +8. The `OFFSET` clause specifies the number of results to be skipped before starting to return results. + +:::tip +We recommend working through the [SQL++ Tutorials](https://query-tutorial.couchbase.com/tutorial/#1) as a good way to build your SQL++ skills. +::: + +## SELECT Clause + +### Purpose + +Projects the result returned by the query, identifying the columns it will contain. + +### Syntax + +#### Example 3. SQL++ Select Syntax + +```SQL +select = SELECT _ ( ( DISTINCT | ALL ) _ )? selectResults +selectResults = selectResult ( _? ',' _? selectResult )* +selectResult = expression ( ( _ AS )? _ columnAlias )? +columnAlias = IDENTIFIER +``` + +### Arguments + +1. The select clause begins with the `SELECT` keyword. + * The optional `ALL` argument is used to specify that the query should return `ALL` results (the default). + * The optional `DISTINCT` argument is used to specify that the query should return distinct results. + +2. `selectResults` is a list of columns projected in the query result. Each column is an expression which could be a property expression or any expression or function. You can use the `*` expression, to select all columns. + +3. Use the optional `AS` argument to provides an alias for a column. Each column can be aliased by putting the alias name after the column name. + +#### SELECT Wildcard + +When using the `*` expression, the column name is one of: + + * The alias name, if one was specified. + * The data source name(or its alias if provided) as specified in the [FROM clause](https://cbl-dart.dev/queries/sqlplusplus-mobile/#from-clause). + +This behavior is inline with that of SQL++ for Server - see example in [Table 1](#table-1-example-column-names-for-select). + +#### Table 1. Example Column Names for SELECT * {#table-1-example-column-names-for-select} + +| Query | Column Name | +|-------------------------------|---------------------| +| `SELECT * AS data FROM _` | `data` | +| `SELECT * FROM _` | `_` | +| `SELECT * FROM _default` | `_default` | +| `SELECT * FROM users` | `users` | +| `SELECT * FROM users AS user` | `user` | + +### Example + +#### Example 4. SELECT Examples + +```SQL +SELECT * ...; +SELECT user.* AS data ...; +SELECT name fullName ...; +SELECT user.name ...; +SELECT DISTINCT address.city ...; +``` + +1. Use the `*` expression to select all columns. +2. Select all properties from the `user` data source. Give the object an alias of `data`. +3. Select a pair of properties. +4. Select a specific property from the `user` data source. +5. Select the property `city` from the `address` data source. + +## FROM Clause + +### Purpose + +Specifies the data source and optionally applies an alias (`AS`). It is mandatory. + +### Syntax + +#### Example 5. FROM Syntax + +```SQL +from = FROM _ dataSource +dataSource = collectionName ( ( _ AS )? _ collectionAlias )? +collectionName = IDENTIFIER +collectionAlias = IDENTIFIER +``` + +Here `dataSource` is the collection name against which the query is to run. Use `AS` to give the collection an alias you can use within the query. To use the default collection, without specifying a name, use `_` as the data source. + +### Example + +#### Example 6. FROM Examples + +```SQL +SELECT name FROM testScope.user; +SELECT user.name FROM testScope.users AS user; +SELECT user.name FROM testScope.users user; +-- These queries use the default scope and default collection (_default._default) in Couchbase. +SELECT name FROM _default._default; +SELECT user.name FROM _default._default AS user; +SELECT user.name FROM _default._default user; +``` + +## JOIN Clause + +### Purpose + +The `JOIN` clause enables you to select data from multiple data sources linked by criteria specified in the ON constraint. Currently only self-joins are supported. For example to combine airline details with route details, linked by the airline id - see Example 7. + +### Syntax + +#### Example 7. JOIN Syntax + +```SQL +join = joinOperator _ dataSource ( _ constraint )? +joinOperator = ( ( LEFT ( _ OUTER )? | INNER | CROSS ) _ )? JOIN +dataSource = collectionName ( ( _ AS )? _ collectionAlias )? +constraint = ON _ expression +collectionName = IDENTIFIER +collectionAlias = IDENTIFIER +``` + +### Arguments + +1. The `JOIN` clause starts with a `JOIN` operator followed by the data source. +2. Five `JOIN` operators are supported: + * `JOIN`, `LEFT JOIN`, `LEFT OUTER JOIN`, `INNER JOIN`, and `CROSS JOIN`. + * Note: `JOIN` and `INNER JOIN` are the same, and `LEFT JOIN` and `LEFT OUTER JOIN` are the same. +3. The `JOIN` constraint starts with the `ON` keyword followed by the expression that defines the joining constraints. + +### Example + +#### Example 8. JOIN Examples + +```SQL +SELECT users.prop1, other.prop2 +FROM testScope.users +JOIN users AS other ON users.key = other.key; + +SELECT users.prop1, other.prop2 +FROM testScope.users +LEFT JOIN users AS other ON users.key = other.key; +``` + +#### Example 9. Using JOIN to Combine Document Details + +This example joins the documents from the `routes` collections with documents from the `airlines` collection using the document ID (`id`) of the *airline* document and the `airlineId` property of the *route* document. + +```SQL +SELECT * +FROM inventory.routes r +JOIN airlines a ON r.airlineId = META(a).id +WHERE a.country = "France"; +``` + +## WHERE Clause + +### Purpose + +Specifies the selection criteria used to filter results. As with SQL, use the `WHERE` clause to choose which results are returned by your query. + +### Syntax + +#### Example 10. WHERE Syntax + +```SQL +where = WHERE _ expression +``` + +### Arguments + +1. `WHERE` evalates the expression to a `BOOLEAN` value. You can combine any number of expressions through logical operators, in order to implement sophisticated filtering capabilities. + +### Example + +#### Example 11. WHERE Examples + +```SQL +SELECT name +FROM testScope.employees +WHERE department = "engineer" AND group = "mobile" +``` + +## GROUP BY Clause + +### Purpose + +Use `GROUP BY` to group results for aggreation, based on one or more expressions. + +### Syntax + +#### Example 12. GROUP BY Syntax + +```SQL +groupBy = grouping ( _ having )? +grouping = GROUP BY _ expression ( _? ',' _? expression )* +having = HAVING _ expression +``` + +### Arguments + +1. The `GROUP BY` clause starts with the `GROUP BY` keyword followed by one or more expressions. +2. The `GROUP BY` clause is normally used together with aggregate functions (e.g. `COUNT`, `MAX`, `MIN`, `SUM`, `AVG`). +3. The `HAVING` clause allows you to filter the results based on aggregate functions - for example, `HAVING COUNT(airlineId) > 100`. + +### Example + +#### Example 13. GROUP BY Examples + +```SQL +SELECT COUNT(airlineId), destination +FROM inventory.routes +GROUP BY destination; + +SELECT COUNT(airlineId), destination +FROM inventory.routes +GROUP BY destination +HAVING COUNT(airlineId) > 100; + +SELECT COUNT(airlineId), destination +FROM inventory.routes +WHERE destinationState = "CA" +GROUP BY destination +HAVING COUNT(airlineId) > 100; +``` + +## ORDER BY Clause + +### Purpose + +Sort query results based on a expression. + +### Syntax + +#### Example 14. ORDER BY Syntax + +```SQL +orderBy = ORDER BY _ ordering ( _? ',' _? ordering )* +ordering = expression ( _ order )? +order = ( ASC | DESC ) +``` + +### Arguments + +1. The `ORDER BY` clause starts with the `ORDER BY` keyword followed by one or more ordering expressions. +2. An ordering expression specifies an expressions to use for ordering the results. +3. For each ordering expression, the sorting direction can be specified using the optional `ASC` (ascending) or `DESC` (descending) directives. Default is `ASC`. + +### Example + +#### Example 15. ORDER BY Examples + +```SQL +SELECT name +FROM testScope.users +ORDER BY name; + +SELECT name +FROM testScope.users +ORDER BY name DESC; + +SELECT name, score +FROM testScope.users +ORDER BY name ASC, score DESC; +``` + +## LIMIT Clause + +### Purpose + +Specifies the maximum number of results to be returned by the query. + +### Syntax + +#### Example 16. LINIT Syntax + +```SQL +limit = LIMIT _ expression +``` + +### Arguments + +1. The `LIMIT` clause starts with the `LIMIT` keyword followed by an expression that will be evaluated as a number. + +### Example + +#### Example 17. LIMIT Examples + +```SQL +SELECT name +FROM testScope.users +LIMIT 10; +``` + +## OFFSET Clause {#offset-clause} + +### Purpose + +Specifies the number of results to be skipped by the query. + +### Syntax + + +#### Example 18. OFFSET syntax + +``` +offset = OFFSET _ expression +``` + +### Arguments + +1. The offset clause starts with the `OFFSET` keyword followed by an expression + that will be evaluated as a number that represents the number of results to + be skipped before the query begins returning results. + +### Example + +#### Example 19. OFFSET Examples + +```sql +SELECT name +FROM testScope.users +OFFSET 10; + +SELECT name +FROM testScope.users +LIMIT 10 +OFFSET 10; +``` + +## Expressions {#expressions} + +An expression is a specification for a value that is resolved when executing a +query. This section, together with [Operators](#operators) and +[Functions](#functions), which are covered in their own sections, covers all the +available types of expressions. + +### Literals + +#### Boolean + +##### Purpose + +Represents a true or false value. + +##### Syntax + +#### Example 20. Boolean Syntax + +``` +boolean = ( TRUE | FALSE ) +``` + +##### Example + +#### Example 21. Boolean Examples + +```sql +SELECT value +FROM testScope.testCollection +WHERE value = true; + +SELECT value +FROM testScope.testCollection +WHERE value = false; +``` + +#### Numeric + +##### Purpose + +Represents a numeric value. Numbers may be signed or unsigned digits. They have +optional fractional and exponent components. + +##### Syntax + +#### Example 22. Numeric Syntax + +``` +numeric = -? ( ( . digit+ ) | ( digit+ ( . digit* )? ) ) ( ( E | e ) ( - | + )? digit+ )? +digit = /[0-9]/ +``` + + +##### Example + +#### Example 23. Numeric Examples + +```sql +SELECT + 10, + 0, + -10, + 10.25, + 10.25e2, + 10.25E2, + 10.25E+2, + 10.25E-2 +FROM testScope.testCollection; +``` + +#### String + +##### Purpose + +The string literal represents a string or sequence of characters. + +##### Syntax + +#### Example 24. String Syntax + +``` +string = ( " character* " | ' character* ' ) +character = ( escapeSequence | any codepoint except ", ' or control characters ) +escapeSequence = \ ( " | ' | \ | / | b | f | n | r | t | u hex hex hex hex ) +hex = hexDigit hexDigit +hexDigit = /[0-9a-fA-F]/ +``` + +:::note + +The string literal can be double-quoted as well as single-quoted. + +::: + +##### Example + +#### Example 25. String Examples + +```sql +SELECT firstName, lastName +FROM crm.customer +WHERE contact.middleName = "middle" AND contact.lastName = 'last'; +``` + +#### NULL + +##### Purpose + +Represents the absence of a value. + +##### Syntax + +#### Example 26. NULL Syntax + +``` +null = NULL +``` + +##### Example + +#### Example 27. NULL Examples + +```sql +SELECT firstName, lastName +FROM crm.customer +WHERE contact.middleName IS NULL; +``` + +#### MISSING + +##### Purpose + +Represents a missing name-value pair in a dictionary. + +##### Syntax + +#### Example 28. MISSING Syntax + +``` +missing = MISSING +``` + +##### Example + +#### Example 29. MISSING Examples + +```sql +SELECT firstName, lastName +FROM crm.customer +WHERE contact.middleName IS MISSING; +``` + +#### Array + +##### Purpose + +Represents an array. + +##### Syntax + +#### Example 30. ARRAY Syntax + +``` +array = [ ( _? expression ( _? ',' _? expression )* _? )? ] +``` + +##### Example + +#### Example 31. ARRAY examples + +```sql +SELECT ["a", "b", "c"] +FROM testScope.testCollection + +SELECT [property1, property2, property3] +FROM testScope.testCollection +``` + +#### Dictionary + +##### Purpose + +Represents a dictionary. + +##### Syntax + +#### Example 32. Dictionary Syntax + +``` +dictionary = { ( _? string _? : _? expression ( _? , _? string _? : _? expression )* _? )? } +``` + + +##### Example + +#### Example 33. Dictionary Examples + +```sql +SELECT { 'name': 'James', 'department': 10 } +FROM testScope.testCollection; + +SELECT { 'name': 'James', 'department': dept } +FROM testScope.testCollection; + +SELECT { 'name': 'James', 'phones': ['650-100-1000', '650-100-2000'] } +FROM testScope.testCollection; +``` + +### Identifier + +#### Purpose + +An identifier references an entity by its symbolic name. Use an identifier for +example to identify: + +- Column alias names +- Database names +- Database alias names +- Property names +- Parameter names +- Function names +- FTS index names + +#### Syntax + +#### Example 34. Identifier Syntax + +``` +identifier = ( plainIdentifier | quotedIdentifier ) +plainIdentifier = /[a-zA-Z_][a-zA-Z0-9_$]*/ +quotedIdentifier = /`[^`]+`/ +``` + +:::tip + +To use other than basic characters in the identifier, surround the identifier +with the backticks ` character. For example, to use a hyphen (-) in an +identifier, use backticks to surround the identifier. + +Please note that backticks are commonly used for string literals/interpolation in TypeScript/JavaScript. Therefore, you should be aware that backticks need to be escaped properly to function correctly in TypeScript/JavaScript. For more information, refer to [Template Literal Types in TypeScript](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html). + +::: + +#### Example + +#### Example 35. Identifier Examples + +```sql +-- This query uses the default scope and default collection (_default._default) in Couchbase. +SELECT * +FROM _default._default; + +SELECT * +FROM test-scope.test-collection; + +SELECT key +FROM testScope.testCollection; + +SELECT key$1 +FROM test_Scope.test_Collection; + +SELECT `key-1` +FROM testScope.testCollection; +``` + +### Property Expression + +#### Purpose + +The property expression is used to reference a property of a dictionary. + +#### Syntax + +#### Example 36. Property Expression Syntax + +``` +property = ( * | dataSourceName . _? * | propertyPath ) +propertyPath = propertyName ( ( . _? propertyName ) | ( [ _? numeric _? ] _? ) )* +propertyName = IDENTIFIER +``` + +1. Prefix the property expression with the data source name or alias to indicate + its origin. +2. Use dot syntax to refer to nested properties in the propertyPath. +3. Use bracket (`[index]`) syntax to refer to an item in an array. +4. Use the asterisk (`*`) character to represents all properties. This can only + be used in the result list of the `SELECT` clause. + + +#### Example + +#### Example 37. Property Expressions Examples + +```sql +SELECT * +FROM crm.customer +WHERE contact.firstName = 'daniel'; + +SELECT crm.customer.* +FROM crm.customer +WHERE contact.firstName = 'daniel'; + +SELECT crm.customer.contact.address.city +FROM crm.customer +WHERE contact.firstName = 'daniel'; + +SELECT contact.address.city, contact.phones[0] +FROM crm.customer +WHERE contact.firstName = 'daniel'; +``` + +### Any and Every Expression + +#### Purpose + +Evaluates expressions over items in an array. + +#### Syntax + +#### Example 38. Any and Every Expression Syntax + + +```sql +arrayExpression = anyEvery _ variableName _ IN _ expression _ SATISFIES _ expression _ END +anyEvery = ( anyOrSome AND EVERY | anyOrSome | EVERY ) +anyOrSome = ( ANY | SOME ) +variableName = IDENTIFIER +``` + +1. The array expression starts with `anyEvery`, where each possible combination + has a different function as described below, and is terminated by `END`. + + - `ANY` or `SOME`: Returns `TRUE` if at least one item in the array satisfies + the expression, otherwise returns `FALSE`. + + :::note + + `ANY` and `SOME` are interchangeable. + + ::: + + - `EVERY`: Returns `TRUE` if all items in the array satisfies the expression, + otherwise returns `FALSE`. If the array is empty, returns `TRUE`. + - `( ANY | SOME ) AND EVERY`: Same as `EVERY` but returns `FALSE` if the + array is empty. + +2. The `variableName` represents each item in the array. +3. The `IN` keyword is used to specify the array to be evaluated. +4. The `SATISFIES` keyword is used to specify the expression to evaluate for + each item in the array. +5. `END` terminates the array expression. + + +#### Example + +#### Example 39. Any and Every Expression Examples + + +```sql +SELECT firstName, lastName +FROM crm.customer +WHERE + ANY contact IN contacts + SATISFIES contact.city = 'San Mateo' + END; +``` + +### Parameter Expression + +#### Purpose + +A parameter expression references a value from the `api|Parameters` assigned to +the query before execution. + +:::note + +If a parameter is specified in the query string, but no value has been provided, +an error will be thrown when executing the query. + +::: + +#### Syntax + +#### Example 40. Parameter Expression Syntax + + +``` +parameter = $ IDENTIFIER +``` + +#### Example + +#### Example 41. Parameter Expression Examples + + +```sql +SELECT * +FROM crm.customer +WHERE contact.firstName = $firstName; +``` + +#### Example 42. Using a Parameter + + +```typescript +const query = database.createQuery('SELECT * FROM crm.customer WHERE contact.firstName = $firstName'); +const params = new Parameters(); +params.setValue('firstName', 'daniel'); +query.parameters = params; +const resultSet = await query.execute(); +``` + +### Parenthesis Expression + +#### Purpose + +Use parentheses to group expressions together to make them more readable or to +establish operator precedence. + +#### Example + +#### Example 43. Parenthesis Expression Examples + + +```sql +SELECT (value1 + value2) * value 3 +FROM testScope.testCollection + +SELECT * +FROM testScope.testCollection +WHERE ((value1 + value2) * value3) + value4 = 10; + +SELECT * +FROM testScope.testCollection +WHERE (value1 = value2) + OR (value3 = value4); +``` + +## Operators {#operators} + +### Binary Operators + +#### Maths + +#### Table 2. Maths Operators + +| Op | Description | Example | +| --- | -------------- | --------------------- | +| `+` | Add | `WHERE v1 + v2 = 10` | +| `-` | Subtract | `WHERE v1 - v2 = 10` | +| `*` | Multiply | `WHERE v1 \* v2 = 10` | +| `/` | Divide - see 1 | `WHERE v1 / v2 = 10` | +| `%` | Modulus | `WHERE v1 % v2 = 0` | + +1. If both operands are integers, integer division is used, but if one is a + floating number, then float division is used. This differs from SQL++ for + Server, which performs float division regardless. Use `DIV(x, y)` to force + float division in SQL++ for Mobile. + +#### Comparison Operators + +##### Purpose + +The comparison operators can for example be used in the `WHERE` clause to +specify the condition on which to match documents. + +#### Table 3. Comparison Operators + + +| Op | Description | Example | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `=` or `==` | Equals | `WHERE v1 = v2
WHERE v1 == v2` | +| `!=` or <> | Not Equal to | `WHERE v1 != v2
WHERE v1 <> v2` | +| `>` | Greater than | `WHERE v1 > v2` | +| `>=` | Greater than or equal to | `WHERE v1 >= v2` | +| `<` | Less than | `WHERE v1 < v2` | +| `<=` | Less than or equal to | `WHERE v1 <= v2` | +| `IN` | Returns `TRUE` if the value is in the list or array of values specified by the right hand side expression; Otherwise returns `FALSE`. | `WHERE 'James' IN contactsList` | +| `LIKE` | String wildcard pattern matching, comparison - see 2. Two wildchards are supported:
• `%` Matches zero or more characters.
• \_` Matches a single character. | `WHERE name LIKE 'a%'`
`WHERE name LIKE '%a'`
`WHERE name LIKE '%or%'`
`WHERE name LIKE 'a%o%'`
`WHERE name LIKE '%_r%'`
`WHERE name LIKE '%a_%'`
`WHERE name LIKE '%a__%'`
`WHERE name LIKE 'aldo'`
| +| `MATCH` | String matching using FTS | `WHERE v1-index MATCH "value"` | +| `BETWEEN` | Logically equivalent to `v1 >= start AND v1 <= end` | `WHERE v1 BETWEEN 10 AND 100` | +| `IS NULL` - see 3 | Equal to `null` | `WHERE v1 IS NULL` | +| `IS NOT NULL` | Not equal to `null` | `WHERE v1 IS NOT NULL` | +| `IS MISSING` | Equal to MISSING | `WHERE v1 IS MISSING` | +| `IS NOT MISSING` | Not equal to MISSING | `WHERE v1 IS NOT MISSING` | +| `IS VALUED` | Logically equivalent to `IS NOT NULL AND MISSING` | `WHERE v1 IS VALUED` | +| `IS NOT VALUED` | Logically equivalent to `IS NULL OR MISSING` | `WHERE v1 IS NOT VALUED` | + +2. Matching is case-insensitive for ASCII characters, case-sensitive for + non-ASCII. +3. Use of `IS` and `IS NOT` is limited to comparing `NULL` and `MISSING` values + (this encompasses `VALUED`). This is different from `api|QueryBuilder`, in + which they operate as equivalents of `==` and `!=`. + + +#### Table 4. Comparing NULL and MISSING values using IS + + +| Op | Non-`NULL` Value | `NULL` | `MISSING` | +| ---------------- | ---------------- | ------- | --------- | +| `IS NULL` | `FALSE` | `TRUE` | `MISSING` | +| `IS NOT NULL` | `TRUE` | `FALSE` | `MISSING` | +| `IS MISSING` | `FALSE` | `FALSE` | `TRUE` | +| `IS NOT MISSING` | `TRUE` | `TRUE` | `FALSE` | +| `IS VALUED` | `TRUE` | `FALSE` | `FALSE` | +| `IS NOT VALUED` | `FALSE` | `TRUE` | `TRUE` | + + +#### Logical Operators + +##### Purpose + +Logical operators combine expressions using the following boolean logic rules: + +- `TRUE` is `TRUE`, and `FALSE` is `FALSE`. +- Numbers `0` or `0.0` are `FALSE`. +- Arrays and dictionaries are `FALSE`. +- Strings and Blobs are `TRUE` if the values are casted as a non-zero or `FALSE` + if the values are casted as `0` or `0.0`. +- `NULL` is `FALSE`. +- `MISSING` is `MISSING`. + +:::note + +This is different from SQL++ for Server, where: + +- `MISSING`, `NULL` and `FALSE` are `FALSE`. +- Numbers `0` is `FALSE`. +- Empty strings, arrays, and objects are `FALSE`. +- All other values are `TRUE`. + +::: + +:::tip + +Use the `TOBOOLEAN(expr)` function to convert a value based on SQL++ for Server +boolean value rules. + +::: + +#### Table 5. Logical Operators + + +| Op | Description | Example | +| ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| `AND` | Returns `TRUE` if the operand expressions evaluate to `TRUE`; otherwise `FALSE`.

If an operand is `MISSING` and the other is `TRUE` returns `MISSING`, if the other operand is `FALSE` it returns `FALSE`.

If an operand is `NULL` and the other is `TRUE` returns `NULL`, if the other operand is `FALSE` it returns `FALSE`. | `WHERE city = 'San Francisco' AND status = TRUE` | +| `OR` | Returns `TRUE` if one of the operand expressions is evaluated to `TRUE`; otherwise returns `FALSE`

If an operand is `MISSING`, the operation will result in `MISSING` if the other operand is `FALSE` or `TRUE` if the other operand is `TRUE`.

If an operand is `NULL`, the operation will result in `NULL` if the other operand is `FALSE` or `TRUE` if the other operand is `TRUE`. | `WHERE city = 'San Francisco' OR city = 'Santa Clara'` | + + +#### Table 6. Logical Operators Table + + +| `a` | `b` | `a AND b` | `a OR b` | +| --------- | --------- | -------------- | ---------------- | +| `TRUE` | `TRUE` | `TRUE` | `TRUE` | +| | `FALSE` | `FALSE` | `TRUE` | +| | `NULL` | `FALSE`, see 5 | `TRUE` | +| | `MISSING` | `MISSING` | `TRUE` | +| `FALSE` | `TRUE` | `FALSE` | `TRUE` | +| | `FALSE` | `FALSE` | `FALSE` | +| | `NULL` | `FALSE` | `FALSE`, see 5 | +| | `MISSING` | `FALSE` | `MISSING` | +| `NULL` | `TRUE` | `FALSE`, see 5 | `TRUE` | +| | `FALSE` | `FALSE` | `FALSE`, see 5 | +| | `NULL` | `FALSE`, see 5 | `FALSE`, see 5 | +| | `MISSING` | `FALSE`, see 6 | `MISSING`, see 7 | +| `MISSING` | `TRUE` | `MISSING` | `TRUE` | +| | `FALSE` | `FALSE` | `MISSING` | +| | `NULL` | `FALSE`, see 6 | `FALSE`, see 7 | +| | `MISSING` | `MISSING` | `MISSING` | + +This differs from SQL++ for Server in the following instances: + +- 5. Server will return: `NULL` instead of `FALSE`. +- 6. Server will return: `MISSING` instead of `FALSE`. +- 7. Server will return: `NULL` instead of `MISSING`. + +#### String Operators + +##### Purpose + +A single string operator is provided. It enables string concatenation. + +#### Table 7. String Operators + + +| Op | Description | Example | +| ------------ | ------------- | -------------------------------------------------------------------------------- | +| `||` | Concatenating | `SELECT firstName || lastName AS fullName FROM testScope.testCollection` | + + +### Unary Operators + +#### Purpose + +Three unary operators are provided. They operate by modifying an expression, +making it numerically positive or negative, or by logically negating its value +(`TRUE` becomes `FALSE`). + +#### Syntax + +#### Example 44. Unary Operators Syntax + + +```sql +unaryOperator = ( + | - | NOT ) _ expression +``` + + +#### Table 8. Unary Operators + +| Op | Description | Example | +| ----- | ------------------------------ | ----------------------------------- | +| `+` | Positive value | `WHERE v1 = +10` | +| `-` | Negative value | `WHERE v1 = -10` | +| `NOT` | Logical Negate operator, see 8 | `WHERE "James" NOT IN contactsList` | + +8. The `NOT` operator is often used in conjunction with operators such as `IN`, + `LIKE`, `MATCH`, and `BETWEEN` operators. + - `NOT` operation on `NULL` value returns `NULL`. + - `NOT` operation on `MISSING` value returns `MISSING`. + + +#### Table 9. NOT Operators + +| `a` | `NOT a` | +| --------- | --------- | +| `TRUE` | `FALSE` | +| `FALSE` | `TRUE` | +| `NULL` | `FALSE` | +| `MISSING` | `MISSING` | + + +### `COLLATE` Operator + +#### Purpose + +Collate operators specify how a string comparison is conducted. + +#### Usage + +The collate operator is used in conjunction with string comparison expressions +and `ORDER BY` clauses. It allows for one or more collations. If multiple +collations are used, the collations need to be specified in a parenthesis. When +only one collation is used, the parenthesis is optional. + +:::note + +Collation is not supported by SQL++ for Server. + +::: + +#### Syntax + +#### Example 45. COLLATE Operator Syntax + +```sql +collate = COLLATE _ ( collation | '(' collation ( _ collation )+ ')' ) +collation = NO? (UNICODE | CASE | DIACRITICS) +``` + +#### Arguments + +1. The available collation options are: + - `UNICODE`: Conduct a Unicode comparison; the default is to do ASCII + comparison. + - `CASE`: Conduct case-sensitive comparison + - `DIACRITIC`: Take accents and diacritics into account in the comparison; On + by default. + - `NO`: This can be used as a prefix to the other collations, to disable + them. For example, use `NOCASE` to enable case-insensitive comparison. + +#### Example + +#### Example 46. COLLATE Operator Example + +```sql +SELECT contact +FROM crm.customer +WHERE contact.firstName = "fred" COLLATE UNICODE; + +SELECT contact +FROM crm.customer +WHERE contact.firstName = "fred" COLLATE (UNICODE CASE); + +SELECT firstName, lastName +FROM crm.customer +ORDER BY firstName COLLATE (UNICODE DIACRITIC), lastName COLLATE (UNICODE DIACRITIC); +``` + +### Conditional Operator + +#### Purpose + +The conditional (or `CASE`) operator evaluates conditional logic in a similar +way to the `IF`/`ELSE` operator. + +#### Syntax + +#### Example 47. Conditional Operators Syntax + +``` +case = CASE _ ( expression _ )? + ( WHEN _ expression _ THEN _ expression _ )+ + ( ELSE _ expression _)? + END +``` + +Both _Simple Case_ and _Searched Case_ expressions are supported. The syntactic +difference being that the _Simple Case_ expression has an expression after the +`CASE` keyword. + +1. Simple Case Expression + - If the `CASE` expression is equal to the first `WHEN` expression, the + result is the `THEN` expression. + - Otherwise, any subsequent `WHEN` clauses are evaluated in the same way. + - If no match is found, the result of the `CASE` expression is the `ELSE` + expression, or `NULL` if no `ELSE` expression was provided. +2. Searched Case Expression + - If the first `WHEN` expression is `TRUE`, the result of this expression is + its `THEN` expression. + - Otherwise, subsequent `WHEN` clauses are evaluated in the same way. + - If no `WHEN` clause evaluate to `TRUE`, then the result of the expression + is the `ELSE` expression, or `NULL` if no `ELSE` expression was provided. + + +#### Examples + +#### Example 48. Simple Case + +```sql +SELECT + CASE state + WHEN 'CA' + THEN 'Local' + ELSE 'Non-Local' + END +FROM testScope.testCollection; +``` + +#### Examples + +#### Example 49. Searched Case + + +```sql +SELECT + CASE + WHEN shippedOn IS NOT NULL + THEN 'SHIPPED' + ELSE 'NOT-SHIPPED' + END +FROM testScope.testCollection; +``` + + +## Functions {#functions} + +### Purpose + +Functions provide specialised operations through a generalized syntax. + +### Syntax + +#### Example 50. Functions Syntax + + +The function syntax is the same as C-style language function syntax. It starts +with the function name, followed by optional arguments inside parentheses. + +``` +function = functionName _? '(' ( _? expression ( _? ',' _? expression )* _? )? ')' +functionName = IDENTIFIER +``` + +### Aggregation Functions + +#### Table 10. Aggregation Functions + +| Function | Description | +| -------------- | ------------------------------------------------------- | +| `AVG(value)` | Returns the average of all numeric values in the group. | +| `COUNT(value)` | Returns the count of all values in the group. | +| `MIN(value)` | Returns the minimum numeric value in the group. | +| `MAX(value)` | Returns the maximum numeric value in the group. | +| `SUM(value)` | Returns the sum of all numeric values in the group. | + + +### Array Functions + +#### Table 11. Array Functions + +| Function | Description | +| ----------------------- | ---------------------------------------------------------------------------------------------------- | +| `ARRAY_AGG(value)` | Returns an array of the non-`MISSING` group values in the input expression, including `NULL` values. | +| `ARRAY_AVG(value)` | Returns the average of all non-`NULL` number values in the array; or `NULL` if there are none. | +| `ARRAY_CONTAINS(value)` | Returns `TRUE` if the value exists in the array; otherwise `FALSE`. | +| `ARRAY_COUNT(value)` | Returns the number of non-`NULL` values in the array. | +| `ARRAY_IFNULL(value)` | Returns the first non-`NULL` value in the array. | +| `ARRAY_MAX(value)` | Returns the largest non-`NULL`, non_MISSING value in the array. | +| `ARRAY_MIN(value)` | Returns the smallest non-`NULL`, non_MISSING value in the array. | +| `ARRAY_LENGTH(value)` | Returns the length of the array. | +| `ARRAY_SUM(value)` | Returns the sum of all non-`NULL` numeric value in the array. | + + +### Conditional Functions + +#### Table 12. Conditional Functions + +| Function | Description | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `IFMISSING(value, ...)` | Returns the first non-`MISSING` value, or `NULL` if all values are `MISSING`. | +| `IFMISSINGORNULL(value, ...)` | Returns the first non-`NULL` and non-`MISSING` value, or `NULL` if all values are `NULL` or `MISSING`. | +| `IFNULL(value, ...)` | Returns the first non-`NULL`, or `NULL` if all values are `NULL`. | +| `MISSINGIF(value, other)` | Returns `MISSING` when `value = other`; otherwise returns `value`.
Returns `MISSING` if either or both expressions are `MISSING`.
Returns `NULL` if either or both expressions are `NULL`. | +| `NULLIF(value, other)` | Returns `NULL` when `value = other`; otherwise returns `value`.
Returns `MISSING` if either or both expressions are `MISSING`.
Returns `NULL` if either or both expressions are `NULL`. | + + +### Date and Time Functions + +#### Table 13. Date and Time Functions + +| Function | Description | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `STR_TO_MILLIS(value)` | Returns the number of milliseconds since the unix epoch of the given ISO 8601 date input string. | +| `STR_TO_UTC(value)` | Returns the ISO 8601 UTC date time string of the given ISO 8601 date input string. | +| `MILLIS_TO_STR(value)` | Returns a ISO 8601 date time string in device local timezone of the given number of milliseconds since the unix epoch expression. | +| `MILLIS_TO_UTC(value)` | Returns the UTC ISO 8601 date time string of the given number of milliseconds since the unix epoch expression. | + + +### Full Text Search Functions + +#### Table 14. FTS Functions + +| Function | Description | Example | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | +| `MATCH(indexName, term`)` | Returns `TRUE` if `term` expression matches the FTS indexed document. `indexName` identifies the FTS index to search for matches. | `WHERE MATCH(description, 'couchbase')` | +| `RANK(indexName)` | Returns a numeric value indicating how well the current query result matches the full-text query when performing the MATCH. indexName is an IDENTIFIER for the FTS index. | `WHERE MATCH(description, 'couchbase') ORDER BY RANK(description)` | + + +### Maths Functions + +#### Table 15. Maths Functions + +| Function | Description | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ABS(value)` | Returns the absolute value of a number. | +| `ACOS(value)` | Returns the arc cosine in radians. | +| `ASIN(value)` | Returns the arcsine in radians. | +| `ATAN(value)` | Returns the arctangent in radians. | +| `ATAN2(a, b)` | Returns the arctangent of `a` / `b`. | +| `CEIL(value)` | Returns the smallest integer not less than the number. | +| `COS(value)` | Returns the cosine of an angle in radians. | +| `DIV(a, b)` | Returns float division of `a` and `b`. Both `a` and `b` are cast to a double number before division. The returned result is always a double. | +| `DEGREES(value)` | Converts radians to degrees. | +| `E()` | Returns the e constant, which is the base of natural logarithms. | +| `EXP(value)` | Returns the natural exponential of a number. | +| `FLOOR(value)` | Returns largest integer not greater than the number. | +| `IDIV(a, b)` | Returns integer division of `a` and `b`. | +| `LN(value)` | Returns log base e. | +| `LOG(value)` | Returns log base 10. | +| `PI()` | Returns the pi constant. | +| `POWER(a, b)` | Returns `a` to the `b`th power. | +| `RADIANS(value)` | Converts degrees to radians. | +| `ROUND(value (, digits)?)` | Returns the rounded value to the given number of integer digits to the right of the decimal point (left if digits is negative). Digits are 0 if not given.

The function uses Rounding Away From Zero convention to round midpoint values to the next number away from zero (so, for example, `ROUND(1.75)` returns 1.8 but `ROUND(1.85)` returns 1.9. | +| `ROUND_EVEN(value (, digits)?)` | Returns rounded value to the given number of integer digits to the right of the decimal point (left if digits is negative). Digits are 0 if not given.

The function uses Rounding to Nearest Even (Banker's Rounding) convention which rounds midpoint values to the nearest even number (for example, both `ROUND_EVEN(1.75)` and `ROUND_EVEN(1.85)` return 1.8). | +| `SIGN(value)` | Returns -1 for negative, 0 for zero, and 1 for positive numbers. | +| `SIN(value)` | Returns sine of an angle in radians. | +| `SQRT(value)` | Returns the square root. | +| `TAN(value)` | Returns tangent of an angle in radians. | +| `TRUNC(value (, digits)?)` | Returns a truncated number to the given number of integer `digits` to the right of the decimal point (left if digits is negative). Digits are 0 if not given. | + +:::note + +The behavior of the `ROUND()` function is different from SQL++ for Server +`ROUND()`, which rounds the midpoint values using Rounding to Nearest Even +convention. + +::: + +### Pattern Searching Functions + +#### Table 16. Pattern Searching Functions + +| Function | Description | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `REGEXP_CONTAINS(value, pattern)` | Returns `TRUE` if the string value contains any sequence that matches the regular expression pattern. | +| `REGEXP_LIKE(value, pattern)` | Return `TRUE` if the string value exactly matches the regular expression pattern. | +| `REGEXP_POSITION(value, pattern)` | Returns the first position of the occurrence of the regular expression pattern within the input string expression. Returns `-1` if no match is found. Position counting starts from zero. | +| `REGEXP_REPLACE(value, pattern, repl [, n])` | Returns a new string with occurrences of `pattern` replaced with `repl`. If `n` is given, at the most `n` replacements are performed. If `n` is not given, all matching occurrences are replaced. | + + +### String Functions + +#### Table 17. String Functions + +| Function | Description | +| ---------------------------- | ---------------------------------------------------------------------------------------------------- | +| `CONTAINS(value, substring)` | Returns `TRUE` if the substring exists within the input string, otherwise returns `FALSE`. | +| `LENGTH(value)` | Returns the length of a string. The length is defined as the number of characters within the string. | +| `LOWER(value)` | Returns the lower-case string of the input string. | +| `LTRIM(value)` | Returns the string with all leading whitespace characters removed. | +| `RTRIM(value)` | Returns the string with all trailing whitespace characters removed. | +| `TRIM(value)` | Returns the string with all leading and trailing whitespace characters removed. | +| `UPPER(value)` | Returns the upper-case string of the input string. | + + +### Type Checking Functions + +#### Table 18. Type Checking Functions + +| Function | Description | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ISARRAY(value)` | Returns `TRUE` if `value` is an array, otherwise returns `MISSING`, `NULL` or `FALSE`. | +| `ISATOM(value)` | Returns `TRUE` if `value` is a boolean, number, or string, otherwise returns `MISSING`, `NULL` or `FALSE`. | +| `ISBOOLEAN(value)` | Returns `TRUE` if `value` is a boolean, otherwise returns `MISSING`, `NULL` or `FALSE`. | +| `ISNUMBER(value)` | Returns `TRUE` if `value` is a number, otherwise returns `MISSING`, `NULL` or `FALSE`. | +| `ISOBJECT(value)` | Returns `TRUE` if `value` is an object (dictionary), otherwise returns `MISSING`, `NULL` or `FALSE`. | +| `ISSTRING(value)` | Returns `TRUE` if `value` is a string, otherwise returns `MISSING`, `NULL` or `FALSE`. | +| `TYPE(value)` | Returns one of the following strings, based on the value of `value`:
• "missing"
• "null"
• "boolean"
• "number"
• "string"
• "array"
• "object"
• "binary" | + + +### Type Conversion Functionsunctions + +#### Table 19. Type Conversion Functions + +| Function | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `TOARRAY(value)` | Returns `MISSING` if the value is `MISSING`.
Returns `NULL` if the value is `NULL`.
Returns an array value as is.
Returns all other values wrapped in an array. | +| `TOATOM(value)` | Returns `MISSING` if the value is `MISSING`.
Returns `NULL` if the value is `NULL`.
Returns an array of a single item if the value is an array.
Returns an object of a single key/value pair if the value is an object.
Returns a boolean, number, or string value as is.
Returns `NULL` for all other values. | +| `TOBOOLEAN(value)` | Returns `MISSING` if the value is `MISSING`.
Returns `NULL` if the value is `NULL`.
Returns `FALSE` if the value is `FALSE`.
Returns `FALSE` if the value is `0` or `NaN`.
Returns `FALSE` if the value is an empty string, array, and object.
Return `TRUE` for all other values. | +| `TONUMBER(value)` | Returns `MISSING` if the value is `MISSING`.
Returns `NULL` if the value is `NULL`.
Returns `0` if the value is `FALSE`.
Returns `1` if the value is `TRUE`.
Returns a number value as is.
Parses a string value in to a number.
Returns `NULL` for all other values. | +| `TOOBJECT(value)` | Returns `MISSING` if the value is `MISSING`.
Returns `NULL` if the value is `NULL`.
Returns an object value as is.
Returns an empty object for all other values. | +| `TOSTRING(value)` | Returns `MISSING` if the value is `MISSING`.
Returns `NULL` if the value is `NULL`.
Returns "false" if the value is `FALSE`.
Returns "true" if the value is `TRUE`.
Returns a string representation of a number value.
Returns a string value as is.
Returns `NULL` for all other values. | + + +## QueryBuilder Differences {#querybuilder-differences} + +SQL++ for Mobile queries support all `QueryBuilder` features. See [Table 20](#) +for the features supported by SQL++ for Mobile but not by `QueryBuilder`. + +#### Table 20. QueryBuilder Differences + +| Category | Components | +| -------------------------- | ------------------------------------------------------------------------------------------------------------- | +| Conditional Operator | `CASE(WHEN ... THEN ... ELSE ...)` | +| Array Functions | `ARRAY_AGG`, `ARRAY_AVG`, `ARRAY_COUNT`, `ARRAY_IFNULL`, `ARRAY_MAX`, `ARRAY_MIN`, `ARRAY_SUM` | +| Conditional Functions | `IFMISSING`, `IFMISSINGORNULL`, `IFNULL`, `MISSINGIF`, `NULLIF`, `MATCH`, `RANK`, `DIV`, `IDIV`, `ROUND_EVEN` | +| Pattern Matching Functions | `REGEXP_CONTAINS`, `REGEXP_LIKE`, `REGEXP_POSITION`, `REGEXP_REPLACE` | +| Type Checking Functions | `ISARRAY`, `ISATOM`, `ISBOOLEAN`, `ISNUMBER`, `ISOBJECT`, `ISSTRING`, `TYPE` | +| Type Conversion Functions | `TOARRAY`, `TOATOM`, `TOBOOLEAN`, `TONUMBER`, `TOOBJECT`, `TOSTRING` | + + +## Query Parameters {#query-parameters} + +You can provide runtime parameters to your SQL++ query to make it more flexible. +To specify substitutable parameters within your query string prefix the name +with `$` - see: [Example 51](#example-51-running-a-sql-query). + +#### Example 51. Running a SQL++ Query + +You can provide runtime parameters to your SQL++ query to make it more flexible. To specify substitutable parameters within your query string prefix the name with $ - see: [Example 51](#example-51-running-a-sql-query). + +```typescript +const query = await database.createQuery( + 'SELECT META().id AS docId FROM hotel WHERE country = $country' +); + +const params = new Parameters(); +params.setString('country','France') +query.parameters = params; +const resultSet = await query.execute(); +``` + +1. Define a parameter placeholder `$country`. +2. Set the value of the `country` parameter. \ No newline at end of file diff --git a/versioned_docs/version-1.0/StartHere/_category_.json b/versioned_docs/version-1.0/StartHere/_category_.json new file mode 100644 index 0000000..00320f9 --- /dev/null +++ b/versioned_docs/version-1.0/StartHere/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Start Here!", + "position": 2, + "link": { + "type": "generated-index", + "description": "Learn the prerequisites, how to install the plugin, and how to build and run your application with Couchbase Lite." + } +} diff --git a/versioned_docs/version-1.0/StartHere/build-run.md b/versioned_docs/version-1.0/StartHere/build-run.md new file mode 100644 index 0000000..f9f44cc --- /dev/null +++ b/versioned_docs/version-1.0/StartHere/build-run.md @@ -0,0 +1,113 @@ +--- +id: build-run +sidebar_position: 3 +--- + +# Build and Run + +## Quick Steps + + +**iOS** + +**Android** + +The following code snippet is a basic example on how to do CRUD operations and optionally running bi-directional sync with Sync Gateway in Typescript. + +```typescript +import { + Database, + DatabaseConfiguiration, + FileSystem, + Collection, + Query, + MutableDocument, + BasicAuthenticator, + Replicator, + ReplicatorActivityLevel, + CollectionConfiguration, + ReplicatorConfiguration, + ReplicatorType, + URLEndpoint +} from 'cbl-reactnative'; + + +async function runDbSample() : Promise { + try { + //get a file path that you can write the database file to for each platform + const fileSystem = new FileSystem(); + const directoryPath = await fileSystem.getDefaultPath(); + + const dc = new DatabaseConfiguration(); + dc.setDirectory(directoryPath); + const database = new Database('travel', dc); + + await database.open(); + const collection = database.getDefaultCollection(); + + //create a document + const mutableDoc = new MutableDocument('doc-1'); + mutableDoc.setFloat('version', 3.1); + mutableDoc.setString('type', 'SDK'); + + //save it to the database + await collection.save(mutableDoc); + + //update the document + const mutableDoc2 = await collection.document('doc-1'); + mutableDoc2.setString('language', 'Typescript'); + await collection.save(mutableDoc2); + + //create a query to get the documents of type SDK + const query = database.createQuery('SELECT * FROM _default._default WHERE type = "SDK"'); + + //run the query + const results = await query.execute(); + + console.log('Number of documents of type SDK: ' + results.length); + + //loop through the results and do something with them + for (const item of results) { + //to something with the data + const doc = item['_default']; + console.log(doc.type); + console.log(doc.language); + } + + //assumes you are running sync gateway locally, if you are + //running app services, replace enpoint with proper url and creditentials + const target = new URLEndpoint('ws://localhost:4984/projects'); + const auth = new BasicAuthenticator('demo@example.com', 'P@ssw0rd12'); + const collectionConfig = new CollectionConfiguration(collection); + const config = new ReplicatorConfiguration([collectionConfig], target); + config.setAuthenticator(auth); + + const replicator = await Replicator.create(config); + + //listen to the replicator change events + const token = await replicator.addChangeListener((change) => { + //check to see if there was an error + const error = change.status.getError(); + if (error !== undefined) { + //do something with the error + } + //get the status of the replicator using ReplicatorActivityLevel enum + if (change.status.getActivityLevel() === ReplicatorActivityLevel.IDLE) { + //do something because the replicator is now IDLE + } + }); + + // start the replicator without making a new checkpoint + await replicator.start(false); + + //remember you must clean up the replicator when done with it by + //doing the following lines + + //await token.remove(); + //await replicator.stop(); + + } catch (e) { + console.error(e); + } +} +``` diff --git a/versioned_docs/version-1.0/StartHere/example-apps.md b/versioned_docs/version-1.0/StartHere/example-apps.md new file mode 100644 index 0000000..b3e586e --- /dev/null +++ b/versioned_docs/version-1.0/StartHere/example-apps.md @@ -0,0 +1,17 @@ +--- +id: example-apps +sidebar_position: 4 +--- + +# Example Apps + +Several example apps are available for developers to review. These apps are designed to show you how to use the Couchbase Lite for React Native - Native Module in a real-world application. The apps are built using React Native and the Couchbase Lite for React Native. + +## React Native Module Example App + +The cbl-reactnative module has an example app in the repository's [expo-example](https://github.com/couchbase/couchbase-lite-react-native/tree/main/expo-example) folder. This example app is designed to show off all the various APIs and is used to test the Native Module during the development process. + +## Expo Travel Sample App + +The [Expo Travel Sample App](https://github.com/couchbase-examples/expo-cbl-travel) is an example app that uses the Couchbase Capella free-tier and Travel-Sample dataset to show off a list of locations and hotels. + diff --git a/versioned_docs/version-1.0/StartHere/install.md b/versioned_docs/version-1.0/StartHere/install.md new file mode 100644 index 0000000..78790f1 --- /dev/null +++ b/versioned_docs/version-1.0/StartHere/install.md @@ -0,0 +1,228 @@ +--- +id: install +sidebar_position: 2 +--- +# Install + + :::note +This Native Module is currently under active development. If you find problems, please open an issue on the [GitHub repo](https://github.com/couchbase/couchbase-lite-react-native/issues). + ::: + +:::note +The version of this Native Module is based on supporting Couchbase Lite Enterprise for iOS and Android. A [license](https://www.couchbase.com/pricing/) is required to use Couchbase Lite Enterprise edition. +::: + +## Get Started + +The setup for using the Couchbase Lite React Native Native Module is a bit more involved than a typical React Native project. This is because the Native Module is a wrapper around the Couchbase Lite SDKs for iOS and Android. The Couchbase Lite SDKs are written in Swift and Kotlin, respectively, and are not directly compatible with JavaScript. The Native Module provides a bridge between the two languages. + +The installation for the Native Module is provided in two sections: one section for standard React Native apps and one for Expo based apps. + +### React Native Based Apps + +To use the Couchbase Lite React Native Native Module in a standard React Native app, you will need to install the npm package. From the root of your applications project directory (the directory containing your `package.json` file), run the following command: + +```bash +npm install cbl-reactnative +``` + +Once installed, you will want to build each native project (iOS and Android) to link the native module to your project. + +#### iOS + +The Cocoapods for iOS need to be installed in order for the application to build + +```bash +cd ios +pod install +cd .. +``` + +#### Android + +In the current beta release, the Android Gradle file needs to be updated manually to include the native module. This can be done by editing the build.gradle file in the android directory of your React native app and adding the following line below the apply plugin line for the com.facebook.react.rootproject: + +```kotlin +apply from: "../node_modules/cbl-reactnative/android/build.gradle" +``` + +when completed the last two lines of the file should look something like this: + +```kotlin +apply plugin: "com.facebook.react.rootproject" +apply from: "../node_modules/cbl-reactnative/android/build.gradle" +``` + +Now you can install the gradle dependencies and build the Android project: + +```bash +cd android +./gradlew clean +``` + +From there you can do a clean build of the Android project: + +```bash +./gradlew assembleDebug +cd .. +``` + +Note that Android builds can take a while to complete the first time due to downloading all the gradle pacakges required to build the application. Subsequent builds will be faster. + +#### Running the App + +Finall you can run your app like you have before installing the package: + +```bash +npm run start +``` + +### Expo Based Apps + +For developers using Expo, you must make sure you have the dev-client installed in your app. Expo Go is not compatible with custom React Native Native Module. To install the dev-client, review the [Expo documentation](https://docs.expo.dev/develop/development-builds/introduction/#what-is-expo-dev-client) along with the [Local App develompent documentation](https://docs.expo.dev/guides/local-app-development/). These directions assume dev-client is setup and you can build locally your app on iOS and Android. + +The expo environment dynamically builds both the Cocoapod file for iOS and Gradle file for Android. Because of this you will need to register the cbl-reactnative package with the expo environment via the Expo plugin api. + +This can be done by creating a new file in the root of your project called `plugin.config.js` and adding the following code: + +```javascript +const { + withProjectBuildGradle, + withXcodeProject, + withPodfileProperties, +} = require('@expo/config-plugins'); + +// Function to modify Android build.gradle +function modifyAndroidBuildGradle(config) { + const lineToAdd = ` apply from: "../node_modules/cbl-reactnative/android/build.gradle"`; + if (!config.modResults.contents.includes(lineToAdd)) { + config.modResults.contents += `\n${lineToAdd}`; + console.debug(config.modResults.contents); + } + return config; +} + +// Function to modify iOS Xcode project +function modifyXcodeProject(config) { + const xcodeProject = config.modResults; + // Example modification: adding a build phase for a script + // xcodeProject.addBuildPhase([], 'PBXShellScriptBuildPhase', 'Run Script', null, script); + return config; +} + +// Function to modify Podfile properties to include the native module podspec +function includeNativeModulePod(config) { + return withPodfileProperties(config, async (podConfig) => { + const podspecPath = `../node_modules/cbl-reactnative/cbl-reactnative.podspec`; + if (podConfig.modResults.podfileProperties !== undefined && podConfig.modResults.podfileProperties.pod !== undefined) { + podConfig.modResults.podfileProperties.pod( + `'cbl-reactnative', :path => '${podspecPath}'` + ); + } + return podConfig; + }); +} + +module.exports = (config) => { + config = withProjectBuildGradle(config, (gradleConfig) => { + return modifyAndroidBuildGradle(gradleConfig); + }); + config = withXcodeProject(config, (xcodeConfig) => { + return modifyXcodeProject(xcodeConfig); + }); + config = includeNativeModulePod(config); + return config; +}; +``` + +Save this file. Now you can register this file in your app.json file by adding the following line: + +`./plugin.config.js` + +When completed, the `expo` section of your app.json file should look something like this: + +```json +{ + "expo": { + "plugins": [ + "expo-router", + "./plugin.config.js" + ] + } +} +``` + +You can now build your expo app locally on iOS and Android. The native module will be included in the build. + +iOS: +```bash +npx expo run:ios +``` + +Android: +```bash +npx expo run:android +``` +## Register the Module/CBLReactNativeEngine + +The final step in setting up the Couchbase Lite React Native Native Module is to register the module with the React Native environment. The [context and provider](https://react.dev/reference/react/createContext) pattern is the recommended approach for registering the module. This is done by wrapping the root component of your application with a `DatabaseProvider` component that you create. This component will provide the Couchbase Lite React Native Native Module to the rest of your application. Your provider can then be used to create a `Database` object that can be used to interact with the Couchbase Lite database. This must be done regarless of whether your app uses React Native or Expo. + +An example of this pattern can be found in the Expo Example app in [this repository](https://github.com/couchbase-examples/expo-cbl-travel/tree/main/providers). + +The example app provides a [DatabaseService](https://github.com/couchbase-examples/expo-cbl-travel/blob/main/services/database.service.ts) class that manages the database connection and provides methods for interacting with the database. + +Note that the `DatabaseService` class registers the CblReactNative engine in the constructor. This must be done once in your application and is recommended to be done in a singleton like this class to ensure that the engine is only registered once. + +```typescript +export class DatabaseService { + private database: Database | undefined; + private replicator: Replicator | undefined; + private engine: CblReactNativeEngine | undefined; + + constructor() { + //must create a new engine to use the SDK in a React Native environment + //this must only be done once for the entire app. This will be implemented as singleton, so it's Ok here. + this.engine = new CblReactNativeEngine(); + } + .... +``` + +The [DatabaseProvider](https://github.com/couchbase-examples/expo-cbl-travel/blob/main/providers/DatabaseProvider.tsx) component is then used to wrap the root component of the application. This component provides the `DatabaseService` to the rest of the application. It's also a good place to initialize the database and do other tasks like this. + +```typescript +const DatabaseProvider: React.FC = ({children}) => { + const dbService = new DatabaseService(); + const [databaseService, setDatabaseService] = useState(dbService); + + useEffect(() => { + const initializeDatabase = async () => { + await dbService.initializeDatabase(); + }; + //if error should write to custom log file + initializeDatabase().then().catch(e => console.error(e)); + }, [dbService]); + + const databaseServiceValue = useMemo(() => ({databaseService, setDatabaseService}), [databaseService, setDatabaseService]); + return ( + + {children} + + ); + ... +``` + +Finally, the provider should be used in the root component of the application. In modern Expo apps this is usually done in the [app/_layout.tsx](https://github.com/couchbase-examples/expo-cbl-travel/blob/main/app/_layout.tsx#L29C4-L37C7) file. + +```typescript + return ( + + + + + + + + ); +``` + +In React Native apps, this is done usually the App.tsx file. diff --git a/versioned_docs/version-1.0/StartHere/prerequisties.md b/versioned_docs/version-1.0/StartHere/prerequisties.md new file mode 100644 index 0000000..60a8747 --- /dev/null +++ b/versioned_docs/version-1.0/StartHere/prerequisties.md @@ -0,0 +1,57 @@ +--- +id: prerequisites +sidebar_position: 1 +--- + +# Prerequisites + +Couchbase Lite for React Native is provided as a [Native Module](https://reactnative.dev/docs/legacy/native-modules-intro). + +The Native Module can be found at the following repository [Couchbase Lite for React Native](https://github.com/couchbase/couchbase-lite-react-native). This plugin is actively developed and maintained by the community. It is not an official Couchbase product. + +A developer using this plugin should have a basic understanding of the following technologies: +- [React Native](https://reactnative.dev/) +- [React Native - Native Modules](https://reactnative.dev/docs/legacy/native-modules-intro) +- [Expo Framework](https://docs.expo.dev/) +- [Couchbase Lite](https://docs.couchbase.com/couchbase-lite/current/index.html) + +## Expo Note +React Native's recommmendation is to use [Expo](https://reactnative.dev/blog/2024/06/25/use-a-framework-to-build-react-native-apps) for development . The example app that comes with the cbl-reactnative repo is an Expo based app, thus this Native Module can work in Expo apps. Note using Expo Go is not supported due to Expo Go not supporting loading 3rd party Native Modules. You will need to be familiar with the [Expo Dev Client](https://docs.expo.dev/guides/local-app-development/#local-builds-with-expo-dev-client) process to use this Native Module in an Expo app. + +## Supported Platforms +- The React Native - Native Module is supported on iOS and Android platforms. MacOS, Windows, and Web support is not available at this time. + +## React Native Version +- The plugin is built using React Native 0.76.3. Support for older versions of React Native is not guaranteed and apps should be based on 0.76.3 or higher. + +Please review the React Native [Support documentation](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) for a full listed of supported platform versions. + +## Development Environment +- Javascript + - [Node >= 20](https://formulae.brew.sh/formula/node@18) +- React Native + - [React Native Docs](https://reactnative.dev/) + - [Understanding React Native - Native Modules ](https://reactnative.dev/docs/legacy/native-modules-intro) +- Expo (if you choose to use Expo, not required but recommended) + - [Expo Docs](https://docs.expo.dev/) + - [Expo Dev Client](https://docs.expo.dev/guides/local-app-development/#local-builds-with-expo-dev-client) + - [Expo Mono Repos](https://docs.expo.dev/guides/monorepos/) + - [Expo Plugin and mods](https://docs.expo.dev/config-plugins/introduction/) +- IDEs + - [Visual Studio Code](https://code.visualstudio.com/download) + - [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) +- iOS Development + - A modern Mac + - [XCode 15.1](https://developer.apple.com/xcode/) or higher installed and working + - [iOS 15.1 or higher]. Any apps using the plugin must be upgraded to iOS 15.1 or higher. + - [XCode Command Line Tools](https://developer.apple.com/download/more/) installed + - [Simulators](https://developer.apple.com/documentation/safari-developer-tools/installing-xcode-and-simulators) downloaded and working + - [Homebrew](https://brew.sh/) + - [Cocopods](https://formulae.brew.sh/formula/cocoapods) + - A valid Apple Developer account and certificates installed and working +- Android Development + - [API 24 (Android 7)] or higher. Any apps using the plugin must be upgraded to API 24 or higher. Any older versions of Android are not supported by React Native and Expo. + - [Android Studio](https://developer.android.com/studio?gad_source=1&gclid=CjwKCAjwzN-vBhAkEiwAYiO7oALYfxbMYW_zkuYoacS9TX16aItdvLYe6GB7_j1QwvXBjFDRkawfUBoComcQAvD_BwE&gclsrc=aw.ds) installed and working + - Android SDK 34 >= installed and working (with command line tools) + - Java SDK v17 installed and configured to work with Android Studio + - An Android Emulator downloaded and working \ No newline at end of file diff --git a/versioned_docs/version-1.0/Troubleshooting/_category_.json b/versioned_docs/version-1.0/Troubleshooting/_category_.json new file mode 100644 index 0000000..4224f1c --- /dev/null +++ b/versioned_docs/version-1.0/Troubleshooting/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Troubleshooting", + "position": 16, + "link": { + "type": "generated-index", + "description": "Learn about how to troubleshoot issues between React Native - Native Module and Couchbase Lite." + } +} diff --git a/versioned_docs/version-1.0/Troubleshooting/troubleshoot-crashes.md b/versioned_docs/version-1.0/Troubleshooting/troubleshoot-crashes.md new file mode 100644 index 0000000..da53199 --- /dev/null +++ b/versioned_docs/version-1.0/Troubleshooting/troubleshoot-crashes.md @@ -0,0 +1,6 @@ +--- +id: troubleshoot-crashes +sidebar_position: 3 +--- + +# Troubleshooting Crashes \ No newline at end of file diff --git a/versioned_docs/version-1.0/Troubleshooting/troubleshoot-queries.md b/versioned_docs/version-1.0/Troubleshooting/troubleshoot-queries.md new file mode 100644 index 0000000..4c3a160 --- /dev/null +++ b/versioned_docs/version-1.0/Troubleshooting/troubleshoot-queries.md @@ -0,0 +1,6 @@ +--- +id: troubleshoot-queries +sidebar_position: 2 +--- + +# Troubleshooting Queries \ No newline at end of file diff --git a/versioned_docs/version-1.0/Troubleshooting/using-logs.md b/versioned_docs/version-1.0/Troubleshooting/using-logs.md new file mode 100644 index 0000000..47f0bff --- /dev/null +++ b/versioned_docs/version-1.0/Troubleshooting/using-logs.md @@ -0,0 +1,195 @@ +--- +id: using-logs +sidebar_position: 1 +--- + +# Using Logs for Troubleshooting + +> Description - _Couchbase Lite on React Native - Using Logs for Troubleshooting_ +> Related Content - [Troubleshooting Queries](../Queries/query-troubleshooeting.md) | [Troubleshooting Crashes](troubleshoot-crashes.md) + +:::note +* The retrieval of logs from the device is out of scope of this feature. +::: + +## Introduction + +Couchbase Lite provides a robust Logging API - see: API References for Logging classes - which make debugging and troubleshooting easier during development and in production. It delivers flexibility in terms of how logs are generated and retained, whilst also maintaining the level of logging required by Couchbase Support for investigation of issues. + +Log output is split into the following streams: + +* File based logging + + Here logs are written to separate log files filtered by log level, with each log level supporting individual retention policies. + +* Console based logging + + You can independently configure and control console logs, which provides a convenient method of accessing diagnostic information during debugging scenarios. With console logging, you can fine-tune diagnostic output to suit specific debug scenarios, without interfering with any logging required by Couchbase Support for the investigation of issues. + +* Custom logging + + For greater flexibility you can implement a custom logging class using the ILogger interface. + +## Log Sink API + +Version 1.0 introduces the Log Sink API which provides three types of log sinks for flexible logging control. + +### Log Levels + +| Level | Value | Description | +|-------|-------|-------------| +| LogLevel.DEBUG | 0 | Most verbose - all logs | +| LogLevel.VERBOSE | 1 | Detailed diagnostic logs | +| LogLevel.INFO | 2 | Informational messages | +| LogLevel.WARNING | 3 | Warning messages only | +| LogLevel.ERROR | 4 | Error messages only | +| LogLevel.NONE | 5 | No logging | + +### Log Domains + +| Domain | Description | +|--------|-------------| +| LogDomain.DATABASE | Database operations | +| LogDomain.QUERY | Query execution and planning | +| LogDomain.REPLICATOR | Replication activity | +| LogDomain.NETWORK | Network operations | +| LogDomain.LISTENER | Change listeners | +| LogDomain.ALL | All domains (new in 1.0) | + +## Console Log Sink + +Console based logging outputs logs to the system console (stdout/stderr), useful for development and debugging. + +#### Example 1. Enable Console Logging + +```typescript +import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; + +// Enable verbose logging for all domains +await LogSinks.setConsole({ + level: LogLevel.VERBOSE, + domains: [LogDomain.ALL] +}); +``` + +#### Example 2. Console with Specific Domains + +```typescript +// Log only replication and network activity +await LogSinks.setConsole({ + level: LogLevel.INFO, + domains: [LogDomain.REPLICATOR, LogDomain.NETWORK] +}); +``` + +#### Example 3. Disable Console Logging + +```typescript +// Disable console logging +await LogSinks.setConsole(null); +``` + +## File Log Sink + +File logging writes logs to files on the device with automatic rotation and retention policies. + +#### Example 4. Enable File Logging + +```typescript +import { Platform } from 'react-native'; +import RNFS from 'react-native-fs'; + +// Determine platform-specific log directory +const logDirectory = Platform.OS === 'ios' + ? RNFS.DocumentDirectoryPath + '/logs' + : RNFS.ExternalDirectoryPath + '/logs'; + +await LogSinks.setFile({ + level: LogLevel.INFO, + directory: logDirectory, + maxKeptFiles: 5, // Keep 5 old log files + maxFileSize: 1024 * 1024, // 1MB max file size + usePlaintext: true // Use plaintext format +}); +``` + +:::note File Rotation +When a log file reaches `maxFileSize`, it's closed and a new one is created. Old files exceeding `maxKeptFiles` are automatically deleted. +::: + +#### Example 5. Disable File Logging + +```typescript +await LogSinks.setFile(null); +``` + +## Custom Log Sink + +Custom logging allows you to implement your own logging logic with a callback function. + +#### Example 6. Custom Logging with Callback + +```typescript +await LogSinks.setCustom({ + level: LogLevel.ERROR, + domains: [LogDomain.ALL], + callback: (level, domain, message) => { + const timestamp = new Date().toISOString(); + console.log(`[${timestamp}] [${domain}] ${message}`); + + // You can also send to analytics, log to database, etc. + } +}); +``` + +#### Example 7. Disable Custom Logging + +```typescript +await LogSinks.setCustom(null); +``` + +## Using Multiple Log Sinks + +You can enable multiple log sinks simultaneously for different purposes. + +#### Example 8. Development and Production Configuration + +```typescript +if (__DEV__) { + // Development: Verbose console logging + await LogSinks.setConsole({ + level: LogLevel.VERBOSE, + domains: [LogDomain.ALL] + }); +} else { + // Production: File logging for warnings and errors + await LogSinks.setFile({ + level: LogLevel.WARNING, + directory: logDirectory, + maxKeptFiles: 7, + maxFileSize: 2 * 1024 * 1024, + usePlaintext: true + }); + + // Also send errors to analytics + await LogSinks.setCustom({ + level: LogLevel.ERROR, + domains: [LogDomain.ALL], + callback: (level, domain, message) => { + Analytics.logError({ level, domain, message }); + } + }); +} +``` + +## Platform Considerations + +**iOS:** +- Log files are stored in the app's Documents directory +- Path: `RNFS.DocumentDirectoryPath + '/logs'` +- Accessible via iTunes File Sharing if enabled in Info.plist + +**Android:** +- Log files are stored in the app's external directory +- Path: `RNFS.ExternalDirectoryPath + '/logs'` +- May require storage permissions in AndroidManifest.xml diff --git a/versioned_docs/version-1.0/blobs.md b/versioned_docs/version-1.0/blobs.md new file mode 100644 index 0000000..bb12766 --- /dev/null +++ b/versioned_docs/version-1.0/blobs.md @@ -0,0 +1,115 @@ +--- +id: blobs +sidebar_position: 8 +--- + +# Blobs + +> Description - _Couchbase Lite database data model concepts - blobs_ +> Related Content - [Databases](databases.md) | [Documents](documents.md) | [Indexing](indexes.md) + +## Introduction + +Couchbase Lite for React Native uses blobs to store the contents of images, other media files and similar format files as binary objects. + +The blob itself is not stored in the document. It is held in a separate content-addressable store indexed from the document and retrieved only on-demand. + +When a document is synchronized, the Couchbase Lite replicator adds an `_attachments` dictionary to the document's properties if it contains a blob - see [Figure 1](#figure-1-sample-blob-document). + +## Blob Objects + +The blob as an object appears in a document as dictionary property - see, for example avatar in [Figure 1](#figure-1-sample-blob-document). + +Other properties include `length` (the length in bytes), and optionally `content_type` (typically, its MIME type). + +The blob's data (an image, audio or video content) is not stored in the document, but in a separate content-addressable store, indexed by the `digest` property - see [Using Blobs](#using-blobs). + +### Constraints + +* Couchbase Lite + + Blobs can be arbitrarily large. They are only read on demand, not when you load a the document. + +* Capella App Services/Sync Gateway + + The maximum content size is 20 MB per blob. If a document's blob is over 20 MB, the document will be replicated but not the blob. + +## Using Blobs + +The Blob API lets you access the blob's data content as in-memory data or as a Stream of Uint8Array. + +The code in [Example 1](#example-1-working-with-blobs) shows how you might add a blob to a document and save it to the database. Here we use avatar as the property key and a jpeg file as the blob data. + +#### Example 1. Working with Blobs + +```typescript +// Example function to simulate fetching binary data for an avatar image +const imageData = await fetchAvatarImageData(); + +// Create a Blob instance with the image data and content type +const avatarBlob = new Blob('image/jpeg', imageData); + +// Retrieve an existing document +const document = await collection.document(documentId); + +// Assign the Blob to the document under the 'avatar' key +document.setBlob('avatar', avatarBlob); + +// Save the updated document back to the database +await collection.save(document); +``` + +#### Example 2. Get Blob's content + +```typescript +// code for setting blob +const mutDoc = new MutableDocument('doc1'); +const encoder = new TextEncoder(); +const blobEncoded = new Blob("text/plain", encoder.encode("Hello World")); +mutDoc.setBlob('textBlob', blobEncoded); +await collection.save(mutDoc); + +// code for getting blob's content +const doc = await collection.document('doc1'); +const textDecoder = new TextDecoder(); +const blob = doc.getBlob('textBlob'); +const blobArrayBuffer = blob.getBytes(); +const textBlobResults = textDecoder.decode(blobArrayBuffer); +``` + +## Syncing + +When a document containing a blob object is synchronized, the Couchbase Lite replicator generates an `_attachments` dictionary with an auto-generated name for each blob attachment. This is different to the `avatar` key and is used internally to access the blob content. + +If you view a sync'd blob document in either Capella's Admin Interface or Couchbase Server's Admin Console, you will see something similar to [Figure 1](#figure-1-sample-blob-document), which shows the document with its generated `_attachments` dictionary, including the digest. + +#### Figure 1. Sample Blob Document + +```typescript +{ + "_attachments": { + "blob_1": { + "content_type": "image/jpeg", + "digest": "sha1-F1Tfe61RZP4zC9UYT6JFmLTh2s8=", + "length": 8112955, + "revpos": 2, + "stub": true + } + }, + "avatar": { + "@type": "blob", + "content_type": "image/jpeg", + "digest": "sha1-F1Tfe61RZP4zC9UYT6JFmLTh2s8=", + "length": 8112955 + } +} +``` + + + + + + + + + diff --git a/versioned_docs/version-1.0/database-prebuilt.md b/versioned_docs/version-1.0/database-prebuilt.md new file mode 100644 index 0000000..cc35985 --- /dev/null +++ b/versioned_docs/version-1.0/database-prebuilt.md @@ -0,0 +1,127 @@ +--- +id: database-prebuilt +sidebar_position: 5 +--- + +# Pre-built Database + +> Description - _How to Handle Pre-Built Couchbase Lite Databases in Your App_ +> Abstract - _This content explains how to include a snapshot of a pre-built database in your package to shorten initial sync time and reduce bandwidth use._ + +## Overview + +*Couchbase Lite* supports pre-built databases. You can pre-load your app with data instead of syncing it from Sync Gateway during startup to minimize consumer wait time (arising from data setup) on initial install and launch of the application. + +Avoiding an initial bulk sync reduces startup time and network transfer costs. + +It is typically more efficient to download bulk data using the http/ftp stream employed during the application installation than to install a smaller application bundle and then use a replicator to pull in the bulk data. + +Pre-loaded data is typically public/shared, non-user-specific data that is static. Even if the data is not static, you can still benefit from preloading it and only syncing the changed documents on startup. + +The initial sync of any pre-built database pulls in any content changes on the server that occurred after its incorporation into the app, updating the database. + +## To use a Pre-built Database + +1. Create a new Couchbase Lite database with the required data set - see [Creating Pre-built database](#creating-pre-built-database). + +2. Incorporate the pre-built database with your app bundle as an asset/resource - see [Bundle a Database with an Application](#bundle-a-database-with-an-application). + +3. Adjust the start-up logic of your app to check for the presence of the required database. If the database doesn’t already exist, create one using the bundled pre-built database. Initiate a sync to update the data - see [Using Pre-built Database on App Launch](#using-pre-built-database-on-app-launch). + +## Creating Pre-built database + +These steps should form part of your build and release process: + +1. Create a fresh Couchbase Lite database (every time) + +:::important +Always start with a fresh database for each app version; this ensures there are no checkpoint issues. + +**Otherwise:** You will invalidate the cached checkpoint in the packaged database, and instead reuse the same database in your build process (for subsequent app versions). +::: + +2. Pull the data from Sync Gateway into the new Couchbase Lite database + +:::important +Ensure the replication used to populate Couchbase Lite database uses the exact same remote URL and replication config parameters (channels and filters) as those your app will use when it is running. + +**Otherwise:** …​ there will be a checkpoint mismatch and the app will attempt to pull the data down again + +Don’t, for instance, create a pre-built database against a staging Sync Gateway server and use it within a production app that syncs against a production Sync Gateway. +::: + +You can use the cblite tool (cblite cp) for this - see: [cblite cp (export, import, push, pull)](https://github.com/couchbaselabs/couchbase-mobile-tools/blob/master/Documentation.md#cp-aka-export-import-push-pull) on GitHub. + + +3. Create the same indexes the app will use (wait for the replication to finish before doing this). + +## Bundle a database with an application + +Copy the database into your app package. + +Put it in an appropriate place (for example, an assets or resource folder). + +Where the platform permits you can zip the database. + +**Alternatively:**​ rather than bundling the database within the app, the app could pull the database down from a CDN server on launch. + +## Database Encryption + +If you are using en encrypted database, note that Database.copy does not change the encryption key. The encryption key specified in the config when opening the database is the encryption key used for both the original database and copied database. + +If you copied an un-encrypted database and want to apply encryption to the copy, or if you want to change (or remove) the encryption key applied to the copy: + +1. Provide the original encryption-key (if any) in the database copy's configuration using DatabaseConfiguration.getEncryptionKey(). + +2. Open the database copy + +3. Use Database.setEncryptionKey() on the database copy to set the required encryption key. + +:::tip +To remove encryption on the copy, provide a null encryption-key. +::: + +## Using Pre-built Database on App Launch + +1. Locate the pre-packaged database (for example, in the assets or other resource folder) + +2. Copy the pre-packaged database to the required location + +Use the API's Database.copy method - see: [Example 1](#example-1-decompress-and-copy-database-using-api). This ensures that a unique UUID is generated for each copy. + +:::important +Do not copy the database using any other method. + +**Otherwise:** Each copy of the app will invalidate the other apps' checkpoints because a new UUID was not generated. +::: + +3. Open the database; you can now start querying the data and using it. + +4. Start a pull replication, to sync any changes. + +The replicator uses the pre-built database's checkpoint as the timestamp to sync from; only documents changed since then are synced. + +:::note +Start your normal application logic immediately, unless it is essential to have the absolute up-to-date data set to begin. That way the user is not kept hanging around watching a progress indicator. They can begin interacting with your app whilst any out-of-data data is being updated. +::: + +#### Example 1. Decompress and Copy Database using API + +```typescript +// Get the default path +const path = await pd.getDefaultPath(); + +// Set database name, source path, and configuration +const dbName = 'my_prebuilt_db'; +const sourcePath = await database.getPath(); +const config = new DatabaseConfiguration(); +config.setDirectory(path); + +const databaseExists = await database.exists(dbName, sourcePath); +if (!databaseExists) { + // Copy the database from the sourcePath to the app's directory if it doesn't already exist + await database.copy(sourcePath, dbName, config); +} +``` + + diff --git a/versioned_docs/version-1.0/databases.md b/versioned_docs/version-1.0/databases.md new file mode 100644 index 0000000..9622c4b --- /dev/null +++ b/versioned_docs/version-1.0/databases.md @@ -0,0 +1,250 @@ +--- +id: databases +sidebar_position: 4 +--- + +# Databases + +> Description - _Working with Couchbase Lite Databases_ +> Related Content - [Blobs](blobs.md) | [Documents](documents.md) | [Indexing](indexes.md) + + +## Database Concepts + +Databases created on Couchbase Lite can share the same hierarchical structure as Capella databases. This makes it easier to sync data between mobile applications and applications built using Capella. + +
+ +![Couchbase Lite Database Hierarchy](/img/Couchbase_Lite_Database_Hierarchy.svg) + +_Figure 1. Couchbase Lite Database Hierarchy_ + +
+ +Although the terminology is different, the structure can be mapped to relational database terms: + +Table 1. Relational Database → Couchbase + +| Relational database | Couchbase | +|---------------------|---------------------| +| Database | Database | +| Schema | Scope | +| Table | Collection | + +This structure gives you plenty of choices when it comes to partitioning your data. The most basic structure is to use the single default scope with a single default collection; or you could opt for a structure that allow you to split your collections into logical scopes. + + +
+ +![Couchbase Lite Examples](/img/Couchbase_Lite_Examples.svg) + +_Figure 2. Couchbase Lite Examples_ + +
+ +### Storing local configuration + +You may not need to sync all the data related to a particular application. You can set up a scope that syncs data, and a second scope that doesn’t. One reason for doing this is to store local configuration data (such as the preferred screen orientation or keyboard layout). Since this information only relates to a particular device, there is no need to sync it: + +- **local data scope** - Contains information pertaining to the device. +- **syncing data scope** - Contains information pertaining to the user, which can be synced back to the cloud for use on the web or another device. + +## Managing Couchbase Lite Databases in React Native + +### Initializing the Environment + +In a React Native application using Couchbase Lite, begin by initializing the React Native Engine. Subsequently, employ a design pattern such as Context/Provider or Service Locator to maintain and access your database instances throughout the application lifecycle. + +**Example: Initializing React Native Engine and Database Context** +```typescript +import { CblReactNativeEngine } from 'cbl-reactnative'; + +const engine = new CblReactNativeEngine(); // Initialize once, early in your app +``` + +This configuration ensures seamless interaction between your React Native app and the underlying native database functionalities, facilitating effective database management. + +### Create or Open a Database + +To create or open a database, use the Database class from the cbl-reactnative package, specifying the database name and optionally, a DatabaseConfiguration for custom settings like the database directory or encryption. + +**Example 1. Creating/Opening a Database** + +```javascript +import { Database, DatabaseConfiguration } from 'cbl-reactnative'; //import the package +``` + +```javascript +const config = new DatabaseConfiguration(); +config.setDirectory('path/to/database'); // Optional +const myDatabase = new Database('myDatabaseName', config); +await myDatabase.open(); +``` + +### Closing a Database + +You are advised to incorporate the closing of all open databases into your application workflow. + +**Example 2. Closing a Database** + +```javascript +await myDatabase.close(); +``` + +### Deleting a Database + +The delete method is used to delete a database. + +```typescript + await database.deleteDatabase(); +``` + +Alternatively, you can delete a database by calling the delete method on the Database class. + +```typescript +Database.deleteDatabase('myDatabaseName', 'path/to/database'); +``` + +## Database Encryption + +Couchbase Lite includes the ability to encrypt Couchbase Lite databases. This allows mobile applications to secure data at rest, when it is being stored on the device. The algorithm used to encrypt the database is 256-bit AES. + +### Enabling + +To enable database encryption in React Native, use the `DatabaseConfiguration` class to set an encryption key before opening or creating a database. This encryption key must be provided every time the database is accessed. + +**Example3. Configure Database Encryption** + +```typescript +const dbName = 'my_secure_db'; +const encryptionKey = 'my_secret_key'; + +const config = new DatabaseConfiguration(); +config.setEncryptionKey(encryptionKey); + +const db = new Database(dbName, config); + +await db.open(); +``` + +### Persisting + +Couchbase Lite does not persist the key. It is the application’s responsibility to manage the key and store it in a platform-specific secure store such as Apples's [Keystore](https://developer.apple.com/documentation/security/keychain_services) or Android’s [Keystore](https://developer.android.com/privacy-and-security/keystore). + +### Opening + +An encrypted database can only be opened with the same language package that was used to encrypt it in the first place. So a database encrypted using the React Native package, and then exported, is readable only by the React Native package. + +## Database Maintenance + +From time to time it may be necessary to perform certain maintenance activities on your database, for example to compact the database file, removing unused documents and blobs no longer referenced by any documents. + +Couchbase Lite's API provides the Database.performMaintenance method. The available maintenance operations, including compact are as shown in the enum MaintenanceType to accomplish this. + +```typescript +const dbName = 'my_secure_db'; +const config = new DatabaseConfiguration(); +const db = new Database(dbName, config); +await db.open(); +await db.performMaintenance(MaintenanceType.compact); +``` + +This is a resource intensive operation and is not performed automatically. It should be run on-demand using the API. A full listing of the available maintenance operations is shown below: + +- **MaintenanceType.compact**: Compact the database file and delete unused attachments. +- **MaintenanceType.reindex**: (Volatile API) Rebuild the entire database’s indexes. +- **MaintenanceType.integrityCheck**: (Volatile API) Check for the database’s corruption. If found, an error will be returned. +- **MaintenanceType.optimize**: Quickly updates database statistics that may help optimize queries that have been run by this Database since it was opened +- **MaintenanceType.fullOptimize**: Fully scans all indexes to gather database statistics that help optimize queries. + +For questions or issues, please visit the [Couchbase Forums](https://www.couchbase.com/forums/) where you can ask for help and discuss with the community. + +## Couchbase Lite for VSCode + +Couchbase Lite for VSCode is a Visual Studio Code extension that provides a user interface for inspecting and querying Couchbase Lite databases. You can find more information about this extension from it's [GitHub repository](https://github.com/couchbaselabs/vscode-cblite). + +## Couchbase Lite for JetBrains + +Couchbase Lite for JetBrains is a JetBrains IDE plugin that provides a user interface for inspecting and querying Couchbase Lite databases. You can find more information about this plugin from its [GitHub repository](https://github.com/couchbaselabs/couchbase_jetbrains_plugin). + +## Command Line Tool + +cblite is a command-line tool for inspecting and querying Couchbase Lite databases. + +You can download and build it from the couchbaselabs [GitHub repository](https://github.com/couchbaselabs/couchbase-mobile-tools/blob/master/README.cblite.md). Note that the cblite tool is not supported by the [Couchbase Support Policy](https://www.couchbase.com/support-policy/). + + +## Troubleshooting + +You should use console logs as your first source of diagnostic information. If the default logging level is insufficient, you can enable verbose logging scoped specifically to database operations. + +```typescript +import { LogSinks, LogLevel, LogDomain } from 'cbl-reactnative'; + +try { + await LogSinks.setConsole({ + level: LogLevel.VERBOSE, + domains: [LogDomain.DATABASE] + }); + console.log('Database logging enabled.'); +} catch (error) { + console.error('Setting log level failed:', error); +} +``` + +:::caution +Enable VERBOSE logging only during development or debugging. Avoid using it in production builds. +::: + +For more on using logs for troubleshooting, see [Using Logs](Troubleshooting/using-logs.md). + +## Listener Token API + +Version 1.0 introduces the `ListenerToken` class for improved listener management across all change listener types. + +All `addChangeListener` methods return a `ListenerToken` object with these methods: + +**remove()** - Removes the listener (recommended) + +```typescript +const token = await collection.addChangeListener(...); +await token.remove(); +``` + +**getUuidToken()** - Gets the internal UUID string (for debugging) + +```typescript +const uuid = token.getUuidToken(); +console.log('Token:', uuid); // "listener-abc-123-def-456" +``` + +**isRemoved()** - Checks if listener was already removed + +```typescript +if (!token.isRemoved()) { + await token.remove(); +} +``` + +:::caution Deprecated Methods +The `removeChangeListener()` methods on Collection, Query, and Replicator are deprecated. This method is deprecated. It remains available for backward compatibility, but new applications should use `token.remove()`. Existing applications are strongly encouraged to migrate. + +```typescript +// DEPRECATED +await collection.removeChangeListener(token); +await query.removeChangeListener(token); +await replicator.removeChangeListener(token); + +// RECOMMENDED +await token.remove(); +``` +::: + + + + + + + + + diff --git a/versioned_docs/version-1.0/documents.md b/versioned_docs/version-1.0/documents.md new file mode 100644 index 0000000..b335026 --- /dev/null +++ b/versioned_docs/version-1.0/documents.md @@ -0,0 +1,486 @@ +--- +id: documents +sidebar_position: 7 +--- + +# Documents + +> Description - _Couchbase Lite Concepts - Data Model - Documents_ +> Related Content - [Databases](databases.md) | [Blobs](blobs.md) | [Indexing](indexing.md) + +## Overview + +### Document Structure + +In Couchbase Lite the term 'document' refers to an entry in the database. You can compare it to a record, or a row in a table. + +Each document has an ID or unique identifier. This ID is similar to a primary key in other databases. + +You can specify the ID programmatically. If you omit it, it will be automatically generated as a UUID. + +:::note +Couchbase documents are assigned to a Collection. The ID of a document must be unique within the Collection it is written to. You cannot change it after you have written the document. +::: + +The document also has a value which contains the actual application data. This value is stored as a dictionary of key-value (k-v) pairs. The values can be made of up several different Data Types such as numbers, strings, arrays, and nested objects. + +### Data Encoding + +The document body is stored in an internal, efficient, binary form called [Fleece](https://github.com/couchbase/fleece#readme). This internal form can be easily converted into a manageable native dictionary format for manipulation in applications. + +Fleece data is stored in the smallest format that will hold the value whilst maintaining the integrity of the value. + +### Data Types + +The Document class offers a set of property accessors for various scalar types, such as: + +* Boolean + +* Date + +* Double + +* Float + +* Int + +* Long + +* String + +These accessors take care of converting to/from JSON encoding, and make sure you get the type you expect. + +In addition to these basic data types Couchbase Lite provides for the following: + +* **Dictionary** - Represents a read-only key-value pair collection +* **MutableDictionary** - Represents a writeable key-value pair collection. +* **Array** - Represents a writeable collection of objects. +* **MutableArray** - Represents a writeable collection of objects. +* **Blob** - Represents an arbitrary piece of binary data. + +### JSON + +Couchbase Lite also provides for the direct handling of JSON data implemented in most cases by the provision of a ToJSON() method on appropriate API classes (for example, on MutableDocument, Dictionary, Blob and Array) - see Working with JSON Data. + +## Constructing a Document + +An individual document often represents a single instance of an object in application code. A document might be considered equivalent to a row in a relational table; with each of the document's attributes being equivalent to a column. + +Documents can contain nested structures. This allows developers to express many-to-many relationships without requiring a reference or junction table; and is naturally expressive of hierarchical data. + +Most apps will work with one or more documents, persisting them to a local database and optionally syncing them, either centrally or to the cloud. + +In this section we provide an example of how you might create a hotel document, which provides basic contact details and price data. + +### Data Model + +```typescript +hotel: { + type: string (value = `hotel`) + name: string + address: dictionary { + street: string + city: string + state: string + country: string + code: string + } + phones: array + rate: float +} +``` + +### Open a Database + +First open your database. If the database does not already exist, Couchbase Lite will create it for you. + +```typescript +const myDatabase = new Database('myDatabaseName', config); +``` +See [Databases](https://cbl-reactnative.dev/databases) for more information. + +### Create a Document + +```typescript +let document = new MutableDocument(hotel.id); +``` + + +For more on using Documents, see Document Initializers and Mutability. + +### Create a Dictionary + +Now create a mutable dictionary (address). + +Each element of the dictionary value will be directly accessible via its own key. + +```typescript +// Create a new MutableDocument instance +const document = new MutableDocument(); + +// Now, populate the document as if it's a dictionary named 'address' +document.setString('address.street', '1 Main st.'); +document.setString('address.city', 'San Francisco'); +document.setString('address.state', 'CA'); +document.setString('address.country', 'USA'); +document.setString('address.code', '90210'); +``` + +### Create an Array + +Since the hotel may have multiple contact numbers, provide a field (phones) as a mutable array. + +```typescript +// Create an instance of MutableDocument +const hotelInfo = new MutableDocument(); + +// Since `setArray` method accepts an array, directly pass the contact numbers as an array +hotelInfo.setArray("phones", ["650-000-0000", "650-000-0001"]); +``` + +### Populate a Document + +Now add your data to the mutable document created earlier. Each data item is stored as a key-value pair. + +```typescript +// Assuming address and phones are already defined as shown in previous examples +const address = { + street: "1 Main st.", + city: "San Francisco", + state: "CA", + country: "USA", + code: "90210" +}; + +const phones = ["650-000-0000", "650-000-0001"]; + +// Create an instance of MutableDocument +let hotelInfo = new MutableDocument(); + +// Add document type and hotel name as string +hotelInfo.setString("type", "hotel"); +hotelInfo.setString("name", "Hotel Java Mo"); + +// Add average room rate (float) +hotelInfo.setFloat("room_rate", 121.75); + +// Add address (dictionary) +hotelInfo.setDictionary("address", address); + +// Add phone numbers (array) +hotelInfo.setArray("phones", phones); +``` + +:::note +Couchbase recommend using a type attribute to define each logical document type. +::: + +### Save a Document + +With the document now populated, we can persist to our Couchbase Lite database. + +```typescript +await collection.save(document); +``` + +### Close the Database + +With your document saved, you can now close our Couchbase Lite database. + +```typescript +database.close(); +``` + +## Working with Data + +### Date accessors + +Couchbase Lite offers Date accessors as a convenience. Dates are a common data type, but JSON doesn’t natively support them, so the convention is to store them as strings in ISO-8601 format. + +#### Example 1. Date Getter + +This example sets the date on the createdAt property and reads it back using the Document.getDate() accessor method. + +```typescript +// Create an instance of MutableDocument +let document = new MutableDocument(); + +// Set the current date on the "createdAt" property +document.setDate("createdAt", new Date()); + +// Get the Date +const date = document.getDate("createdAt"); +``` + +### Using Dictionaries + +#### Example 2. Read Only + +```typescript +// Get a document by ID +const doc: Document = collection.document('doc1'); + +// Getting a dictionary from the document's properties +const addressDict = doc.getDictionary('address'); + +// Access a value with a key from the dictionary +const street = addressDict?.getString('street'); + +// Iterate over the dictionary +Object.keys(addressDict || {}).forEach(key => { + console.log(`Key ${key} = ${addressDict[key]}`); +}); + +// Create a mutable copy +const mutableDict = { ...addressDict }; +``` + +#### Example 3. Mutable + +```typescript +// Create a new "dictionary" as a simple JavaScript object +const addressDict = { + street: "1 Main st.", + city: "San Francisco", +}; + +// Create a new mutable document and add the dictionary to its properties +const mutableDoc = new MutableDocument("doc1"); +mutableDoc.setDictionary("address", addressDict); + +// Simulate saving the document +await collection.save(mutableDoc); +``` + +### Using Arrays + +#### Example 4. Read Only + +```typescript +// Getting a phones array from the document's properties +const phonesArray = document.data.phones; + +// Get element count +const count = phonesArray.length; + +// Access an array element by index +if (count >= 1) { + const phone = phonesArray[1]; + console.log(`Second phone: ${phone}`); +} + +// Iterate over the array +phonesArray.forEach((item, index) => { + console.log(`Item ${index} = ${item}`); +}); + +// Create a mutable copy of the array +const mutableArray = [...phonesArray]; +``` + +#### Example 5. Mutable + +```typescript +// Create a new mutable document +const document = new MutableDocument(); + +// Prepare the data for the array +const phones = ["650-000-0000", "650-000-0001"]; + +// Assign the array to a key in the document +doc.setArray("phones", phones); + +// Save the document with the new array to the database +await collection.save(doc); +``` + +### Using Blobs + +For more on working with blobs, see [Blobs](blobs.md) + +## Document Initializers + +The `MutableDocument` constructor can be used to create a new document where the document ID is randomly generated by the database. + +Use the `MutableDocument('specific_id')` initializer to create a new document with a specific ID. + +The `Collection.document` method can be used to get a document. If it doesn't exist in the collection, it will return null. This method can be used to check if a document with a given ID already exists in the collection. + +### Convert Mutable Document from Document + +The `MutableDocument` class has a static function `fromDocument` that takes a `Document` as a parameter. This allows you to create a mutable copy of an existing document so you can modify it and then save it to a collection. + +```typescript +const doc: Document = collection.document('doc1'); +const mutableDoc = MutableDocument.fromDocument(doc); +mutableDoc.setString('name', 'New Name'); +await collection.save(mutableDoc); +``` + +#### Example 6. Persist a document + +The following code example creates a document and persists it to the database. + +```typescript +// Create a new MutableDocument instance +const document = new MutableDocument(); + +// Set various fields on the document +document.setString('type', 'task'); +document.setString('owner', 'todo'); +document.setDate('createdAt', new Date()); + +// Persist the document to the database +await collection.save(document); +``` + +## Document Change Listeners + +Monitor changes to a specific document by ID. Couchbase Lite allows you to register listeners to be notified when documents change. Version 1.0 introduces the `ListenerToken` API for better listener lifecycle management. + +### When to Use + +- Watch a user profile for updates +- Monitor a shopping cart +- Track order status changes +- Real-time document collaboration + +### Data Structure + +```typescript +interface DocumentChange { + documentId: string; // Document ID (lowercase Id) + collection: Collection; // Collection reference + database: Database; // Database reference (available!) +} +``` + +:::note +Unlike collection listeners, document change listeners DO have a direct `database` property. +::: + +#### Example 7. Monitor Specific Document + +```typescript +import { ListenerToken } from 'cbl-reactnative'; + +const token: ListenerToken = await collection.addDocumentChangeListener( + 'user-123', + async (change) => { + console.log(`Document ${change.documentId} changed`); + + // Fetch latest version + const doc = await collection.document(change.documentId); + if (doc) { + console.log('New data:', doc.getData()); + } else { + console.log('Document was deleted'); + } + } +); + +// Remove when done +await token.remove(); +``` + +#### Example 8. Multiple Document Listeners + +```typescript +// Monitor multiple specific documents +const userToken = await collection.addDocumentChangeListener('user-123', (change) => { + console.log('User changed'); +}); + +const cartToken = await collection.addDocumentChangeListener('cart-abc', (change) => { + console.log('Cart changed'); +}); + +// Cleanup all +await Promise.all([userToken.remove(), cartToken.remove()]); +``` + +:::tip New in Version 1.0 +Change listeners now return a `ListenerToken` object. Use `token.remove()` to remove the listener. +::: + +:::caution Deprecated +The `collection.removeDocumentChangeListener(token)` method is deprecated. It remains available for backward compatibility, but new applications should use `token.remove()`. Existing applications are strongly encouraged to migrate. +::: + +### Common Pitfalls + +:::caution Property Name +```typescript +change.documentId // CORRECT (lowercase Id) +change.documentID // WRONG - will be undefined +``` + +Getting this property name wrong is a common source of errors. +::: + +**Async Handling in Callbacks** + +```typescript +// WRONG - promise not awaited +const token = await collection.addDocumentChangeListener('doc-id', (change) => { + collection.document(change.documentId); // Returns promise, not awaited! +}); + +// CORRECT - proper async handling +const token = await collection.addDocumentChangeListener('doc-id', async (change) => { + const doc = await collection.document(change.documentId); + console.log('Document:', doc); +}); +``` + +## Document Expiration + +Document expiration allows users to set the expiration date for a document. When the document expires, it is purged from the database. The purge is not replicated to Sync Gateway or Capella App Services. + +```typescript +const expirationDate = new Date('2024-12-31T23:59:59'); +await collection.setDocumentExpiration('doc123', expirationDate); +``` + +## Purge a Document + +Documents can be purged from the local database using the `purge` method on the collection they are stored in. + +```typescript +const doc = collection.document('doc1'); +await collection.purge(doc); +``` +You can also purge a document by it's ID. + +```typescript +await collection.purgeById('doc1'); +``` + +Collection purges are not replicated. + +## Delete a Document + +Documents can be deleted using the `delete` method on the collection they are stored in. + +```typescript +const doc = collection.document('doc1'); +await collection.delete(doc); +``` +Note that document deletion are replicated to Sync Gateway or Capella App Services. + +## Document Constraints + +Couchbase Lite APIs do not explicitly disallow the use of attributes with the underscore prefix at the top level of document. This is to facilitate the creation of documents for use either in local only mode where documents are not synced. + +:::note +`_id`, `_rev` and `_sequence` are reserved keywords and must not be used as top-level attributes - see [Example 13](#example-13-reserved-keys-list). +::: + +#### Example 13. Reserved Keys List + +* `_attachments` +* `_deleted` +* `_id` +* `_removed` +* `_rev` +* `_sequence` \ No newline at end of file diff --git a/versioned_docs/version-1.0/full-text-search.md b/versioned_docs/version-1.0/full-text-search.md new file mode 100644 index 0000000..d341ab0 --- /dev/null +++ b/versioned_docs/version-1.0/full-text-search.md @@ -0,0 +1,162 @@ +--- +id: full-text-search +sidebar_position: 10 +--- + +# Using Full Text Search + +> Description - _Couchbase Lite database data querying concepts - full text search_ +> Related Content - [Indexing](indexes.md) + +## Overview + +To run a full-text search (FTS) query, you must create a full-text index on the expression being matched. Unlike regular queries, the index is not optional. + +The following examples use the data model introduced in Indexing. They create and use an FTS index built from the hotel’s `Overview` text. + +## SQL++ + +### Create Index + +SQL++ provides a configuration object to define Full Text Search indexes using `FullTextIndexItem` and `IndexBuilder`. + +#### Using SQL++'s FullTextIndexItem and IndexBuilder + +```typescript +const indexProperty = FullTextIndexItem.property('overview'); +const index = IndexBuilder.fullTextIndex(indexProperty).setIgnoreAccents(false); +await collection.createIndex('overviewFTSIndex', index); +``` + +### Use Index + +FullTextSearch is enabled using the SQL++ match() function. + +With the index created, you can construct and run a Full-text search (FTS) query using the indexed properties. + +The index will omit a set of common words, to avoid words like "I", "the", "an" from overly influencing your queries. See [full list of these stopwords](https://github.com/couchbasedeps/sqlite3-unicodesn/blob/HEAD/stopwords_en.h). + +The following example finds all hotels mentioning *Michigan* in their *Overview* text. + +#### Example 2. Using SQL++ Full Text Search + +```typescript +const ftsQueryString = ` + SELECT _id, overview + FROM _default + WHERE MATCH(overviewFTSIndex, 'michigan') + ORDER BY RANK(overviewFTSIndex) +`; +const ftsQuery = database.createQuery(ftsQueryString); + +const results = await ftsQuery.execute(); + +for(const result of results) { + console.log(result.getString('_id') + ": " + result.getString('overview')); +} +``` + +## Operation + +In the examples above, the pattern to match is a word, the full-text search query matches all documents that contain the word "michigan" in the value of the `doc.overview` property. + +Search is supported for all languages that use whitespace to separate words. + +Stemming, which is the process of fuzzy matching parts of speech, like "fast" and "faster", is supported in the following languages: Danish, Dutch, English, Finnish, French, German, Hungarian, Italian, Norwegian, Portuguese, Romanian, Russian, Spanish, Swedish and Turkish. + +## Pattern Matching Formats + +As well as providing specific words or strings to match against, you can provide the pattern to match in these formats. + +### Prefix Queries + +The query expression used to search for a term prefix is the prefix itself with a "*" character appended to it. + +#### Example 5. Prefix query + +Query for all documents containing a term with the prefix "lin". + +``` +"lin*" +``` + +This will match + + * All documents that contain "linux" + * And …​ those that contain terms "linear","linker", "linguistic" and so on. + +### Overriding the Property Name + +Normally, a token or token prefix query is matched against the document property specified as the left-hand side of the `match` operator. This may be overridden by specifying a property name followed by a ":" character before a basic term query. There may be space between the ":" and the term to query for, but not between the property name and the ":" character. + +#### Example 6. Override indexed property name + +Query the database for documents for which the term "linux" appears in the document title, and the term "problems" appears in either the title or body of the document. + +``` +'title:linux problems' +``` + +### Phrase Queries + +A `phrase query`is one that retrieves all documents containing a nominated set of terms or term prefixes in a specified order with no intervening tokens. + +Phrase queries are specified by enclosing a space separated sequence of terms or term prefixes in double quotes ("). + +#### Example 7. Phrase query + +Query for all documents that contain the phrase "linux applications". + +``` +"linux applications" +``` + +### NEAR Queries + +Search for a document that contains the phrase "replication" and the term "database" with not more than 2 terms separating the two. + +``` +"database NEAR/2 replication" +``` + +### AND, OR & NOT Query Operators:: + +The enhanced query syntax supports the AND, OR and NOT binary set operators. Each of the two operands to an operator may be a basic FTS query, or the result of another AND, OR or NOT set operation. Operators must be entered using capital letters. Otherwise, they are interpreted as basic term queries instead of set operators. + +#### Example 9. Using And, Or and Not + +Return the set of documents that contain the term "couchbase", and the term "database". + +``` +"couchbase AND database" +``` + +### Operator Precedence + +When using the enhanced query syntax, parenthesis may be used to specify the precedence of the various operators. + +#### Example 10. Operator precedence + +Query for the set of documents that contains the term "linux", and at least one of the phrases "couchbase database" and "sqlite library". + +``` +'("couchbase database" OR "sqlite library") AND "linux"' +``` + +## Ordering Results + +It’s very common to sort full-text results in descending order of relevance. This can be a very difficult heuristic to define, but Couchbase Lite comes with a ranking function you can use. + +In the `OrderBy` array, use a string of the form `Rank(X)`, where `X` is the property or expression being searched, to represent the ranking of the result. + + + + + + + + + + + + diff --git a/versioned_docs/version-1.0/indexes.md b/versioned_docs/version-1.0/indexes.md new file mode 100644 index 0000000..1d7330e --- /dev/null +++ b/versioned_docs/version-1.0/indexes.md @@ -0,0 +1,62 @@ +--- +id: indexes +sidebar_position: 11 +--- + +# Indexing + +> Description - _Couchbase mobile database indexes and indexing concepts_ +> Related Content - [Databases](databases.md) | [Documents](documents.md) | [Indexing](indexes.md) + +## Introduction + +Before we begin querying documents, let's briefly mention the importance of having an appropriate and balanced approach to indexes. + +Creating indexes can speed up the performance of queries. A query will typically return results more quickly if it can take advantage of an existing database index to search, narrowing down the set of documents to be examined. + +:::note CONSTRAINTS +Couchbase Lite does not currently support partial value indexes; indexes with non-property expressions. You should only index with properties that you plan to use in the query. +::: + +## Creating a new index + +You can use SQL++ to create an index + +[Example 2](#example-2-create-index) creates a new index for the type and name properties, shown in this data model: + +**Example 1. Data Model** + +```json +{ + "_id": "hotel123", + "type": "hotel", + "name": "The Michigander", + "overview": "Ideally situated for exploration of the Motor City and the wider state of Michigan. Tripadvisor rated the hotel ...", + "state": "Michigan" +} +``` + +The code to create the index will look something like this: + +#### Example 2. Create Index + +```typescript + // Define a value index on 'name' and 'documentType' +const valueIndex = IndexBuilder.valueIndex( + ValueIndexItem.property('name'), + ValueIndexItem.property('documentType') +); + +// Create the value index +const valueIndexName = 'nameTypeIndex'; +await collection.createIndex(valueIndexName, valueIndex); +``` + +## Summary + +When planning the indexes you need for your database, remember that while indexes make queries faster, they may also: + +* Make writes slightly slower, because each index must be updated whenever a document is updated. +* Make your Couchbase Lite database slightly larger. + +So too many indexes may hurt performance. Optimal performance depends on designing and creating the right indexes to go along with your queries. \ No newline at end of file diff --git a/versioned_docs/version-1.0/intro.md b/versioned_docs/version-1.0/intro.md new file mode 100644 index 0000000..4d12f20 --- /dev/null +++ b/versioned_docs/version-1.0/intro.md @@ -0,0 +1,61 @@ +--- +slug: / +sidebar_position: 1 +sidebar_label: Overview +id: intro +title: Overview +description: + Couchbase Lite is an embedded, document-style NoSQL database that is syncable and makes it easy to build offline-enabled applications. +--- + +# Couchbase Lite for React Native + +Couchbase Lite is an embedded, document-style NoSQL database that is syncable and makes it easy to build offline-enabled applications. + +Couchbase Lite for React Native is a Native Module implementation of Couchbase Lite for React Native using Typescript. It has feature parity with Couchbase Lite implementations for other platforms, with a few exceptions. + +More information on React Native - Native Modules can be found here: [React Native Docs](https://reactnative.dev/docs/legacy/native-modules-intro) + +:::note +This plugin only works with iOS and Android platforms. Web, Windows, and MacOS support is not available. +::: + +The version of this Native Module is based on supporting Couchbase Lite Enterprise for iOS and Android. A [license](https://www.couchbase.com/pricing/) is required to use Couchbase Lite Enterprise edition. + +## Features +* Offline First +* Documents + - Schemaless + - Stored in efficient binary format +* Blobs + - Store and sync binary data, for example JPGs or PDFs +* Queries + - SQL++ Query Language + - Extension of familiar SQL for JSON-like data + - Many built-in functions + - Full-Text Search + - Indexing +* Data Sync + - [Couchbase Capella App Servcies](https://www.couchbase.com/products/capella) - Sync your data from your mobile app to the Cloud + - Remote On-Premise via [Sync Gateway](https://www.couchbase.com/products/sync-gateway) +* Change Notifications for + - Documents + - Collections + - Queries + - Replication +* Encryption + - Full Database + +## Upgrading from 0.6.x? + +See the [Migration Guide](Guides/Migration/v1.md) for detailed instructions on upgrading to version 1.0. + + +## Limitations +Some of the features supported by other platform implementations of Couchbase Lite are currently not supported: + +* Vector Search + - This is still in beta for the native platforms and is not yet supported in the plugin. + +* Peer-to-Peer Sync + - There is no "platform" specific code built into the plugin to allow you to find other peers. \ No newline at end of file diff --git a/versioned_docs/version-1.0/scopes-collections.md b/versioned_docs/version-1.0/scopes-collections.md new file mode 100644 index 0000000..9b84e6e --- /dev/null +++ b/versioned_docs/version-1.0/scopes-collections.md @@ -0,0 +1,216 @@ +--- +id: scopes-collections +sidebar_position: 6 +--- + +# Scopes and Collections + +> Description - _Scopes and collections allow you to organize your documents within a database._ + +:::important AT A GLANCE +**Use collections to organize your content in a database** + +For example, if your database contains travel information, airport documents can be assigned to an airports collection, hotel documents can be assigned to a hotels collection, and so on. + +* Document names must be unique within their collection. + +**Use scopes to group multiple collections** + +Collections can be assigned to different scopes according to content-type or deployment-phase (for example, test versus production). + +* Collection names must be unique within their scope. + +Unlike Couchbase Server and Capella, a scope cannot exist without a collection, thus there is no API to create a scope. Instead, use the collection API to create a collection in a scope and the scope is created from that API. +::: + +## Default Scopes and Collections + +Every database you create contains a default scope and a default collection named `_default`. + +If you create a document in the database and don't specify a specific scope or collection, it is saved in the default collection, in the default scope. + +If you upgrade from a version of Couchbase Lite prior to support for scops and collections, all existing data is automatically placed in the default scope and default collection. + +The default scope and collection cannot be dropped. + +## Create a Scope and Collection + +In addition to the default scope and collection, you can create your own scope and collection when you create a document. + +Naming conventions for collections and scopes: + +* Must be between 1 and 251 characters in length. +* Can only contain the characters `A-Z`, `a-z`, `0-9`, and the symbols `_`, `-`, and `%`. +* Cannot start with `_` or `%`. +* Scope names must be unique in databases. +* Collection names must be unique within a scope. + +:::note +Scope and collection names are case sensitive. +::: + +**Example 1. Create a scope and collection** + +```typescript +const scopeName = "myScopeName"; +const collectionName = "myCollectionName"; +const collection = await database.createCollection(collectionName, scopeName); +``` + +In the example above, you can see that `Database.createCollection` can take two parameters. The second is the scope assigned to the created collection, if this parameter is omitted then a collection of the given name will be assigned to the `_default` scope. + +The first parameter is the name of the collection you want to create, in this case `myCollectionName`. + +If a collection with the specified name already exists in the specified scope, `Database.createCollection` returns the existing collection. + +:::note +You cannot create an empty user-defined scope. A scope is implicitly created and removed by the `Database.createCollection` and `Database.deleteCollection` methods. +::: + + +## Index a Collection + +**Example 2. Index a Collection** + +```typescript + // Define a value index on 'name' and 'documentType' +const valueIndex = IndexBuilder.valueIndex( + ValueIndexItem.property('name'), + ValueIndexItem.property('documentType') +); + +// Create the value index +const valueIndexName = 'nameTypeIndex'; +await collection.createIndex(valueIndexName, valueIndex); +``` + +## Drop a Collection + +**Example 3. Drop a Collection** + +```typescript +await database.deleteCollection(collectionName, scopeName); +``` + +## List Scopes and Collections + +**Example 4. List Scopes and Collections** + +```typescript +// Get Scopes +const scopes = await database.scopes(); + +// Get Collections of a particular Scope +const collections = await database.collections(scopeName); +``` + +## Get Collection Full Name + +The `fullName()` method returns the fully qualified name of a collection in the format "scope.collection". + +The full name is useful for logging, debugging, and displaying collection information in your application. + +**Example 5. Get Collection Full Name** + +```typescript +// User-defined collection +const collection = await database.createCollection('users', 'production'); +const fullName = await collection.fullName(); +console.log(fullName); +// Output: "production.users" + +// Default collection +const defaultCol = await database.defaultCollection(); +const defaultName = await defaultCol.fullName(); +console.log(defaultName); +// Output: "_default._default" +``` + +## Collection Change Listeners + +Monitor all changes to any document in a collection. + +### When to Use + +- Refresh list views when data changes +- Trigger background sync after local updates +- Audit logging of collection activity +- Real-time collaboration features + +### Data Structure + +When a collection changes, your callback receives a `CollectionChange` object: + +```typescript +interface CollectionChange { + documentIDs: string[]; // Array of changed document IDs + collection: Collection; // Reference to the collection +} +``` + +:::caution Property Names +The property is `documentIDs` (capital IDs), not `documentIds`. + +There is NO direct `database` property. Access it via `change.collection.database`. +::: + +#### Example 6. Basic Collection Listener + +```typescript +import { Collection, ListenerToken } from 'cbl-reactnative'; + +const collection = await database.createCollection('users'); + +const token: ListenerToken = await collection.addChangeListener((change) => { + console.log('Collection changed!'); + console.log('Changed documents:', change.documentIDs); + // Example output: ['user-123', 'user-456'] + + console.log('Collection name:', change.collection.name); + // Output: users + + console.log('Database name:', change.collection.database.getName()); + // Output: mydb +}); + +// Remove listener when done +await token.remove(); +``` + +#### Example 7. React Hook Pattern + +```typescript +import { useEffect } from 'react'; +import { ListenerToken } from 'cbl-reactnative'; + +function UserListScreen({ collection }) { + useEffect(() => { + if (!collection) return; + + let token; + + const setup = async () => { + token = await collection.addChangeListener((change) => { + console.log(`${change.documentIDs.length} documents changed`); + refreshUserList(); + }); + }; + setup(); + + // Cleanup on unmount + return () => { + if (token) token.remove(); + }; + }, [collection]); +} +``` + +:::important Limitation +Only ONE collection-wide listener is allowed per collection instance. Calling `addChangeListener` twice will throw an error. + +However, you can have MULTIPLE document listeners on the same collection. +::: + +:::caution Deprecated +The `collection.removeChangeListener(token)` method is deprecated. It remains available for backward compatibility, but new applications should use `token.remove()`. Existing applications are strongly encouraged to migrate. +::: \ No newline at end of file diff --git a/versioned_docs/version-1.0/typed-data.md b/versioned_docs/version-1.0/typed-data.md new file mode 100644 index 0000000..e0d5ab7 --- /dev/null +++ b/versioned_docs/version-1.0/typed-data.md @@ -0,0 +1,6 @@ +--- +id: typed-data +sidebar_position: 12 +--- + +# Typed Data \ No newline at end of file diff --git a/versioned_sidebars/version-1.0-sidebars.json b/versioned_sidebars/version-1.0-sidebars.json new file mode 100644 index 0000000..caea0c0 --- /dev/null +++ b/versioned_sidebars/version-1.0-sidebars.json @@ -0,0 +1,8 @@ +{ + "tutorialSidebar": [ + { + "type": "autogenerated", + "dirName": "." + } + ] +} diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..9a369ed --- /dev/null +++ b/versions.json @@ -0,0 +1,3 @@ +[ + "1.0" +]