stream-feeds-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStream Feeds - v2 to v3 sync mapping
Stream Feeds - v2 到 v3 同步映射
For CLI commands or routing to another Stream skill, read
.
../stream/SKILL.mdProduce the object for an app's v2 -> v3 sync configuration by looking
at what the app's v2 data actually contains, rather than at what anyone believes
it contains.
mappingOne mapping serves both migration paths - the live syncer and the bulk exporter
read the same configuration. Getting it right once fixes both; getting it wrong
breaks both in the same way.
对于 CLI 命令或将任务路由到另一个 Stream skill,请阅读
。
../stream/SKILL.md为应用的 v2 -> v3 sync 配置生成 object,方法是查看应用的 v2 数据实际包含什么,而不是依据任何人的说法。
mapping一份 mapping 同时适用于两条 migration path——live syncer 与 bulk exporter 读取同一份配置。正确配置一次即可修复两者;配置错误也会以相同方式破坏两者。
Workflow
工作流
1. Get credentials into the environment
1. 将凭据加载到环境中
Use to write the v2 app's credentials to an env file. Read
for app selection and output options. Use so
the file contains both and , which the
sampler expects.
getstream envgetstream env -h--target webSTREAM_API_KEYSTREAM_API_SECRETLoad that file when running the sampler below. Do not print its contents or put
credential values in chat or tool commands.
使用 将 v2 应用的凭据写入一个 env file。阅读 以了解 app 选择和输出选项。使用 ,使文件同时包含 和 ,sampler 需要这两个值。
getstream envgetstream env -h--target webSTREAM_API_KEYSTREAM_API_SECRET运行下方 sampler 时加载该文件。不要打印其内容,也不要将凭据值放入 chat 或 tool command 中。
2. Pick the target
2. 选择目标环境
Default to production - . Use it unless the
user names a different environment (EU, dedicated, staging), in which case pass
.
https://api.stream-io-api.com--base-url <that URL>Do not raise alternative environments on your own; production is the assumed
target and the user will say so if they mean otherwise.
默认使用 production - 。除非用户指定其他环境(EU、dedicated、staging),否则使用该地址;若用户指定,则传入 。
https://api.stream-io-api.com--base-url <that URL>不要自行提出其他环境;默认目标为 production,如果用户不是这个意思会自行说明。
3. Fetch the sample
3. 获取样本
bash
set -a
. "<env-file>"
set +a
python3 <skill-dir>/fetch_sample.py --out v3sync-sample.json<env-file>getstream env<skill-dir>SKILL.mdgetstream skills- (Cursor, Codex)
.agents/skills/stream-feeds-migration - (Claude Code)
.claude/skills/stream-feeds-migration
Glob to settle which.
{.agents,.claude}/skills/stream-feeds-migration/SKILL.mdThe key and secret are read from and . The
script takes no flag by design - a secret passed as an argument
would land in the process list and the shell history, which is exactly what the
environment variable avoids. ( exists, since the key is not
sensitive.) The sample is written to the working directory, which is where you
want it.
STREAM_API_KEYSTREAM_API_SECRET--secret--api-keyStandard library only - no pip install. It calls two server-side-only endpoints:
- - up to 100 activities, drawn from several periods (the newest, then at or before 7d, 30d, 180d, 1y and 2y ago)
GET /api/v1.0/migration/sample/activities - - 5 reactions per distinct kind
GET /api/v1.0/migration/sample/reactions
Both return raw stored payloads: unflattened , unmodified
reaction . That is the point - you are looking for the real field names
and nesting.
extra_contextdataA 401 or 403 means the secret is wrong, or the token was built as a user token
rather than a server-side one. A 404 means these endpoints are not available on
the app's deployment yet; Stream support can
confirm.
An empty array alongside a is worth calling out rather than
working around: the app is either genuinely empty or its activity store cannot
serve this scan. Reactions carry an , so reactions in the sample
prove activities exist and point to the second explanation.
activities200activity_idbash
set -a
. "<env-file>"
set +a
python3 <skill-dir>/fetch_sample.py --out v3sync-sample.json<env-file>getstream env<skill-dir>SKILL.mdgetstream skills- (Cursor、Codex)
.agents/skills/stream-feeds-migration - (Claude Code)
.claude/skills/stream-feeds-migration
使用 Glob 确定实际位置。
{.agents,.claude}/skills/stream-feeds-migration/SKILL.mdkey 和 secret 从 与 读取。脚本特意不提供 flag——作为参数传入的 secret 会出现在 process list 和 shell history 中,而这正是使用 environment variable 所要避免的。(由于 key 不敏感,因此提供了 。)样本会写入当前 working directory,这正是需要它所在的位置。
STREAM_API_KEYSTREAM_API_SECRET--secret--api-key仅使用标准库——无需 pip install。它会调用两个仅限 server-side 使用的 endpoint:
- - 最多返回 100 条 activities,采样自多个时间段(最新时段,以及 7d、30d、180d、1y 和 2y 之前或对应时间点)
GET /api/v1.0/migration/sample/activities - - 每种不同 kind 返回 5 条 reactions
GET /api/v1.0/migration/sample/reactions
二者都返回原始存储 payload:未 flattened 的 、未经修改的 reaction 。这正是重点——你需要查看真实的 field name 和 nesting。
extra_contextdata返回 401 或 403 意味着 secret 不正确,或者 token 是以 user token 而非 server-side token 构建的。返回 404 意味着这些 endpoint 尚未在该应用的 deployment 上可用;Stream support 可以进一步确认。
如果响应为 ,但 array 为空,值得明确指出,而不是绕过它:应用要么确实为空,要么其 activity store 无法提供此次扫描。Reactions 带有 ,因此样本中存在 reactions 证明 activities 确实存在,也指向后一种解释。
200activitiesactivity_idRead what the activity sample left out
了解 activity sample 未覆盖的内容
Recent deployments also return ,
and , and the script keeps them in the sample
file. Read them before the activities, because they say how representative the
activities are:
reaction_reference_countreaction_referencesslices- - how many target-feed copies of reactions the sample left out. v2 writes one of these when a reaction has
reaction_reference_count. You can recognise them by atarget_feedsofforeign_idand anreaction:<id>holding anextra_context.reactionref. They carry no post fields, so they say nothing about the mapping; up to 3 are inSR:for reference.reaction_references - - one entry per period, with
slices,scannedandposts.reaction_referencesis the period's cutoff; the newest slice has none.beforemeans the period was re-read with a larger scan because reaction references crowded it.extended- A slice whose is small next to
postswas crowded: posts from that period are under-represented. Say so.scanned - A slice with held no activities inserted in that window. Periods are by insert time, not by the activity's
scanned: 0, so data imported into v2 recently all lands in the newest slice.time
- A slice whose
When the older slices are empty on an app that has existed for a while, the
sample cannot show what older posts look like. Ask whether the post format ever
changed (different field names for text, media or ids), and say the mapping only
covers the shape the sample shows.
If these keys are missing, the deployment predates them: the activities are the
newest 100 and can include reaction references. Leave those out yourself by the
shape above before analysing.
近期 deployment 还会返回 、 和 ,脚本会将它们保留在 sample file 中。先阅读它们,再阅读 activities,因为它们能说明 activities 的代表性:
reaction_reference_countreaction_referencesslices- - sample 中省略了多少条 reaction 的 target-feed copies。当 reaction 带有
reaction_reference_count时,v2 会写入这种记录。可以通过值为target_feeds的reaction:<id>,以及包含foreign_idref 的SR:来识别它们。它们不包含 post fields,因此无法说明 mapping;extra_context.reaction中最多会保留 3 条供供参考。reaction_references - - 每个 period 一条记录,包含
slices、scanned和posts。reaction_references是该 period 的 cutoff;最新的 slice 没有该值。before表示由于 reaction references 挤占了扫描空间,该 period 曾以更大的 scan 重新读取。extended- 如果某个 slice 的 相比
posts很少,说明该 period 的样本空间被挤占,来自该 period 的 posts 占比不足。应当明确说明这一点。scanned - 的 slice 表示该窗口内没有插入任何 activities。Period 按 insert time 划分,而不是按 activity 的
scanned: 0划分,因此最近批量导入 v2 的数据都会落在最新的 slice 中。time
- 如果某个 slice 的
如果一个已运行较久的应用,其较旧 slices 为空,则样本无法展示较旧 posts 的形态。询问 post format 是否曾经变化,例如 text、media 或 ids 是否使用过不同的 field names,并说明 mapping 仅覆盖样本所展示的形态。
如果缺少这些 key,说明 deployment 的版本早于相关功能:activities 是最新的 100 条,并且可能包含 reaction references。分析前,应根据上述形态自行将这些记录排除。
Cover every feed group
覆盖每个 feed group
The sample is app-wide: it takes the newest activities wherever they were
written, and does not say which feed group each came from. A feed group with
little or old traffic can be missing entirely. One mapping serves the whole app,
so a group whose activities are written differently (a backend job, a
notification feed, an older client) gets a mapping fitted to the others.
Recent deployments list the app's feed groups in (name and
type); on older ones, ask the user which groups the app uses. Either way the
sample does not say which group each activity came from, so ask whether any
group is written by a different code path. For each group the sample may have
missed, read a feed or two of it directly with (read-only,
repeatable):
feed_groups--feedbash
python3 <skill-dir>/fetch_sample.py --out v3sync-sample.json \
--feed notification:<user-id> --feed timeline:<user-id>The activities land under in the same file, normalised to the
sample's shape (custom fields under , plus a field), so
analyse them the same way. Aggregated and notification feeds are unwrapped into
their activities. They also hold the target-feed copies of reactions; leave
those out as above. A feed that fails (for example an unknown group) is
recorded with its error and does not stop the run.
feed_samplesextra_contextfeed样本是 app-wide 的:它会获取写入位置不限的最新 activities,但不会说明每条 activity 来自哪个 feed group。流量较少或较旧的 feed group 可能完全缺失。一份 mapping 服务于整个应用,因此,如果某个 group 的 activities 采用了不同的写入方式(例如 backend job、notification feed 或较旧的 client),它最终只能套用适配其他 group 的 mapping。
近期 deployment 会在 中列出应用的 feed groups(name 和 type);对于较旧的 deployment,应询问用户应用使用了哪些 groups。无论哪种情况,样本都不会说明每条 activity 来自哪个 group,因此应询问是否有 group 使用不同的 code path 写入。对于样本可能遗漏的每个 group,可使用 直接读取其中一两个 feeds(只读、可重复):
feed_groups--feedbash
python3 <skill-dir>/fetch_sample.py --out v3sync-sample.json \
--feed notification:<user-id> --feed timeline:<user-id>Activities 会进入同一文件中的 ,并被规范化为样本的形态(custom fields 位于 下,另有一个 field),因此可采用相同方式分析。Aggregated feeds 和 notification feeds 会被解包为其 activities。它们还包含 reactions 的 target-feed copies;应按上述方式排除。某个 feed 读取失败时,其错误会被记录,但不会中断运行。
feed_samplesextra_contextfeed4. Read the integration code (when you have it)
4. 读取集成代码(如果代码可用)
The sample shows what is stored; the app's v2 integration code shows every way
it gets written - including the paths the sample is weakest on: low-traffic
feed groups, rarely used reaction kinds, child reactions, how users are
created, and whether is unique. If the user's repository is in the
working directory (or they point you at it), read it. If not, ask once whether
it is available; the skill works without it.
foreign_idFollow :
code-inventory.md- Run to list every v2 write call site (read-only; secrets in snippets are redacted - never quote one from the code yourself).
<skill-dir>/scan_v2_calls.py --root <repo> --out v2-code-scan.json - Read the call sites and build the inventory: one row per write path,
with its feed group(s), to-targets, fields, construction, reaction kinds and data shape, child reactions, and user fields.
foreign_id - Check history for fields that changed (): older activities still carry the old format.
git log -S'<field>' - Cross-check the inventory against the sample. Agreement confirms a key;
code-only evidence is a rare or new path (map it, say so, and read a feed of
it with ); sample-only evidence is another writer or an older version (ask). The sample stays the source of truth - the code is rarely the only writer.
--feed
样本展示存储的数据;应用的 v2 integration code 展示数据的所有写入方式——包括样本最薄弱的路径:低流量 feed groups、少用的 reaction kinds、child reactions、users 的创建方式,以及 是否唯一。如果用户的 repository 位于 working directory 中,或用户明确指出其位置,请读取它。如果不可用,则询问一次是否可以获得;此 skill 没有代码也可以工作。
foreign_id按照 操作:
code-inventory.md- 运行 ,列出每个 v2 write call site(只读;snippets 中的 secrets 已脱敏——绝不要自行引用代码中的 secret)。
<skill-dir>/scan_v2_calls.py --root <repo> --out v2-code-scan.json - 阅读 call sites 并构建 inventory:每条 write path 一行,包含 feed group(s)、to-targets、fields、construction、reaction kinds 和 data shape、child reactions,以及 user fields。
foreign_id - 检查 history 中发生变化的 fields():较旧的 activities 仍可能携带旧 format。
git log -S'<field>' - 将 inventory 与 sample 交叉核对。二者一致可确认某个 key;只有 code 证据表示一条少见或新路径(为其建立 mapping、说明这一点,并使用 读取其某个 feed);只有 sample 证据表示还存在另一个 writer 或较旧版本(询问用户)。Sample 始终是 source of truth——code 很少是唯一的 writer。
--feed
5. Analyze
5. 分析
Read and work through the checklist below. Read
for the full key catalogue, defaults, exact
resolution rules, and worked examples of the common shapes - do not guess key
names or defaults from memory.
v3sync-sample.jsonmapping-keys.mdThe single most common mistake is over-configuring. Every key has a default that
is right for a conventional v2 app. Only emit a key when the sample shows the
app deviates from that default. An empty mapping is a valid answer.
阅读 ,并逐一完成下面的检查清单。阅读 以获取完整的 key catalogue、defaults、精确的 resolution rules,以及常见形态的 worked examples——不要凭记忆猜测 key names 或 defaults。
v3sync-sample.jsonmapping-keys.md最常见的错误是过度配置。每个 key 都有一个适用于常规 v2 应用的正确 default。仅当样本显示应用偏离该 default 时,才输出对应 key。空 mapping 也是有效答案。
6. Emit the mapping
6. 输出 mapping
Produce only the object, ready to paste into the app's existing
sync configuration. Do not emit the surrounding configuration (,
, , , , ); those
are deployment concerns, they are not derived from the sample, and restating
them with placeholder credentials invites someone deploying a config with a
still in it.
mappingenabledbase_urlapi_keysecretsample_ratesfeed_groups_synced<v3 app key>Explain each key you set by pointing at the evidence: rows in the sample, and,
when you read the code, the call sites (). Say which keys rest on
code alone.
file:lineFlag explicitly anything you could not determine from the sample - see Limits
of the sample below. Silence there reads as "verified", which it is not.
仅输出可直接粘贴到应用现有 sync 配置中的 object。不要输出外围配置(、、、、、);这些属于 deployment concerns,无法从样本推导,而且使用占位凭据重新输出它们,容易导致有人部署仍包含 的配置。
mappingenabledbase_urlapi_keysecretsample_ratesfeed_groups_synced<v3 app key>设置每个 key 时,请指出其证据来源:样本中的 rows,以及在读取代码后对应的 call sites()。说明哪些 keys 仅依据 code 得出。
file:line明确标记所有无法从 sample 确定的内容——参见下文的 样本的局限。对此保持沉默会让人误以为这些内容已经验证,而事实并非如此。
7. Plan the code port (when you read the code)
7. 规划代码移植(已读取代码时)
If you read the integration code in step 4, also write a porting plan,
following : one row
per write and read path with its v2 call, its v3 call, the switch-over step it
belongs to (reads move first, every write moves together last), and the
behaviour that changes. Take the v3 calls from the V2 to V3 Code Migration
guide (through ), not from memory.
porting-plan.mdgetstream docsThe plan is a document: do not edit the app's code. Offer to port it; if the
user asks, follow Porting the code in that reference (v3 read paths behind a
read flag, every write path behind one write flag, never writing to both
versions).
如果在步骤 4 中读取了 integration code,还应按照 编写 porting plan:每条 write 和 read path 一行,列出其 v2 call、v3 call、所属的 switch-over step(reads 先迁移,所有 writes 最后一起迁移),以及行为变化。V3 calls 应取自 V2 to V3 Code Migration guide(通过 获取),而不是凭记忆编写。
porting-plan.mdgetstream docs该 plan 是一份文档:不要编辑应用代码。可以主动提出帮助移植;如果用户提出要求,则遵循该 reference 中的 Porting the code(v3 read paths 放在 read flag 后,所有 write paths 放在同一个 write flag 后,绝不同时写入两个版本)。
Analysis checklist
分析清单
Activities
Activities
Look at across the whole sample.
activities[].extra_context- Custom field names - any field whose name collides with a v3 reserved key,
or that should be surfaced at the top level of , needs a rename entry. Fields that are already conventionally named need nothing.
custom - Activity id - does a field hold a stable external id (,
external_id,post_id)? If so,uuidshould point at it. If insteadidis populated and meaningful across the sample,foreign_idis the simpler answer. If neither, omit both and let the v2 UUID carry over. Before recommendingforeign_id: "id", check that no two sampled activities share aforeign_id: "id". v2 allows several activities with the same foreign_id (they differ byforeign_id), but as v3 ids they collide and only one survives. If any repeat, or the sample is too small to tell, ask whether foreign_ids are unique in the app, and keep the v2 UUID when they are not.time - Reshares / replies - a field holding an ref (commonly
SA:<uuid>) is theshared_origin_postfield. Note thatparent_idrefs are also autodetected, so only configure this when the sample shows the ref buried somewhere the autodetection would not reach first.SA: - Text - is the post body under (default, no config) or something else like
text/body/message? Setcontentonly if it differs.text - Attachments - a list of media objects under a custom path means
.
activity_attachments_field - Actor format - check across the sample.
actoris handled natively. A different prefix (SU:<id>) needs aUser:<id>entry. An actor that is a whole JSON blob is a known legacy shape and needs no mapping.replaces - extra_context nesting - decide whether the bag should stay nested under
(default), be renamed, or be flattened (
custom.extra_context). This one is a preference, not something the sample can settle: ask.""
查看整个样本中的 。
activities[].extra_context- Custom field names - 任何名称与 v3 reserved key 冲突,或应显示在 顶层的 field,都需要 rename entry。已经按惯例命名的 fields 无需处理。
custom - Activity id - 是否有 field 保存稳定的 external id(、
external_id、post_id)?如果有,uuid应指向该 field。如果相反,id在整个样本中都有值且具有实际意义,那么foreign_id是更简单的答案。如果两者都没有,则两者都省略,让 v2 UUID 继续沿用。建议foreign_id: "id"前,检查是否有两条 sampled activities 共享foreign_id: "id"。V2 允许多条 activities 使用相同的 foreign_id(它们通过foreign_id区分),但作为 v3 ids 时会发生冲突,最终只能保留一条。如果发现重复,或样本太小而无法判断,应询问应用的 foreign_ids 是否唯一;如果并不唯一,则保留 v2 UUID。time - Reshares / replies - 保存 ref 的 field(通常是
SA:<uuid>)就是shared_origin_postfield。注意,parent_idrefs 也会被自动检测,因此只有当样本显示该 ref 位于自动检测无法首先到达的位置时,才配置此项。SA: - Text - post body 位于 (default,无需配置)下,还是位于
text、body或message下?只有不同时才设置content。text - Attachments - 如果自定义路径下存在 media objects list,则需要 。
activity_attachments_field - Actor format - 检查整个样本中的 。
actor会被原生处理。不同前缀(如SU:<id>)需要一条User:<id>entry。如果 actor 是完整的 JSON blob,则属于已知 legacy shape,无需 mapping。replaces - extra_context nesting - 决定该 bag 应保留在 下(default)、进行 rename,还是 flatten(
custom.extra_context)。这属于 preference,并非样本能够确定的事项:询问用户。""
Reactions
Reactions
Look at first - that list contains the reaction kinds represented in
the sample.
kinds-
Classify every kind into exactly one of: comment, bookmark, comment-bookmark, or plain reaction. Then set,
comments,bookmarksaccordingly. A kind you leave out of all three is a plain reaction, which is often correct.comments_bookmarks -
Remember the defaults:already covers
comments. Kind matching is case-insensitive, so["comment", "reply"]andCOMMENTare those same kinds. If those are the app's only comment kinds, do not restate them.REPLY -
Comment text - inspect theof every comment-kind reaction. The default is
data. If that field is missing and the text is underdata.messageortext, setbodyto that key (comment_field, not"text"). A comment kind with no text at the configured field is dropped. It is not kept as a reaction."data.text" -
Replies - is the parent comment id in the top-level(the default, no config needed) or nested in
parent? If nested, setdatato that envelope path. Confirm against a non-reply too: a top-level comment must leave that path empty, or every comment would be treated as a reply.parent_id_field -
Reactions on comments - a non-comment reaction that targets a comment needs. Two shapes are common:
reaction_comment_id_field- the comment id in its (for example
data)data.comment_id - v2's native child reaction, created with : a non-comment kind, often
reactions.addChild, whose top-levellikeis set. This needsparent."reaction_comment_id_field": "parent"
Without it, a like on a comment lands in v3 as a like on the activity. Reactions with no parent and no comment id are unaffected, so a sample mixing both is fine. - the comment id in its
-
Kind renames - only if the app should use different type names in v3.
-
Attachments -if comment attachments are not under
attachments_field.data.attachments
Watch for one kind carrying two incompatible shapes - e.g. some rows
holding a comment id at one path and others at a different one. The single-path
keys (, ) cannot serve both. Say
which rows the mapping will not cover instead of picking one silently.
likeparent_id_fieldreaction_comment_id_fieldSome apps carry legacy layout flags - see Legacy layout flags in
. Never set one.
mapping-keys.md先看 ——该列表包含样本中出现的 reaction kinds。
kinds-
将每种 kind 准确归入以下一类:comment、bookmark、comment-bookmark 或 plain reaction。然后相应设置、
comments、bookmarks。未列入三者任何一项的 kind 属于 plain reaction,这通常是正确的。comments_bookmarks -
记住 defaults:已包含
comments。Kind 匹配不区分大小写,因此["comment", "reply"]和COMMENT就是相同的 kinds。如果这些是应用唯一使用的 comment kinds,则无需再次列出。REPLY -
Comment text - 检查每种 comment-kind reaction 的。Default 是
data。如果该 field 不存在,而 text 位于data.message或text下,则将body设置为该 key(comment_field,而不是"text")。如果某种 comment kind 在配置的 field 下没有 text,该 reaction 会被丢弃,而不会作为普通 reaction 保留。"data.text" -
Replies - parent comment id 位于顶层(default,无需配置)中,还是嵌套在
parent中?如果嵌套在data中,将data设置为对应的 envelope path。还要对照一条非 reply 进行确认:顶层 comment 必须让该路径保持为空,否则每条 comment 都会被当作 reply。parent_id_field -
Reactions on comments - 目标是 comment 的 non-comment reaction 需要。常见形态有两种:
reaction_comment_id_field- comment id 位于其 中(例如
data)data.comment_id - v2 的原生 child reaction,通过 创建:一种 non-comment kind,通常为
reactions.addChild,其顶层like已被设置。这需要parent。"reaction_comment_id_field": "parent"
如果不进行该映射,针对 comment 的 like 在 v3 中会变成针对 activity 的 like。没有 parent 和 comment id 的 reactions 不受影响,因此样本中混合存在两种情况是正常的。 - comment id 位于其
-
Kind renames - 仅当应用在 v3 中应使用不同的 type names 时才设置。
-
Attachments - 如果 comment attachments 不在下,则设置
data.attachments。attachments_field
注意某一种 kind 携带两种不兼容的 shape——例如部分 rows 在一个路径中包含 comment id,另一些则在不同路径中包含。Single-path keys(、)无法同时适配两者。应明确说明 mapping 无法覆盖哪些 rows,而不是默默选择其中一种。
likeparent_id_fieldreaction_comment_id_field一些应用带有 legacy layout flags——参见 中的 Legacy layout flags。绝不要设置这类 flag。
mapping-keys.mdUsers
Users
The sample endpoints return activities and reactions, not users, so user
field mapping cannot be inferred from it. The defaults already cover the
common v2 conventions:
- name: , then
nameusername - image: , then
image, thenprofileImageavatar
So only ask when display name or avatar may be stored under some other key.
Then set / , remembering those two keys read v3-key-to-v2-source,
backwards from every other string key. A list () tries each source in order.
nameimage"image": ["avatar_url", "profileImage"]Sample endpoints 返回 activities 和 reactions,而不返回 users,因此完全无法从样本推断 user field mapping。Defaults 已覆盖常见的 v2 约定:
- name: ,然后是
nameusername - image: ,然后是
image,最后是profileImageavatar
因此,只有当 display name 或 avatar 可能存储在其他 key 下时才询问用户。然后设置 / ,同时记住这两个 keys 的读取方向是从 v3 key 指向 v2 source,与其他所有 string keys 相反。List()会按顺序尝试每个 source。
nameimage"image": ["avatar_url", "profileImage"]Output format
输出格式
The mapping object on its own:
json
{
"bookmarks": ["bookmark"],
"parent_id_field": "data.comment.parent.id",
"attachments_field": "media.files"
}Every key present must be one the sample justified. An empty mapping () is a
valid and correct answer for an app whose v2 data already matches v3
conventions - say so plainly rather than padding it with defaults.
{}Alongside the object, list the keys you deliberately omitted and why (which
default already covers the app's shape). That is what tells the reader the
absence was a decision rather than an oversight.
The omitted list covers only the general-purpose keys. Never include a
legacy layout flag (see ) in that list, in
the mapping, or anywhere else in your response - not as a row, not as an aside,
not even to say it was considered and rejected. They are irrelevant to the app
in front of you, so there is nothing to justify: leave them out silently.
mapping-keys.mdSuggest in the mapping for the initial validation run - it
stamps the live mapping onto of every synced activity -
and remind the user to remove it once the mapping is confirmed.
"debug": truecustom._v3sync_debug单独的 mapping object:
json
{
"bookmarks": ["bookmark"],
"parent_id_field": "data.comment.parent.id",
"attachments_field": "media.files"
}每个存在的 key 都必须得到样本的证明。对于 v2 数据已经符合 v3 conventions 的应用,空 mapping()既有效又正确——应明确说明这一点,而不是用 defaults 填充它。
{}在 object 旁边列出你有意省略的 keys 及其原因(哪个 default 已覆盖应用的形态)。这能让读者知道这些 key 的缺失是一个经过判断的决定,而非疏忽。
省略列表只涵盖 general-purpose keys。绝不要在任何 legacy layout flag(参见 )时将其加入该列表、mapping 或响应中的任何其他位置——不作为 row,不作为旁注,甚至不要说明它曾被考虑后排除。它们与当前应用无关,因此没有什么需要论证:直接忽略即可。
mapping-keys.md建议在 mapping 中加入 ,用于首次 validation run——它会将 live mapping 标记到每条 synced activity 的 上——并提醒用户在确认 mapping 后将其移除。
"debug": truecustom._v3sync_debugLimits of the sample
样本的局限
State whichever of these apply alongside the generated mapping. They are the
difference between "this mapping is correct" and "this mapping fits the sample":
- Activities are at most 100, by insert time. The sample reaches back up to
2 years, but a field that only older activities carry can still be missing:
when older periods are empty or crowded (see ), or when data was bulk-loaded recently. One mapping serves one post format. If older posts store text or media under other fields, they arrive in v3 with that content in
slicesinstead. Apps whose schema changed over time are exactly the ones where this bites.custom - Reactions are drawn from a bounded recent window. A kind that has not
been used lately can be missing from entirely, so the vocabulary may be incomplete on a long-lived app.
kinds - Users are not sampled at all. The defaults cover /
nameandusername/image/profileImage; any other key has to be confirmed separately.avatar - Code is not the only writer. When the mapping used the integration code, say which keys rest on code alone, and that other services, mobile clients writing with user tokens, scripts and older app versions may write shapes the code does not show.
- Feed groups are not covered evenly. The sample does not say which group
an activity came from. Name the groups you read with , and the ones neither the sample nor
--feedcovered.--feed - Rare shapes hide in the tail. A field present on 1% of activities may not show up in 100 rows. Where a mapping decision hinges on a field's presence, say which sampled rows carried it.
- The sample moves. Re-running later can rotate rows out under the 5-per-kind cap and bring new shapes in, so a second run is a cheap way to firm up a decision that rested on a single row.
Before finalizing, check each key you emitted against the worked examples in
. Matching a documented shape is good
evidence the path and nesting are right; not matching one is worth a second look
at the sample.
mapping-keys.md在生成的 mapping 旁说明以下适用项。它们决定了“该 mapping 是正确的”与“该 mapping 仅适用于样本”之间的区别:
- Activities 最多只有 100 条,并且按 insert time 采样。 样本最多回溯 2 年,但只有较旧 activities 才携带的 field 仍可能缺失:当较旧 periods 为空或过于拥挤时(参见 ),或者数据是最近批量加载的。一份 mapping 只对应一种 post format。如果较旧 posts 将 text 或 media 存储在其他 fields 下,这些内容迁移到 v3 后会进入
slices。Schema 随时间发生变化的应用最容易遇到这一问题。custom - Reactions 取自一个有限且较新的时间窗口。 某种近期未使用的 kind 可能完全不出现在 中,因此对于长期运行的应用,reaction vocabulary 可能并不完整。
kinds - 完全不会采样 Users。 Defaults 覆盖 /
name和username/image/profileImage;任何其他 key 都必须单独确认。avatar - Code 并不是唯一的 writer。 当 mapping 使用了 integration code 时,说明哪些 keys 仅依据 code 得出,并指出其他 services、使用 user tokens 写入的 mobile clients、scripts 和较旧 app versions 可能写入代码中未展示的形态。
- Feed groups 的覆盖并不均衡。 样本不会说明 activity 来自哪个 group。列出通过 读取过的 groups,以及样本和
--feed均未覆盖的 groups。--feed - Rare shapes 会隐藏在数据尾部。 仅出现在 1% activities 上的 field,可能不会出现在 100 条 rows 中。如果某个 mapping 决策取决于 field 是否存在,请说明哪些 sampled rows 携带了它。
- 样本会发生变化。 以后重新运行时,rows 可能在每种 kind 最多 5 条的上限下轮换,同时带来新的 shapes。因此,如果某项决策只依赖一条 row,再运行一次是进一步确认该决策的低成本方法。
最终确定前,将每个输出的 key 与 中的 worked examples 进行核对。与已记录形态一致,是 path 和 nesting 正确的良好证据;如果不一致,则应再次检查样本。
mapping-keys.mdHand-off
交接
- SDK questions about the v3 Feeds API (what a v3 activity, comment, or
bookmark looks like in a given SDK) -> .
getstream docs - Porting the app's existing v2 code -> the porting plan (step 7); edit code only when the user asks, following the plan.
- Building or updating the app against v3 once the mapping is settled ->
the platform pack for that app (,
stream-react,stream-swift,stream-android,stream-react-native).stream-flutter
- 关于 v3 Feeds API 的 SDK 问题(给定 SDK 中 v3 activity、comment 或 bookmark 的形态)-> 。
getstream docs - 移植应用现有的 v2 code -> porting plan(步骤 7);仅在用户要求时编辑代码,并遵循该 plan。
- mapping 确定后,针对 v3 构建或更新应用 -> 该应用的 platform pack(、
stream-react、stream-swift、stream-android、stream-react-native)。stream-flutter
Support
支持
If the user asks for support or how to contact someone, direct them to
getstream.io/contact.
如果用户寻求支持或询问如何联系相关人员,请引导他们访问
getstream.io/contact。