Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions .claude/skills/openapi-migration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,13 @@ a public signature.

**Per type, decide:**

- **Which v10 type it maps to, and under what name.** The v10 name is the default, even where the generated one
differs (`OGAttachmentResponse` for `GetOGResponse`, `AppSettings` for `AppResponseFields`). It is not a
requirement: the user may ask for another name, and you may propose one that fits clearly better. Renaming a
v10 type is a break, so propose it with the reason and wait for approval.
- **Which v10 type it maps to, and under what name.** The spec's name is the default, unless it is awkward or
describes something else, and sibling calls share one scheme: `UpdateChannelPartialResponse` and
`UpdateMemberPartialResponse` replaced v10's `PartialUpdateChannelResponse` and `PartialUpdateMemberResponse`,
and `updateMemberPartial` replaced `partialMemberUpdate`. v10's name stays where the generated one does not fit
(`OGAttachmentResponse` for `GetOGResponse`, `AppSettings` for `AppResponseFields`), and names an earlier group
settled stand. Renaming a v10 type or method is a break: list it with the reason, wait for approval, and call it
out in the PR description.
- **Which fields it exposes.** v10's fields, with v10's defaults. A field only the generated type has
(`AppResponseFields.id`, the twelve extra fields on `GetOGResponse`) stays out; exposing it later is additive. A
field the spec marks optional is nullable by default, even if v10 typed it non-null; keeping it non-null needs a
Expand Down Expand Up @@ -249,7 +252,7 @@ packages/stream_chat/lib/src/

