托管同步订阅 - Node.js SDK
在此页面上
Flexible Sync使用订阅和权限来确定要与您的应用同步哪些数据。 您必须至少拥有一个订阅,才能读取或写入启用了 Flexible Sync 的 Realm。 本页详细介绍了如何托管这些订阅。
您可以添加、更新和删除查询订阅,以控制同步到客户端设备的数据。 在 Realm Node.js SDK v12.0.0 及更高版本中,您可以订阅查询,而不是手动管理订阅,或作为手动管理订阅的补充。
您无法为数据导入和非对称对象创建订阅,因为它们仅将数据发送到应用程序的后端。
先决条件
您需要满足以下要求,然后才能将 Atlas Device Sync 与 Node.js SDK 结合使用:
运行MongoDB 5.0或更高版本的非分片Atlas 集群。
Realm JavaScript版本10.12.0 或更高版本。
除了这些要求之外,您还需要设置以下内容才能在 Node.js 客户端中使用 Flexible Sync:
订阅查询
版本 12.0.0 中的新增内容。
Realm Node.js v12.0.0 添加了实验性 API,用于订阅和取消订阅查询结果。 这些 API 抽象了手动添加和删除订阅的细节。
对于所有订阅,您都需要经过身份验证的用户和 Flexible Sync Realm。
在版本 12.3.0 中进行了更改: Atlas Device Sync 支持的地理空间数据
在 Realm Node.js SDK v12.3.0 及更高版本中,您可以创建地理空间查询的订阅。 如果您尝试使用旧版本的 SDK 订阅地理空间查询,您将收到带有补偿写入的服务器错误。
有关详细信息,请参阅查询地理空间数据。
订阅查询
我们建议您为订阅命名。 这样可以更轻松地查找和管理您的订阅。 订阅名称必须是唯一名称。 尝试添加与现有订阅同名的订阅会引发错误。
订阅查询:
查询要读写的对象。
对查询结果调用
subscribe()
,为与查询匹配的对象创建同步订阅。将包含
name
属性的SubscriptionOptions
对象传递给subscribe()
。
const subOptions = { name: "All completed tasks", }; const completedTasks = await realm .objects(Task) .filtered('status == "completed"') .subscribe(subOptions); const completedTasksSubscription = realm.subscriptions.findByName( "All completed tasks" ); // ...work with the subscribed results list or modify the subscription
const completedTasks = await realm .objects(Task) .filtered('status == "completed"') .subscribe({ name: "All completed tasks" }); const completedTasksSubscription = realm.subscriptions.findByName( "All completed tasks" ); // ...work with the subscribed results list or modify the subscription
订阅没有订阅名称的查询
大多数时候,您应该为订阅命名。 否则,该名称将被设置为 null。
如果您在未命名的查询订阅上使用 filtered()
,则订阅标识符基于 filtered
查询。这意味着,每当查询字符串变更时,subscribe()
就会创建新的订阅。
const config = { schema: [Task], sync: { user: app.currentUser, flexible: true, }, }; const realm = await Realm.open(config); const completedTasks = await realm .objects(Task) .filtered('status == "completed"') .subscribe(); // ...work with the subscribed results list
const config: Realm.Configuration = { schema: [Task], sync: { user: app.currentUser!, flexible: true, }, }; const realm = await Realm.open(config); const completedTasks = await realm .objects(Task) .filtered('status == "completed"') .subscribe(); // ...work with the subscribed results list
等待查询订阅同步
当您订阅查询结果时,在下载同步数据之前,结果不包含对象。 当您确实需要等待同步对象完成下载时,请使用waitForSync
。 您可以为订阅指定不同的行为以及它们处理下载的方式。
此示例使用 FirstTime
选项,这是默认行为。具有 FirstTime
行为的订阅仅在首次创建订阅时等待同步完成。
import { WaitForSync } from "realm"; // Get tasks that have a status of "in progress". const completedTasks = realm .objects(Task) .filtered("status == 'completed'"); // Only waits for sync to finish on the initial sync. await completedTasks.subscribe({ behavior: WaitForSync.FirstTime, name: "First time sync only", });
其他受支持的 WaitForSync
选项包括:
Always
:每次应用程序启动时等待下载匹配对象。该应用程序在每次启动时都必须有互联网连接。Never
:永远不要等待下载匹配对象。该应用需要互联网连接才能让用户在首次启动应用时进行身份验证,但可以在后续启动时使用缓存的凭据离线打开。
您可以选择指定 timeout
值来限制同步下载运行的时间:
import { WaitForSync } from "realm"; // Get tasks that have a status of "in progress". const completedTasks = realm .objects(Task) .filtered("status == 'completed'"); // Add subscription with timeout // If timeout expires before sync is completed, currently-downloaded // objects are returned and sync download continues in the background. const taskSubscription = await completedTasks.subscribe({ behavior: WaitForSync.Always, timeout: 500, });
取消订阅查询
您可以使用unsubscribe()
取消订阅查询结果:
import { WaitForSync } from "realm"; // Get tasks that have a status of "in progress". const completedTasks = realm .objects(Task) .filtered("status == 'completed'"); // Only waits for sync to finish on the initial sync. await completedTasks.subscribe({ behavior: WaitForSync.FirstTime, name: "First time sync only", }); // Unsubscribe completedTasks.unsubscribe();
这将从活动订阅列表中删除订阅,类似于手动删除订阅。
如果存在另一个包含重叠对象的订阅,则在调用 unsubscribe()
后,结果列表可能仍包含对象。
当您调用 unsubscribe()
时,相关订阅将被删除。按名称删除订阅。如果它们没有名称,则 unsubscribe()
会删除与您调用 unsubscribe()
的查询完全匹配的查询。
unsubscribe()
方法会在与已删除订阅匹配的对象从 Realm 中删除之前返回。同步会根据新的订阅集在后台继续进行。
手动管理订阅
您可以使用 Subscriptions API 手动管理对可查询字段的特定查询的订阅集。
您可以:
获取所有订阅的列表
添加订阅
检查订阅状态
使用新查询更新订阅
删除单个订阅或某一类型的所有订阅
当数据与订阅匹配并具有适当的权限时,它就会在设备和后端应用程序之间进行同步。
创建订阅时,Realm 会查找与特定对象类型的查询匹配的数据。您可以对几种不同的对象类型进行订阅。您还可以对同一对象类型进行多次查询。
重要
对象链接
要查看链接对象,必须将对象及其链接对象都添加到订阅集中。
如果订阅结果包含一个对象,该对象的属性链接到结果中未包含的对象,则该链接显示为空。我们无法区分该属性的值是否为合法空值,或者所链接的对象是否存在但不在查询订阅的视图中。
获取所有订阅
使用灵活同步 Realm 时,您可以通过Realm访问SubscriptionSet
(订阅集合)。 属性。
// get the SubscriptionSet for the realm const subscriptions = realm.subscriptions;
添加订阅
订阅基于域 查询的结果。
在以下示例中,completed
和 progressMinutes
已被设置为 App Services App 中的可查询字段。在客户端代码中,我们创建经筛选的查询,然后订阅其结果:
已完成的任务
耗时超过 120 的已完成任务
progressMinutes
const tasks = realm.objects("Task"); const longRunningTasks = tasks.filtered( 'status == "completed" && progressMinutes > 120' ); await realm.subscriptions.update((mutableSubs) => { mutableSubs.add(longRunningTasks, { name: "longRunningTasksSubscription", }); mutableSubs.add(realm.objects("Team"), { name: "teamsSubscription", }); });
设置初始订阅
您必须至少拥有一个订阅,才能读取或写入 Flexible Sync 域。您可以在打开 Realm 时添加初始订阅。
要设置初始订阅,请在您 Realm 的SyncConfiguration中包含initialSubscriptions
字段。 在initialSubscriptions
对象中,添加update
字段并设置为订阅查询的回调:
const config = { schema: [Task], sync: { user: app.currentUser, flexible: true, initialSubscriptions: { update: (subs, realm) => { subs.add(realm.objects(Task).filtered("status == 'in progress'"), { name: "In progress tasks", }); }, rerunOnOpen: true, }, }, }; const realm = await Realm.open(config);
const config: Realm.Configuration = { schema: [Task], sync: { user: app.currentUser!, flexible: true, initialSubscriptions: { update: (subs, realm) => { subs.add(realm.objects(Task).filtered("status == 'in progress'"), { name: "In progress tasks", }); }, rerunOnOpen: true, }, }, }; const realm = await Realm.open(config);
默认情况下,仅在首次打开域时才会创建初始订阅。如果应用程序需要在每次启动时重新运行初始订阅,则您可以将 rerunOnOpen
设置为 true
。可能需要执行此操作以重新运行动态时间范围或其他需要重新计算订阅的静态变量的查询。
检查订阅状态
您可以检查订阅状态,查看服务器是否已确认订阅,设备是否已将数据下载到本地。
可以使用订阅状态执行以下操作:
Trigger error handling
显示事务是挂起还是已完成
了解订阅集被取代的时间,并且您应获取订阅集的新实例,以写入订阅变更
注意
订阅状态 Complete(完成)
订阅集状态“完成”并不意味着“同步已完成”或“所有文档已同步”。“完成”意味着发生了以下两件事:
该订阅已成为当前正在与服务器同步的活动订阅集。
在将订阅发送到服务器时与订阅匹配的文档现在位于本地设备上。请注意,这并不一定包括当前与订阅匹配的所有文档。
对于所有与订阅匹配的文档是否已同步到设备,Realm SDK 不提供检查方法。
版本 12.0.0 中的新增内容。
Node.js v 12.0.0 添加了可用于获取订阅状态的SubscriptionSetState 枚举。
订阅状态 Complete(完成)
订阅集状态“完成”并不意味着“同步已完成”或“所有文档已同步”。“完成”意味着发生了以下两件事:
该订阅已成为当前正在与服务器同步的活动订阅集。
在将订阅发送到服务器时与订阅匹配的文档现在位于本地设备上。请注意,这并不一定包括当前与订阅匹配的所有文档。
对于所有与订阅匹配的文档是否已同步到设备,Realm SDK 不提供检查方法。
使用新查询更新订阅
您可以使用新查询更新已命名的订阅。要更新订阅的查询,请将新查询以及带有要更新的订阅名称的订阅选项传递给 MutableSubscriptionSet.add()
方法。与添加新订阅一样,您必须通过调用 subscriptions.update()
在事务中更新订阅。
在以下示例中,长时间运行的任务被重新定义为任何耗时超过 180 分钟的任务。
realm.subscriptions.update((mutableSubs) => { mutableSubs.add( tasks.filtered('status == "completed" && progressMinutes > 180'), { name: "longRunningTasksSubscription", } ); });
注意
尝试更新已将 SubscriptionOptions.throwOnUpdate
字段设置为 true 的订阅,会出现异常。
删除订阅
您可以通过多种方式删除订阅:
使用特定查询删除单个订阅
删除具有特定名称的单个订阅
删除对特定对象模型的所有订阅
删除所有未命名的订阅
删除所有订阅
删除订阅查询时,服务器也会删除客户端设备上的同步数据。
通过查询删除订阅
您可以在订阅设立上执行ACID 事务,通过查询删除特定订阅。 将查询传递给ACID 事务中MutableSubscriptionSet
上的删除 ()方法。
在以下示例中,将从订阅集中删除所有者名为“Ben”的任务的订阅。
realm.subscriptions.update((mutableSubs) => { // remove a subscription with a specific query mutableSubs.remove(tasks.filtered('owner == "Ben"')); });
按名称删除订阅
要按名称删除特定订阅,请在订阅设立上执行ACID 事务。 在ACID 事务中,将该名称传递给MutableSubscriptionSet
上的removeByName()方法。
realm.subscriptions.update((mutableSubs) => { // remove a subscription with a specific name mutableSubs.removeByName("longRunningTasksSubscription"); });
通过引用删除订阅
如果您有对订阅的引用,则可以删除该订阅。 为此,请在订阅集上执行事务。 在事务中,将引用变量传递给MutableSubscriptionSet
上的removeSubscription方法。
let subscriptionReference; realm.subscriptions.update((mutableSubs) => { subscriptionReference = mutableSubs.add(realm.objects("Task")); }); // later.. realm.subscriptions.removeSubscription(subscriptionReference);
删除对象类型的所有订阅
要删除特定对象类型的所有订阅,请在订阅设立上执行ACID 事务。 在ACID 事务中,将对象类型作为string传递给 MutableSubscriptionSet
上的removeByObjectType方法。
realm.subscriptions.update((mutableSubs) => { mutableSubs.removeByObjectType("Team"); });
删除所有未命名的订阅
v12.0.0 版本新增。
您可能想要删除临时或动态生成的未命名订阅,但保留已命名订阅。
您可以通过在 mutableSubs
上调用 .removeUnnamed()
,从订阅集中删除所有未命名的订阅。.removeUnnamed()
将返回已删除的未命名订阅的数量。
// Remove unnamed subscriptions. let numberRemovedSubscriptions = 0; await realm.subscriptions.update((mutableSubs) => { numberRemovedSubscriptions = mutableSubs.removeUnnamed(); });
删除所有订阅
要从订阅设立删除所有订阅,请在订阅设立上执行ACID 事务。 在ACID 事务中对MutableSubscriptionSet
调用removeAll()
realm.subscriptions.update((mutableSubs) => { mutableSubs.removeAll(); });
性能考虑因素
API 效率
使用订阅查询部分中描述的 subscribe()
和 unsubscribe()
API 管理多个订阅的效率低于手动管理订阅时执行批量更新的效率。
为了在进行多项订阅更改时获得更好的性能,请使用subscriptions
API 在单个事务中更新所有订阅。 要了解如何操作,请参阅手动管理订阅。
更新群组以提高性能
订阅集的每个写入事务都会产生性能成本。如果需要在会话期间对 Realm 对象进行多次更新,请考虑将编辑的对象保留在内存中,直到所有更改完成。这通过仅将完整且更新的对象写入 Realm 而不是每次更改来提高同步性能。
“灵活同步”RQL 的要求和限制
已索引可查询字段订阅的要求
向应用添加索引可查询字段可以提高对强分区数据进行简单查询的性能。 例如,如果查询将数据强映射到设备、商店或用户的应用(例如user_id == $0, “641374b03725038381d2e1fb”
,则非常适合使用索引可查询字段。 但是,在查询订阅中使用索引化可查询字段有特定的要求:
每个订阅查询中都必须使用索引化可查询字段。查询中不能缺少该字段。
在订阅查询中,索引化可查询字段必须使用
==
或IN
进行至少一次针对常量的比较。例如,user_id == $0, "641374b03725038381d2e1fb"
或store_id IN $0, {1,2,3}
。
可以选择包含 AND
比较,前提是使用 ==
或 IN
将索引化可查询字段直接与常量进行至少一次比较。例如,store_id IN {1,2,3} AND region=="Northeast"
或 store_id == 1 AND (active_promotions < 5 OR num_employees < 10)
。
对索引化可查询字段的无效灵活同步查询包括以下情况的查询:
索引化可查询字段未将
AND
与查询的其余部分结合使用。例如,store_id IN {1,2,3} OR region=="Northeast"
是无效的,因为它使用了OR
而不是AND
。同样,store_id == 1 AND active_promotions < 5 OR num_employees < 10
也是无效的,因为AND
仅适用于其旁边的词,而不适用于整个查询。索引化可查询字段未在相等运算符中使用。例如,
store_id > 2 AND region=="Northeast"
是无效的,因为它仅将>
运算符与索引化可查询字段结合使用,而没有相等比较。查询中完全没有索引化可查询字段。例如,
region=="Northeast
和truepredicate
都是无效的,因为它们不含索引化可查询字段。
灵活同步中不支持的查询运算符
使用 RQL 操作符时,Flexible Sync有一些限制。当您编写确定要同步哪些数据的查询订阅时,服务器不支持这些查询操作符。但是,您仍然可以使用全部的 RQL 功能来查询客户端应用中的同步数据集。
运算符类型 | 不支持的运算符 |
---|---|
聚合操作符 | @avg , @count , @max , @min , @sum |
查询后缀 | DISTINCT , SORT , LIMIT |
不区分大小写的查询 ([c]
) 无法有效地使用索引。因此,不建议使用不区分大小写的查询,因为它们可能会导致性能问题。
灵活同步仅支持数组字段的 @count
。
列出查询
灵活同步支持使用 IN
运算符来查询列表。
可以查询常量列表,查看其中是否包含可查询字段的值:
// Query a constant list for a queryable field value "priority IN { 1, 2, 3 }"
如果某个可查询字段具有数组值,则可以通过查询确定其中是否包含常量值:
// Query an array-valued queryable field for a constant value "'comedy' IN genres"
警告
您无法在灵活同步查询中相互比较两个列表。请注意,这是灵活同步查询之外的有效 Realm 查询语言语法。
// Invalid Flexible Sync query. Do not do this! "{'comedy', 'horror', 'suspense'} IN genres" // Another invalid Flexible Sync query. Do not do this! "ANY {'comedy', 'horror', 'suspense'} != ANY genres"
嵌入式对象或关联对象
灵活同步不支持查询嵌入式对象或链接中的属性。例如,obj1.field == "foo"
。
查询大小限制
订阅设立任何给定查询订阅的大小限制为256 kB 。 超过此限制会导致LimitsExceeded 错误。