Bug 1927569 - Rust component JS topic guides, r=bgruber

Added JS topic guides and also updated the existing Swift/JS ones a bit.
I didn't add a guide for remote settings, since I don't think we want to
encourage users to use this one over the official client.

Split up the notes at the begining into prerequites and notes on async.
My hope is that the async notes can go away once we start making our
components async.

Differential Revision: https://phabricator.services.mozilla.com/D228481
This commit is contained in:
Ben Dean-Kawamura 2024-11-12 22:14:49 +00:00
parent 2b0abf3bad
commit 726d243f2f
5 changed files with 135 additions and 41 deletions

View File

@ -7,6 +7,20 @@ myst:
The `relevancy` component tracks the user's interests locally, without sharing any data over the network. The component currently supports building an interest vector based on the URLs they visit.
## Prerequisites
That {doc}`viaduct` must be initialized during application startup.
## Async
The Relevancy API is synchronous, which means calling it directly will block the current
thread. To deal with this, all current consumers wrap the API in order to make it async. For
details on this wrapping, see the consumer code itself.
On JS, this wrapping is handled automatically by UniFFI. See
https://searchfox.org/mozilla-central/source/toolkit/components/uniffi-bindgen-gecko-js/config.toml
for details on which functions/methods are wrapped to be async.
## Setting up the store
To use the `RelevancyStore` in either Kotlin or Swift, you need to import the relevant classes and data types from the `MozillaAppServices` library.
@ -24,19 +38,13 @@ import MozillaAppServices
let store = RelevancyStore(dbPath: "path/to/database")
```
:::
```js
ChromeUtils.defineESModuleGetters(lazy, {
RelevancyStore: "resource://gre/modules/RustSuggest.sys.mjs",
});
To work with the RelevancyStore, you need to create an instance using a database path where the users interest data will be stored:
:::{tab-set-code}
```kotlin
val store = RelevancyStore(dbPath)
```
```swift
let store = RelevancyStore(dbPath: "path/to/database")
const store = RelevancyStore.init(dbPath);
```
:::
@ -59,6 +67,11 @@ val interestVector = store.ingest(topUrlsByFrequency)
let topUrlsByFrequency = ["https://example.com", "https://another-example.com"]
let interestVector = store.ingest(topUrlsByFrequency)
```
```js
const topUrlsByFrequency = ["https://example.com", "https://another-example.com"];
const interestVector = await store.ingest(topUrlsByFrequency);
```
:::
* `topUrlsByFrequency`: A list of URLs ranked by how often and recently the user has visited them. This data is used to build the user's interest vector.

View File

@ -7,13 +7,15 @@ myst:
The API for the Remote Settings can be found in the Mozilla Rust components [Kotlin API Reference](https://mozilla.github.io/application-services/kotlin/kotlin-components-docs/mozilla.appservices.remotesettings/index.html) and [Swift API Reference](https://mozilla.github.io/application-services/swift/Classes/RemoteSettings.html).
```{note}
Make sure you initialize {doc}`viaduct` for this component.
```
## Prerequisites
```{warning}
The remote settings code is synchronous, which means it needs to be wrapped in the asynchronous primitive of the target language you are using.
```
That {doc}`viaduct` must be initialized during application startup.
## Async
The Remote Settings API is synchronous, which means calling it directly will block the current
thread. To deal with this, all current consumers wrap the API in order to make it async. For
details on this wrapping, see the consumer code itself.
## Importing items

View File

@ -7,14 +7,19 @@ myst:
The API for the `SuggestStore` can be found in the [MozillaComponents Kotlin documentation](https://mozilla.github.io/application-services/kotlin/kotlin-components-docs/mozilla.appservices.suggest/-suggest-store/index.html).
```{note}
Make sure you initialize {doc}`viaduct` for this component.
```
## Prerequisites
```{warning}
The `SuggestStore` is synchronous, which means it needs to be wrapped in the asynchronous primitive of the target language you are using.
```
That {doc}`viaduct` must be initialized during application startup.
## Async
The Suggest API is synchronous, which means calling it directly will block the current
thread. To deal with this, all current consumers wrap the API in order to make it async. For
details on this wrapping, see the consumer code itself.
On JS, this wrapping is handled automatically by UniFFI. See
https://searchfox.org/mozilla-central/source/toolkit/components/uniffi-bindgen-gecko-js/config.toml
for details on which functions/methods are wrapped to be async.
## Setting up the store
@ -35,6 +40,20 @@ import mozilla.appservices.suggest.SuggestionQuery
```swift
import MozillaAppServices
```
```js
ChromeUtils.defineESModuleGetters(lazy, {
RemoteSettingsServer: "resource://gre/modules/RustSuggest.sys.mjs",
SuggestApiException: "resource://gre/modules/RustSuggest.sys.mjs",
SuggestIngestionConstraints: "resource://gre/modules/RustSuggest.sys.mjs",
SuggestStore: "resource://gre/modules/RustSuggest.sys.mjs",
SuggestStoreBuilder: "resource://gre/modules/RustSuggest.sys.mjs",
Suggestion: "resource://gre/modules/RustSuggest.sys.mjs",
SuggestionProvider: "resource://gre/modules/RustSuggest.sys.mjs",
SuggestionQuery: "resource://gre/modules/RustSuggest.sys.mjs",
});
```
:::
Create a `SuggestStore` as a singleton. You do this via the `SuggestStoreBuilder`, which returns a `SuggestStore`. No I/O or network requests are performed during construction, which makes this safe to do at any point in the application startup:
@ -56,6 +75,15 @@ let store: SuggestStore = {
return storeBuilder.build()
}
```
```js
const store: SuggestStore = SuggestStoreBuilder()
.dataPath(pathForSuggestDatabase)
.remoteSettingsServer(remoteSettingsServer)
.build();
}
:::
* You need to set the `dataPath`, which is the path (the SQLite location) where you store your suggestions.
@ -75,18 +103,26 @@ Ingest with `SuggestIngestionConstraints(emptyOnly=true)` shortly after each sta
:::{tab-set-code}
```kotlin
store.value.ingest(SuggestIngestionConstraints(
store.ingest(SuggestIngestionConstraints(
emptyOnly = true,
providers = listOf(SuggestionProvider.AMP_MOBILE, SuggestionProvider.WIKIPEDIA, SuggestionProvider.WEATHER)
))
```
```swift
store.value.ingest(SuggestIngestionConstraints(
store.ingest(SuggestIngestionConstraints(
emptyOnly: true,
providers: [.AMP_MOBILE, .WIKIPEDIA, .WEATHER]
))
```
```js
store.ingest(SuggestIngestionConstraints(
emptyOnly: true,
providers: [SuggestionProvider.AMP_MOBILE, SuggestionProvider.WIKIPEDIA, SuggestionProvider.WEATHER]
))
```
:::
### Periodically
@ -95,11 +131,25 @@ Ingest with `SuggestIngestionConstraints(emptyOnly=false)` on a regular schedule
:::{tab-set-code}
```kotlin
store.value.ingest(SuggestIngestionConstraints(emptyOnly = false))
store.ingest(SuggestIngestionConstraints(
emptyOnly = false,
providers = listOf(SuggestionProvider.AMP_MOBILE, SuggestionProvider.WIKIPEDIA, SuggestionProvider.WEATHER)
))
```
```swift
store.value.ingest(SuggestIngestionConstraints(emptyOnly: false))
store.ingest(SuggestIngestionConstraints(
emptyOnly: false,
providers: [.AMP_MOBILE, .WIKIPEDIA, .WEATHER]
))
```
```js
store.ingest(SuggestIngestionConstraints(
emptyOnly: false,
providers: [SuggestionProvider.AMP_MOBILE, SuggestionProvider.WIKIPEDIA, SuggestionProvider.WEATHER]
))
```
:::
@ -109,7 +159,7 @@ Call `SuggestStore::query` to fetch suggestions for the suggest bar. The `provid
:::{tab-set-code}
```kotlin
store.value.query(
store.query(
SuggestionQuery(
keyword = text,
providers = listOf(SuggestionProvider.AMP_MOBILE, SuggestionProvider.WIKIPEDIA, SuggestionProvider.WEATHER),
@ -119,7 +169,7 @@ store.value.query(
```
```swift
store.value.query(
store.query(
SuggestionQuery(
keyword: text,
providers: [.AMP_MOBILE, .WIKIPEDIA, .WEATHER],
@ -127,32 +177,50 @@ store.value.query(
)
)
```
```js
store.query(
SuggestionQuery(
keyword = text,
providers = [SuggestionProvider.AMP_MOBILE, SuggestionProvider.WIKIPEDIA, SuggestionProvider.WEATHER],
limit = MAX_NUM_OF_FIREFOX_SUGGESTIONS,
),
)
```
:::
## Interrupt querying
Call `SuggestStore::Interrupt` with `InterruptKind::Read` to interrupt any in-progress queries when the user cancels a query and before running the next query.
Call `interrupt()` with `InterruptKind::Read` to interrupt any in-progress queries when the user cancels a query and before running the next query.
:::{tab-set-code}
```kotlin
store.value.interrupt()
store.interrupt(InterruptKind.READ)
```
```swift
store.value.interrupt()
store.interrupt(kind: InterruptKind.READ)
```
```js
store.interrupt(InterruptKind.READ)
```
:::
## Shutdown the store
On shutdown, call `SuggestStore::Interrupt` with `InterruptKind::ReadWrite` to interrupt any in-progress ingestion and queries.
On shutdown, call `interrupt()` with `InterruptKind::ReadWrite` to interrupt any in-progress ingestion in addition to queries.
:::{tab-set-code}
```kotlin
store.value.interrupt()
store.interrupt(InterruptKind.READ_WRITE)
```
```swift
store.value.interrupt()
store.interrupt(kind: InterruptKind.READ_WRITE)
```
```js
store.interrupt(InterruptKind.READ_WRITE)
```
:::

View File

@ -2,6 +2,9 @@
`Viaduct` initialization is required for all platforms and for multiple components. The [README](https://github.com/mozilla/application-services/blob/main/components/viaduct/README.md) explains the component in more detail.
Firefox Desktop, Firefox Android, and Firefox iOS all already initialize viaduct. You only need to
take action if you want to use the Rust components on a new application.
There are 3 different options to use `viaduct`:
* Any `libxul` based can ignore initialization, since it's handled by `libxul`.

View File

@ -11,7 +11,9 @@
[suggest.async_wrappers]
# All functions/methods are wrapped to be async by default and must be `await`ed.
enable = true
# These are exceptions to the async wrapping. These functions must not be `await`ed.
main_thread = [
"raw_suggestion_url_matches",
"SuggestStore",
@ -25,7 +27,9 @@ main_thread = [
]
[relevancy.async_wrappers]
# All functions/methods are wrapped to be async by default and must be `await`ed.
enable = true
# These are exceptions to the async wrapping. These functions must not be `await`ed.
main_thread = [
"RelevancyStore",
"RelevancyStore.close",
@ -34,21 +38,19 @@ main_thread = [
]
[remote_settings.async_wrappers]
# All functions/methods are wrapped to be async by default and must be `await`ed.
enable = true
# These are exceptions to the async wrapping. These functions must not be `await`ed.
main_thread = [
"RemoteSettings",
]
[uniffi_fixture_callbacks.async_wrappers]
enable = true
main_thread = [
"log_even_numbers_main_thread",
]
[error_support.async_wrappers]
# All functions/methods are wrapped to be async by default and must be `await`ed.
enable = true
[tabs.async_wrappers]
# All functions/methods are wrapped to be async by default and must be `await`ed.
enable = true
[arithmetical.async_wrappers]
@ -57,6 +59,12 @@ enable = true
[uniffi_custom_types.async_wrappers]
enable = true
[uniffi_fixture_callbacks.async_wrappers]
enable = true
main_thread = [
"log_even_numbers_main_thread",
]
[uniffi_fixture_external_types.async_wrappers]
enable = true