Skip to content
Merged
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
2 changes: 2 additions & 0 deletions scripts/apply-custom-data-types.mts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ const CUSTOM_DATA_MAPPING: Record<string, string> = {
// CustomChannelData
ChannelInput: 'CustomChannelData',
ChannelInputRequest: 'CustomChannelData',
ChannelMetadata: 'CustomChannelData',
ChannelResponse: 'CustomChannelData',

// CustomMemberData
ChannelMemberPartialResponse: 'CustomMemberData',
ChannelMemberRequest: 'CustomMemberData',
ChannelMemberResponse: 'CustomMemberData',

Expand Down
7 changes: 7 additions & 0 deletions scripts/generate-client.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,11 @@ rm -rf $OUTPUT_DIR

( cd $CHAT_DIR ; make openapi ; make -C projects/chat-manager build; build/chat-manager openapi generate-client --language ts --spec releases/v2/chat-clientside-api.yaml --output $OUTPUT_DIR --opt typed_filters=true)

# apply-custom-data-types matches `export interface` / `}` anchored to column 0,
# but the generator emits them indented — format first so it can track which
# interface each `custom` field belongs to.
yarn exec prettier --write $OUTPUT_DIR/models/index.ts

node ./scripts/apply-custom-data-types.mts -i $OUTPUT_DIR/models/index.ts

yarn lint-fix
48 changes: 20 additions & 28 deletions src/gen/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@ import type {
export type Filters<
FilterConditions extends Record<string, { type: any; operators: string }>,
> = QueryFilters<{
[Property in keyof FCHelper<FilterConditions>]: FCHelper<FilterConditions>[Property]['operators'] extends string
?
| RequireOnlyOne<{
[Operator in FCHelper<FilterConditions>[Property]['operators']]:
| (Operator extends '$in' | '$nin'
? Array<FCHelper<FilterConditions>[Property]['type']>
: Operator extends '$exists'
? boolean
: FCHelper<FilterConditions>[Property]['type'])
| null;
}>
| FCHelper<FilterConditions>[Property]['type']
| null
: undefined;
[Property in keyof FCHelper<FilterConditions>]:
| RequireOnlyOne<{
[Operator in FCHelper<FilterConditions>[Property]['operators']]: Operator extends
| '$in'
| '$nin'
? Array<FCHelper<FilterConditions>[Property]['type']>
: Operator extends '$exists'
? boolean
: Operator extends '$eq' | '$ne'
? FCHelper<FilterConditions>[Property]['type'] | null
: FCHelper<FilterConditions>[Property]['type'];
}>
| ('$eq' extends FCHelper<FilterConditions>[Property]['operators']
? FCHelper<FilterConditions>[Property]['type'] | null
: never);
}>;

export type FCHelper<
Expand Down Expand Up @@ -61,20 +62,11 @@ export type QueryFilters<Operators> = {
} & QueryLogicalOperators<Operators>;

export type QueryLogicalOperators<Operators> = {
$and?: ArrayOneOrMore<QueryFilters<Operators>>;
$nor?: ArrayOneOrMore<QueryFilters<Operators>>;
$or?: ArrayTwoOrMore<QueryFilters<Operators>>;
$and?: Array<QueryFilters<Operators>>;
$nor?: Array<QueryFilters<Operators>>;
$or?: Array<QueryFilters<Operators>>;
};

export type ArrayOneOrMore<T> = {
0: T;
} & Array<T>;

export type ArrayTwoOrMore<T> = {
0: T;
1: T;
} & Array<T>;

export type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, undefined>>;
Expand Down Expand Up @@ -1641,7 +1633,7 @@ export interface ChannelMemberPartialResponse {
/**
* Channel-member custom fields projected via `member_custom_include`
*/
custom?: Record<string, any>;
custom?: CustomMemberData;
}

export interface ChannelMemberRequest {
Expand Down Expand Up @@ -1755,7 +1747,7 @@ export interface ChannelMetadata {

type: string;

custom: Record<string, any>;
custom: CustomChannelData;

last_message_at?: Date;

Expand Down
8 changes: 4 additions & 4 deletions src/pagination/FilterBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { StateStore } from '../store';
import type { ArrayOneOrMore, ArrayTwoOrMore, QueryFilter } from '../types';
import type { QueryFilter } from '../types';

type ElementType<T> = T extends (infer U)[] ? U : T;

Expand All @@ -12,9 +12,9 @@ export type ExtendedQueryFilter<T = string> = QueryFilter<T> & {
};

export type ExtendedQueryLogicalOperators<T> = {
$and?: ArrayOneOrMore<ExtendedQueryFilters<T>>;
$nor?: ArrayOneOrMore<ExtendedQueryFilters<T>>;
$or?: ArrayTwoOrMore<ExtendedQueryFilters<T>>;
$and?: Array<ExtendedQueryFilters<T>>;
$nor?: Array<ExtendedQueryFilters<T>>;
$or?: Array<ExtendedQueryFilters<T>>;
};

export type ExtendedQueryFilters<T> = {
Expand Down
47 changes: 47 additions & 0 deletions v9-to-v10-migration-guide-other.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,53 @@ Beyond the three breaking effects above, the field sets shifted to match the API

The legacy building blocks (`QueryFilter`, `PrimitiveFilter`, `QueryFilters`, `RequireOnlyOne`) remain exported for callers who compose their own filter types against `itemMatchesFilter` and the paginators.

### Removed — `ArrayOneOrMore` and `ArrayTwoOrMore`

The two non-empty-array utilities are gone from `stream-chat`. In v9 they existed only to constrain the logical operators — `$and` / `$nor` required at least one element (`ArrayOneOrMore`), `$or` at least two (`ArrayTwoOrMore`):

```ts
// v9 — src/types.ts
export type ArrayOneOrMore<T> = { 0: T } & Array<T>;
export type ArrayTwoOrMore<T> = { 0: T; 1: T } & Array<T>;
```

**Why they were dropped.** That encoding is only satisfied by a value whose length TypeScript knows statically, so any array _variable_ is rejected. It probably wasn't a common use-case for the logical operators, but the helpers were generically named, and cause issues when used in other places, for example:

```ts
declare const ids: string[];

// TS2322: Property '0' is missing in type 'string[]'
const a: ChannelFilters = { members: { $in: ids } };
// same
const b: ChannelFilters = { members: { $in: [...ids] } };
// only literals and non-empty tuples pass
const c: ChannelFilters = { members: { $in: ['u1'] } };
```

No non-empty encoding avoids this: TypeScript cannot prove a `T[]` has at least one element, so the guarantee costs a hard compile error on working code. An empty array stays a runtime 400 with a clear message, which is the cheaper failure.

**What to do.** Use a plain `Array<T>` (or `T[]`) wherever you referenced them:

```ts
// v9
import type { ArrayOneOrMore, ArrayTwoOrMore } from 'stream-chat';

type MyLogicalOperators<T> = {
$and?: ArrayOneOrMore<MyFilters<T>>;
$nor?: ArrayOneOrMore<MyFilters<T>>;
$or?: ArrayTwoOrMore<MyFilters<T>>;
};

// v10
type MyLogicalOperators<T> = {
$and?: Array<MyFilters<T>>;
$nor?: Array<MyFilters<T>>;
$or?: Array<MyFilters<T>>;
};
```

This mirrors what the SDK itself now does in `ExtendedQueryLogicalOperators` (`src/pagination/FilterBuilder.ts`). The change is widening, not narrowing: everything that compiled in v9 still compiles, and the array-variable cases above now compile too. The only thing you lose is the compile error on an empty (or single-element `$or`) array, which the API rejects at runtime anyway.

---

## State shape changes
Expand Down