@ fluid框架/顺序
使用 Fluid 框架库
对于依赖流体框架库的公共API,我们建议 ^ (caret)版本范围.
例如,使用 ^1.3.4。 。 。 .
依赖不稳定的API,例如: beta API,我们推荐一个更具限制性的版本范围.
例如,使用一个 ~ 版本范围.
安装
运行此命令以安装软件包 :
npm i @fluidframework/sequence
从此软件包导入
此软件包使用 packet.json 导出 通过支持级别将API分离. 有关支助保障的信息改为: API 支持级别。 。 。 .
导入 public API 从 @fluidframework/sequence。 。 。 .
导入 legacy API 从 @fluidframework/sequence/legacy。 。 。 .
API 文档
读取 @ fluid框架/顺序 API 文档 https://fluidframework.com/docs/apis/序列 互联网档案馆的存檔,存档日期2014-12-21。 。 。 .
说明
这个 @ fluid框架/顺序 软件包支持类似列表的分布式数据结构. 其主要出口为: 共享结构,用于存储和同时编辑文本序列的 DDS.
注意:"共享String"是一个序列 DDS,但它有额外的专用特征和行为来与文本合作.
这个软件包在历史上包含着其他几个基于序列的DDS,但由于它们有不直观的行为, 他们被贬低,被迁移到 实验 文件夹.
主要原因是缺乏 移动 序列内的语义学,在处理序列时变得至关重要:
复杂内容.
出于这个原因,这个 README 中的所有示例 SharedString然而,讨论的API可以在共同基础类中找到: SharedSegmentSequence。 。 。 .
本文件的其余部分,即: 顺序 将参考此基础类.
项目 是在序列中存储的单个单位(例如,在共享结构中,项目为字符), 但是,无论在序列中存储的数据类型如何,每个序列中的项目都处于一个特定的状态 职位 开始 0时,类似于数组。 然而,序列与阵列不同,因为位置可以作为本地和远程移动 编辑对序列进行修改.
如其名称所示,共享区块序列由以下组成: 部分。片段是序列工作的单位 内有,并含有各种物品。 因此,每个部分的长度至少为1 -- -- 即包含: 至少一个项目 -- -- 随着顺序的编辑,部分可能会被任意分割和合并。 这意味着长度 序列中的分数不是分数,而是所有分数的长度之和.
例如,考虑一个最初为空的共享结构。 用户A将字符a、b和c添加到 顺序。 它的长度现在是3个 -- -- 它包含3个项目。 然而,在内部,序列可以是1、2或3 部分.
Segments: [S1] [S2] [S3]
Items: a b c
Segments: [ S1 ] [S2]
Items: a b c
Segments: [ S1 ]
Items: a b c
在典型的使用中,各部分的分拆和合并是执行细节,与使用 顺序。 然而,可以列举出于业绩原因交叉一系列立场的部分. 在此情况下,重要的是不要保留提及各部分的内容(在列举之外),并且不提及 根据各段本身长度作出的假设.
使用顺序
序列支持三种基本操作:插入,去除和注释. 插入和去除用于从序列中添加和去除项目,而注释用于在项目中添加元数据. 值得注意的是,序列并不支持"移动"一系列内容的概念.
如果"移动"语义 对你的情况来说是一个艰难的要求, 这个问题 概述一些合理的替代办法.
插入
在序列上插入操作与内容一起取出一个单一的位置参数. 这一立场是包容性的,而且 可以是序列中包括 0 在内的任意位置,在序列开头插入,也可以是长度 序列,在结尾插入.
// content:
// positions:
// insert text at position 0
sharedString.insertText(0, "hi");
// content: hi
// positions: 01
// insert text at the end position
sharedString.insertText(sharedString.getLength(), "!");
// content: hi!
// positions: 012
// insert text at position 2
sharedString.insertText(2, " world");
// content: hi world!
// positions: 012345678
删除
删除操作需要开始和结束位置,称为 a 范围。起始位置是包容性的,可以
从0到其序列中的任何位置 length - 1。起始位置不能是类似序列的长度
在插入中,因为该位置上没有任何东西。 最终位置是排他性的,必须大于
开始,所以它可以是 1 到 无 (何处) 无 是序列的长度.
// content: hi world!
// positions: 012345678
// remove the first 3 characters
sharedString.removeRange(0, 3);
// content: world!
// positions: 012345
// remove all the characters
sharedString.removeRange(0, sharedString.getLength());
// content:
// positions:
注释
注释操作可以在序列中的项目中添加或去除类似地图的属性. 他们可以储存任何JSON 可序列化数据,并具有与一个 共享地图 (最后一位作家获胜). 注释要开始和结束 位置,其工作方式与去除操作的开始和结束相同。 除了开始和结束, 注释 还取一个类似地图的属性对象。 提供属性对象的每个密钥将设置在 指定范围。 设置无效的属性密钥将使该属性脱离该范围内的位置.
// content: hi world
// positions: 01234567
let props1 = sharedString.getPropertiesAtPosition(1);
let props5 = sharedString.getPropertiesAtPosition(5);
// props1 = {}
// props5 = {}
// set property called weight on positions 0 and 1
sharedString.annotateRange(0, 2, { weight: 5 });
props1 = sharedString.getPropertiesAtPosition(1);
props5 = sharedString.getPropertiesAtPosition(5);
// props1 = { weight: 5 }
// props5 = {}
// set property called decoration on all positions
sharedString.annotateRange(0, sharedString.getLength(), { decoration: "underline" });
props1 = sharedString.getPropertiesAtPosition(1);
props5 = sharedString.getPropertiesAtPosition(5);
// props1 = { weight: 5, decoration: "underline" }
// props5 = { decoration: "underline" }
// remove property called weight on all positions
sharedString.annotateRange(0, sharedString.getLength(), { weight: null });
props1 = sharedString.getPropertiesAtPosition(1);
props5 = sharedString.getPropertiesAtPosition(5);
// props1 = { decoration: "underline" }
// props5 = { decoration: "underline" }
顺序三角洲事件
每次按顺序进行操作a 序列 德尔塔 将提起事件。 此事件提供了范围 受操作影响,操作类型,以及操作所改变的属性.
sharedString.on("sequenceDelta", ({ deltaOperation, ranges, isLocal }) => {
if (isLocal) {
// undo-redo implementations frequently will only concern themselves with local ops: only operations submitted
// by the local client should be undoable by the current user
addOperationToUndoStack(deltaOperation, ranges);
}
if (deltaOperation === MergeTreeDeltaType.INSERT) {
syncInsertSegmentToModel(deltaOperation, ranges);
}
// realistic app code would likely handle the other deltaOperation types as well here.
});
内部,序列包取决于 @fluidframework/merge-tree,同时提高 MergeTreeMaintenance 该树上的事件为 维修 事件.
这些事件与序列上DDS所引用的API并不直接对应,但可能对高级用户有用.
每当合并-树木变化的基本结构(部分被合并、拆分、脱钩等)时,都会提出维护活动,
因此,试图同步一个依赖于合并-树的分块结构的数据模型的应用程序应该研究这些事件的语义;见 MergeTreeMaintenanceType。 。 。 .
序列Delta和维护事件通常用于同步或使应用程序在备份序列DDS上的视图失效.
序列合并策略
Fluid序列数据结构最终是一致的,这意味着所有编辑器最终都会出现于同一状态 最终状态。 然而,其他合作者可能看不到每个合作者看到的中间状态。 这些 中间状态发生于两个或两个以上合作者修改序列中导致a的相同位置时 冲突.
插入合并策略
考虑这样的顺序:
// content: hi mar
// positions: 012345
现在两个用户同时在序列结尾插入字符. 一个插入 k 插入一个 c。 。 。 .
这个 插入冲突。将解决冲突纳入序列的基本策略是合并 远, (中文(简体) ),
更接近序列的结尾.
这种合并策略之所以可能,是因为"流体框架"具有基本属性,保证了订单. 也就是说,虽然这两个插入同时进行,但业务将获得全球秩序,所有客户都将 在本地应用时,请查看操作的顺序。 这使得每个客户端能够聚合到同一状态 最终.
在早先的例子中,假设 k 操作命令于 c 操作,然后是 k 这将是
插入于第6个位置。 接下来 c 操作被应用——这是合并冲突. 这个 c 行动插入于
请设职位(6) k 被推出到序列的尽头.
// content: hi mar
// positions: 012345
// insert(6, "k")
// k op is ordered first
// content: hi mark
// positions: 0123456
// insert(6, "c")
// c op is now applied, pushing the k towards the end of the sequence
// content: hi marck
// positions: 01234567
如果在同一位置插入多个项目 -- -- 将按住先前订购的项目, 同样的逻辑也适用 随着后期项目的合并, 顺序的正末.
删除策略合并
与插入一样,删除和注释的战略也使用流体框架提供的保证订单.
再次考虑上面的例子。 现在有一个用户插入一个 y 在6号位置,另一个用户删除 c 财务报告和审定财务报表
页:1 k (职位6和7).
// content: hi marck
// positions: 01234567
// REMOVE BEFORE INSERT
// remove(6, 7)
// remove op now applied
// content: hi mar
// positions: 012345
// insert(6, "y")
// no merge conflict -- position 6 is empty
// content: hi mary
// positions: 0123456
// OR
// INSERT BEFORE REMOVE
// insert(6, "y")
// y op is now applied, pushing the c and k towards the end of the sequence
// content: hi maryck
// positions: 012345678
// remove(6, 7)
// remove op now applied, but only removes content ordered before it
// content: hi mary
// positions: 0123456
这种合并行为的关键是,一个去除操作只会去掉当它看到的内容时
操作完成。 在上述例子中,去除操作调整了它去除的范围,只确保 ck 曾经
已删除.
另一种考虑这种行为的方法是,一个去除操作只会去掉早先插入的内容 命令。 命令。 删除操作后插入的任何东西将被忽略 。 序列还检测到重叠去除 操作,并且合并分辨率是直截了当的 -- -- 删除数据.
合并注释策略
如上所述,注解操作在"共享Maps"上的行为类似操作. 使用的合并策略是最后一个写入器 赢了 如果两位合作者在注释属性上设置相同的密钥, 被最后命令的操作 确定值.
当地参考文献
序列支持添加和操纵 本地参考文献 以时间顺序进入本地轨道位置. 如名称所显示,任何创建的引用只会在当地存在;其他客户端不会看到它们. 这可用于实施用户与序列数据的互动,其方式对同时进行编辑十分有力. 例如,考虑一个跟踪用户光标状态的文本编辑器. 应用程序可以在光标位置后存储字符的本地引用 :
// content: hi world!
// positions: 012345678
const { segment, offset } = sharedString.getContainingSegment(5);
const cursor = sharedString.createLocalReferencePosition(
segment,
offset,
ReferenceType.SlideOnRemove,
/* any additional properties */ { cursorColor: "blue" },
);
// cursor: x
// content: hi world!
// positions: 012345678
// ... in some view code, retrieve the position of the local reference for rendering:
const pos = sharedString.localReferencePositionToPosition(cursor); // 5
// meanwhile, some other client submits an edit which gets applied to our string:
otherSharedString.replaceText(1, 2, "ello");
// The local sharedString state will now look like this:
// cursor: x
// content: hello world!
// positions: 0123456789AB (hex)
// ... in some view code, retrieve the position of the local reference for rendering:
const pos = sharedString.localReferencePositionToPosition(cursor); // 8
注意,即使另一个客户端同时编辑了字符串,代表光标的本地引用仍然位于正确位置,没有为API消费者做进一步的工作.
这个 ReferenceType.SlideOnRemove 参数会改变当该引用相关部分被删除时会发生什么.
SlideOnRemove 指示序列试图 幻灯片 引用到下一个最远段的起始,或者如果没有这样的段(即字符串的结尾被删除),则引用到下一个最近段的结尾.
这个 网络流量 实例更详细地说明了这一想法.
和片段不一样,它 实值 安全地在辅助数据结构中坚持本地引用,如倒置-重置堆栈.
间集
序列支持创建 间隔收藏,一个与序列中的位置相关的间隔的辅助集合. 和片段一样,间隔支持添加任意属性,包括指指向其他DS. 间隔收集执行采用当地参考,因此,所有强健性都有利于同时编辑 上节叙述. 与本地参考不同的是,关于间隔收藏的操作发送到所有客户端,并以最终一致的方式更新. 这使得它们适合执行文本文档上的评论线索等功能. 以下的例子说明了这些属性并突出了Interval Collection所支持的主要API.
// content: hi world!
// positions: 012345678
const comments = sharedString.getIntervalCollection("comments");
const comment = comments.add(
3, // (inclusive)
8, // (exclusive): references "world"
IntervalType.SlideOnRemove,
{
creator: "my-user-id",
handle: myCommentThreadDDS.handle,
},
);
// content: hi world!
// positions: 012345678
// comment: [ )
// Interval collection supports iterating over all intervals via Symbol.iterator or `.map()`:
const allIntervalsInCollection = Array.from(comments);
const allProperties = comments.map((comment) => comment.properties);
// or iterating over intervals overlapping a region:
const intervalsOverlappingFirstHalf = comments.findOverlappingIntervals(0, 4);
// Interval endpoints are LocalReferencePositions, so all APIs in the above section can be used:
const startPosition = sharedString.localReferencePositionToPosition(comment.start); // returns 3
const endPosition = sharedString.localReferencePositionToPosition(comment.end); // returns 8: note this is exclusive!
// Intervals can be modified:
comments.change(comment.getIntervalId(), 0, 1);
// content: hi world!
// positions: 012345678
// comment: [)
// their properties can be changed:
comments.changeProperties(comment.getIntervalId(), { status: "resolved" });
// comment.properties === { creator: 'my-user-id', handle: <some DDS handle object>, status: "resolved" }
// and they can be removed:
comments.removeIntervalById(comment.getIntervalId());
间粘度
"Stickness"是指文本被插入到任一文字上时间隔的行为 中间的边。 “ 粘接” 间隔是扩展为包含文本的间隔 插入与之直接相邻.
“ 启动粘度” 间隔是只扩展为包含插入到 开始。 "端粘"的间隔相同,但文字方面 插入到尾端旁边.
例如,让我们看看弦"abc". 如果我们有一个间隔 字符"b",当插入文字到其两侧时会发生什么? 在那个 在图下,我们代表一个间隔 直接放在下方 包含的字符.
示例
原始字符串
abc
^
没有粘着
aXbYc
^
间隔不扩展为包含新插入的字符 X 财务报告和审定财务报表 Y。 。 。 .
开始粘着
aXbYc
^^
结束粘性
aXbYc
^^
完全粘着
aXbYc
^^^
具体执行
以上是对粘性概念的抽象语义的描述. 在实践中,这是使用"侧"的概念实施的
对于给定的N字符序列,有2N + 2 个位置可以是: 参考:每个字符前后的位置,以及两个 特殊端点段,在开始前后标出位置 先后结束序列.
在字符之前或之后放置间隔的终点 使终点具有包容性或排他性。 唯一终点 在一个特定方向上意味着该方向的粘性。 是否为终点 包容性或排他性取决于双方,如果是开始还是开始 结束.
鉴于字符串"ABCD":
// Refers to "BC". If any content is inserted before B or after C, this
// interval will include that content
//
// Picture:
// {start} - A[- B - C -]D - {end}
// {start} - A - B - C - D - {end}
collection.add(
{ pos: 0, side: Side.After },
{ pos: 3, side: Side.Before },
IntervalType.SlideOnRemove,
);
// Equivalent to specifying the same positions and Side.Before.
// Refers to "ABC". Content inserted after C will be included in the
// interval, but content inserted before A will not.
// {start} -[A - B - C -]D - {end}
// {start} - A - B - C - D - {end}
collection.add(0, 3, IntervalType.SlideOnRemove);
如果显示第一个间隔,如果删除文本,
// Delete the character "B"
string.removeRange(1, 2);
间隔的起始点会滑到紧接之前的位置 "C",同样的情况也会是这样.
{start} - A[- C -]D - {end}
在此情况下,在“C”之前插入的案文将列入 间隔.
string.insertText(1, "EFG");
随着弦现在,
{start} - A[- E - F - G - C -]D - {end}
注意终点继续与相关字符相伴,但
当字符被删除时。 当删除包含端点的内容时,
After 端点向后移动 Before 结束点向前移动以保持其
边值和包容性/排他性行为.
共享结构
CommunityString是处理协作文本的专门数据结构. 它基于一个更普遍的 序列数据结构但具有使文本工作更容易的额外特性.
除文字外,一个共享String还可以包含标记. 标记可以用来将元数据存储在文本中的位置上,就像一个应该随文本而渲染的图像或流体对象的引用.
标记和文本都作为分块存储在"共享链"中. 文本部分将在修改共享结构时进行分拆并合并,因此长度可变 匹配它们包含的文本内容的长度. 标记片段从未被分拆或合并,并始终有一长.
共享String的长度将是所有文本和标记部分的合并长度. 与其他序列一样,在谈论共享结构中的位置时,我们使用近相和远相的术语. 在一个共享String中最近的位置是0,最远的位置是它的长度. 在比较两个位置时,更相近的位置更接近于0,更相近的位置更接近长度.
间隔对标记
中间端点和标记都执行 参考选项 似乎有相似的功能,所以他们之间的区别 以及你们为什么选择一个或另一个.
使用间隔收集 API 有两个主要好处:
-
高效的空间查询
- 跨区域收集支持跨区域重叠
[start, end]内O(log N) + O(overlap size)时间、地点N是收藏中间隔的总数. 这对于只显示少量文件内容的应用程序可能至关重要. 另一方面,使用标记来执行间隔需要从序列起或起端进行线性扫描,以确定哪些间隔重叠.
- 跨区域收集支持跨区域重叠
-
更多工程学修改 API
- 间距集合在本地支持间隔上进行修改操作,这样可以将间隔的终点移动到序列中不同的位置.
这种操作是原子操作,而有一个标记,必须提交现有位置的删除操作和新位置的插入.
为了实现同样的原子化,这些操作需要利用
SharedSegmentSequence.groupOperationAPI, (英语), 这不太方便用户. 如果提交行动时使用标准插入并删除API,如果删除,数据可能会丢失 操作最终被服务器承认,但插入操作没有.
- 间距集合在本地支持间隔上进行修改操作,这样可以将间隔的终点移动到序列中不同的位置.
这种操作是原子操作,而有一个标记,必须提交现有位置的删除操作和新位置的插入.
为了实现同样的原子化,这些操作需要利用
归属
重要: 归属(Attributiation)目前处于α开发阶段并被标记为内部:在我们获得API形状的反馈时,预期会打破小释放的变化.
共享String支持存储信息,将每个字符位置归属于插入信息的用户以及插入的时间. 此功能默认关闭 . 为启用此功能, 首先确保所有客户端在加载器设置中使用归属政策厂创建:
import { createInsertOnlyAttributionPolicy } from "@fluidframework/merge-tree";
// Use these options in the IContainerContext used to instantiate your container runtime.
const options: ILoaderOptions = {
attribution: {
policyFactory: createInsertOnlyAttributionPolicy,
},
};
确保客户端能够装入包含归属信息的现有文件,
并具体说明应在共享结构一级确定哪些类型的业务.
存储的归属信息可见于 attribution 字段.
属性变化以及插入,使用
createPropertyTrackingAndInsertionAttributionPolicyFactory替换仅插入的工厂.
下一个,启用 "Fluid.Attribution.EnableOnNewFile" 配置旗以开始跟踪新文件的归属信息 .
const { segment, offset } = sharedString.getContainingSegment(5);
const key = segment.attribution.getAtOffset(offset);
// `key` can be used with an IAttributor to recover user/timestamp info about the insertion of the character at offset 5.
// See the @fluid-experimental/attributor package for more details.
注意,因为归属信息只有在收到服务器的确认后才能定稿,
任何关于未取消更改的归属密钥的查询都会返回 LocalAttributionKey。 。 。 .
倾听属性信息的更改(例如,将数据模型与一个 SharedString, (中文(简体) ),
使用"维护"事件进行确认.
有关归属的更多信息,请参见: @ fluid- experental/ atributor README (英语)。 。 。 .
实例
客户最低要求
Fluid框架客户端库支持此文档中的平台. 这些要求有意具有限制性. 在一个主要的版本系列中,我们可以放松这些要求,但我们不能使其更严格. 对于长期支持(LTS)版本,我们可能需要支持这些平台几年.
其他配置可以奏效,但Fluid框架不支持. 如果一个不支持的配置停止工作, 我们不会将此归类为错误 . 要请求支持未列出的配置, 请存档 . 产品组会评估你的要求. 在此问题上,请具体说明配置的现状:
- 配置有效但需要官方支持.
- 配置不起作用,需要更改.
支持的运行时间
- Fluid框架支持节点.js版本22和24同时收到 上游支助。 。 。 .
- 流体框架将停止支持第22版 当上游支持于2027-04-30结束时。 。 。 .
- 流体框架不支持节点.js与
--no-experimental-fetch旗帜.
- Fluid框架支持支持ES2022标准库的现代浏览器.
支持的工具
- 类型脚本 6.0数字 :
- 流体框架支持全部
strict选项. - 设定建设目标(
lib, (中文(简体) ),target改为:ES2022或后会. - 启用
strictNullChecks。 。 。 . - 流体框架不支持 在 TypeScript 6.0 中贬值的配置选项。 。 。 .
- 流体框架不完全支持
exactOptionalPropertyTypes。 。 。 . 如果您启用此选项, 请不要使用in, (中文(简体) ),Reflect.has, (中文(简体) ),Object.hasOwn,或Object.prototype.hasOwnProperty改为Fluid框架类型的窄成员. 这些方法可以错误地排除undefined从可能的值.
- 流体框架支持全部
- 网络软件包 页:1
- 我们不需要一个特定的捆绑者. 其他处理ES模块的捆绑机可以工作,但我们只积极测试网页包.
模块分辨率
在类型脚本中 compilerOptions,使用时 Node16, (中文(简体) ), Node20, (中文(简体) ), NodeNext,或 Bundler 模块分辨率.
这些设置遵循 节点.js v12+无害环境管理 分辨率和加载算法。 。 。 .
不使用 Node10 模块分辨率.
模块格式
- ES模块: 使用ES模块来消耗Fluid框架客户端软件包,包括在Node.js中.
- 常见JS : Fluid Framework在3.0版本或后期不正式支持CommonJS.
贡献准则
你们可以 贡献 通过这些方式实现流体框架:
详细指示改为: repo 文档。 。 。 .
本项目遵循 Microsoft 开源行为守则。 。 。 . 更多信息,改为: 《行为守则》经常被问到的问题。 。 。 . 问题或评论,联系 打开码@ microsoft.com。 。 。 .
这个项目可能包含微软项目的商标或标志,产品,或服务. 这些商标或标志的使用必须遵循微软 商标和品牌准则。 。 。 . 在这个项目的修改后版本中使用微软商标或标志不得造成混淆或暗示微软赞助.
帮助
读取 流体框架文件 用于了解流体框架概念和API.
要求提供文件未载列的资料, 创建问题。 。 。 .
商标
这个项目可能包含微软项目的商标或标志,产品,或服务.
这些商标或标志的使用必须遵循微软的 商标和品牌准则。 。 。 .
在这个项目的修改后版本中使用微软商标或标志不得造成混淆或暗示微软赞助.