- **The role decides the folder.** An envelope goes in `response/` (`CreateUserGroupResponse`). A type the caller
passes in to shape a request goes in `request/`, whatever its name: `PartialUpdateUserRequest`, and also
`PaginationParams`, `ThreadOptions` and `MemberUpdatePayload`. Every other model stays in `models/`
`PaginationParams` and `ThreadOptions`. Every other model stays in `models/`
(`UserGroup`). No `request/` type exists yet: the first arrives when a group migrates one out of
`lib/src/core/api/requests.dart`.
- **Models and envelopes** are `@freezed` classes with a const constructor and `@override final` fields. Copy
Expand Down
87 changes: 86 additions & 1 deletion migrations/v11-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ onto Stream's OpenAPI-generated API client.
- [File Upload](#file-upload)
- [User Groups](#user-groups)
- [Link Previews](#link-previews)
- [Partial Updates](#partial-updates)
- [Migration Checklist](#migration-checklist)
- [For AI Agents](#for-ai-agents)
- [Contributing to this guide](#contributing-to-this-guide)
Expand Down Expand Up @@ -78,6 +79,7 @@ from the spec, so don't subclass them or depend on their private constructors.
| [**App Settings**](#app-settings) | `getAppSettings` returns a `Result<AppSettingsResponse>` instead of throwing; `GetAppSettingsResponse` is renamed `AppSettingsResponse` |
| [**Guest Users**](#guest-users) | `connectGuestUser` is unchanged; `StreamChatApi.guest` and `ConnectGuestUserResponse` are removed |
| [**File Upload**](#file-upload) | Uploads return a `Result<UploadedFile>` and deletes a `Result<void>` instead of throwing; `AttachmentFileUploaderProvider` receives a `Dio` |
| [**Partial Updates**](#partial-updates) | Channel and member partial updates — `updatePartial`, `updateName`, `updateImage`, slow mode, pin and archive — return a `Result` instead of throwing; their responses take the API's names, `UpdateChannelPartialResponse` and `UpdateMemberPartialResponse`, and `partialMemberUpdate` becomes `updateMemberPartial` |
| _(filled in per feature as PRs land)_ | |

---
Expand Down Expand Up @@ -219,6 +221,23 @@ search-and-replace you can apply directly. `Kind` is one of `renamed`, `removed`
| `SendAttachmentResponse`, `SendFileResponse`, `SendImageResponse`, `UploadImageResponse`, `UploadFileResponse` | `UploadedFile` (`stream_core`) | `removed` | `.file` becomes `.fileUrl`; `.thumbUrl` is unchanged; `duration` is gone |
| `AttachmentFileUploaderProvider` = `AttachmentFileUploader Function(StreamHttpClient)` | `AttachmentFileUploader Function(Dio)` | `retyped` | Receives the client's `Dio`; `StreamAttachmentFileUploader(dio)` builds the default |
| `StreamChatApi.fileUploader`, `StreamChatApi(attachmentFileUploaderProvider:)` | `StreamChatClient(attachmentFileUploaderProvider:)` | `removed` | The uploader belongs to `StreamChatClient` |
| `PartialUpdateChannelResponse` | `UpdateChannelPartialResponse` | `renamed` | The name of the API's response. Same fields: `duration`, `channel` and `members` |
| `PartialUpdateMemberResponse` | `UpdateMemberPartialResponse` | `renamed` | The name of the API's response. Same fields: `duration` and `channelMember` |
| `StreamChatClient.partialMemberUpdate` | `StreamChatClient.updateMemberPartial` | `renamed` | The name of the API's operation, matching `updateChannelPartial` |
| `StreamChatClient.enableSlowdown` / `disableSlowdown` | `StreamChatClient.enableSlowMode` / `disableSlowMode` | `renamed` | Matches `Channel.enableSlowMode` / `disableSlowMode` and `ChannelCapability.slowMode` |
| `StreamChatClient.updateChannelPartial` / `enableSlowdown` / `disableSlowdown` → `Future<PartialUpdateChannelResponse>` | `updateChannelPartial` / `enableSlowMode` / `disableSlowMode` → `Future<Result<UpdateChannelPartialResponse>>` | `retyped` | Returns a `Result` instead of throwing |
| `Channel.updatePartial` / `updateName` / `updateImage` / `enableSlowMode` / `disableSlowMode` → `Future<PartialUpdateChannelResponse>` | `Future<Result<UpdateChannelPartialResponse>>` | `retyped` | Returns a `Result` instead of throwing. Calling one before the channel is initialized still throws a `StateError` |
| `StreamChatClient.partialMemberUpdate` / `pinChannel` / `unpinChannel` / `archiveChannel` / `unarchiveChannel` → `Future<PartialUpdateMemberResponse>` | `Future<Result<UpdateMemberPartialResponse>>` | `retyped` | Returns a `Result` instead of throwing |
| `Channel.pin` → `Future<Member>`, `unpin` / `archive` / `unarchive` → `Future<Member?>` | `Future<Result<UpdateMemberPartialResponse>>` | `retyped` | Returns a `Result` instead of throwing, and answers the whole response: read the member off `.channelMember`. Calling one before the channel is initialized still throws a `StateError` |
| `PartialUpdateChannelResponse.channel` (`ChannelModel`) | `UpdateChannelPartialResponse.channel` (`ChannelModel?`) | `retyped` | Nullable, as the API declares it |
| `PartialUpdateChannelResponse.members` (`List<Member>?`) | `UpdateChannelPartialResponse.members` (`List<Member>`) | `retyped` | Always present, empty when the channel has no members; drop any `?.` or `?? []` |
| `PartialUpdateMemberResponse.channelMember` (`Member`) | `UpdateMemberPartialResponse.channelMember` (`Member?`) | `retyped` | Nullable, as the API declares it |
| `PartialUpdateChannelResponse.duration` / `PartialUpdateMemberResponse.duration` (`String?`) | `String` | `retyped` | Always present; drop any `!` or `?? ''` |
| `PartialUpdateChannelResponse.fromJson` / `PartialUpdateMemberResponse.fromJson` | — | `removed` | The responses are plain classes; construct them directly |
| `PartialUpdateChannelResponse()..channel = …`, `PartialUpdateMemberResponse()..channelMember = …` and their other setters | `UpdateChannelPartialResponse(duration: …, channel: …)` / `UpdateMemberPartialResponse(duration: …, channelMember: …)` | `retyped` | Plain classes with a const constructor and final fields |
| `PartialUpdateChannelResponse` / `PartialUpdateMemberResponse` identity `==` | value `==`, plus `copyWith` | `retyped` | Two instances with the same fields are now equal |
| `StreamChatApi.channel.updateChannelPartial` / `enableSlowdown` / `disableSlowdown` / `updateMemberPartial` | the `StreamChatClient` methods | `removed` | The endpoints moved to the generated client |
| `MemberUpdatePayload(pinned: true).toJson()` / `MemberUpdateType.pinned.name` | `{'pinned': true}` / `'pinned'` | `removed` | Same for `archived`. Or call `pinChannel` / `unpinChannel` / `archiveChannel` / `unarchiveChannel` |
| _(more added per feature as PRs land)_ | | | |

---
Expand Down Expand Up @@ -411,7 +430,7 @@ counterpart: `code` is now a `StreamErrorCode` (an extension type over `int`, wi
## Offline Cache

If you use `stream_chat_persistence`, the local database is **rebuilt from empty** the first time your app runs
on v11. The Drift schema version moves from `1035` to `1102`, and the upgrade strategy drops and recreates every
on v11. The Drift schema version moves from `1036` to `1104`, and the upgrade strategy drops and recreates every
table rather than migrating rows.

Everything held on disk is discarded: channels, messages, members, reads, drafts, locations, polls, poll votes
Expand Down Expand Up @@ -608,6 +627,72 @@ result.fold(

**`duration` is a non-nullable `String`**, where v10 typed it `String?`.

### Partial Updates

**Channel and member partial updates return a `Result` instead of throwing.** On the channel side that covers
`StreamChatClient.updateChannelPartial`, `enableSlowMode` and `disableSlowMode` (v10's `enableSlowdown` and
`disableSlowdown`), and `Channel.updatePartial`,
`updateName`, `updateImage`, `enableSlowMode` and `disableSlowMode`. On the member side it covers
`StreamChatClient.updateMemberPartial` (v10's `partialMemberUpdate`), `pinChannel`, `unpinChannel`,
`archiveChannel` and `unarchiveChannel`, and `Channel.pin`, `unpin`, `archive` and `unarchive`. The full update,
`updateChannel` and `Channel.update`, still throws. A `try`/`catch` around a partial update still compiles, but
no longer catches a failed call: read the returned `Result` instead.

```dart
// v10
try {
final response = await channel.updatePartial(set: {'name': 'Support'});
print(response.channel.name);
} on StreamChatException catch (e) {
report(e);
}

// v11
final result = await channel.updatePartial(set: {'name': 'Support'});
result.fold(
onSuccess: (response) => print(response.channel?.name),
onFailure: (error, _) => report(error),
);
```

**The responses take the API's names.** `PartialUpdateChannelResponse` is now `UpdateChannelPartialResponse`, and
`PartialUpdateMemberResponse` is now `UpdateMemberPartialResponse`. Their fields keep their names.

**`StreamChatClient.enableSlowdown` and `disableSlowdown` are renamed `enableSlowMode` and `disableSlowMode`,** the
names `Channel` already used.

**`Channel.pin`, `unpin`, `archive` and `unarchive` answer the whole response**, where v10 returned the member:

```dart
// v10
final member = await channel.pin();

// v11
final result = await channel.pin();
final member = result.getOrThrow().channelMember;
```

**`UpdateChannelPartialResponse.channel` and `UpdateMemberPartialResponse.channelMember` are nullable**, where v10
typed them non-null. `UpdateChannelPartialResponse.members` goes the other way: it is a `List<Member>`, empty when
the channel has none, where v10 typed it `List<Member>?`.

**The responses no longer decode JSON.** They are plain classes; build them with their constructors —
`UpdateChannelPartialResponse(duration: '0ms', channel: channel)` where v10 wrote
`PartialUpdateChannelResponse()..channel = channel`. `duration` is a non-nullable `String`.

**`StreamChatApi.channel.updateChannelPartial`, `enableSlowdown`, `disableSlowdown` and `updateMemberPartial` are
removed.** Call them on `StreamChatClient`.

**`MemberUpdatePayload` and `MemberUpdateType` are removed.** They only named `pinned` and `archived`; pass the map
directly — `updateMemberPartial(channelId: …, channelType: …, set: {'pinned': true})` — or call `pinChannel`,
`archiveChannel` and their counterparts.

> **Why:** the endpoints moved onto the generated client, which returns a `Result` for every call; the field types
> follow what the API declares, and the names follow the API's so the channel and member updates share one scheme.
> `Channel.pin` and its siblings answer the whole response because every migrated write returns its envelope, so a
> field the API adds later reaches you without another break. `MemberUpdatePayload` and `MemberUpdateType` named
> only two of the keys a membership accepts, so a plain map replaces them.

### Moderation

**Muting, banning and flagging return a `Result` instead of throwing**, on both `StreamChatClient`
Expand Down
Loading
Loading