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
56 changes: 56 additions & 0 deletions crates/bindings-typescript/src/lib/type_util.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { ConnectionId } from './connection_id';
import type { Identity } from './identity';
import type { Prettify } from './type_util';
import type { TimeDuration } from './time_duration';
import type { Timestamp } from './timestamp';
import type { Uuid } from './uuid';

declare const brand: unique symbol;

/**
* A branded primitive: an intersection of a primitive with a marker object.
* `Prettify` must leave it alone — mapping it produces a structural record of
* `String`'s members that no longer satisfies `string`.
*/
type UserId = string & { readonly [brand]: 'UserId' };

declare const userId: Prettify<UserId>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _brandedIsStillAString: string = userId;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _brandedKeepsItsBrand: UserId = userId;

// Plain primitives round-trip unchanged.
declare const str: Prettify<string>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _str: string = str;
declare const num: Prettify<number>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _num: number = num;
declare const big: Prettify<bigint>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _big: bigint = big;

// Every SATS wrapper class is passed through as the class, not as a
// structural record of its members.
declare const identity: Prettify<Identity>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _identity: Identity = identity;
declare const connectionId: Prettify<ConnectionId>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _connectionId: ConnectionId = connectionId;
declare const timestamp: Prettify<Timestamp>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _timestamp: Timestamp = timestamp;
declare const timeDuration: Prettify<TimeDuration>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _timeDuration: TimeDuration = timeDuration;
declare const uuid: Prettify<Uuid>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _uuid: Uuid = uuid;

// Object types are still flattened.
type Intersected = { a: string } & { b: number };
declare const flattened: Prettify<Intersected>;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _flattened: { a: string; b: number } = flattened;
12 changes: 10 additions & 2 deletions crates/bindings-typescript/src/lib/type_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,28 @@ import type { Identity } from './identity';
import type { ScheduleAt } from './schedule_at';
import type { TimeDuration } from './time_duration';
import type { Timestamp } from './timestamp';
import type { Uuid } from './uuid';

type DoNotPrettify =
| Identity
| ConnectionId
| Timestamp
| TimeDuration
| ScheduleAt;
| ScheduleAt
| Uuid;

/**
* Utility to make TS show cleaner types by flattening intersections.
*
* Only object types are flattened. A non-object has no intersection to
* flatten, and mapping one is not always identity: `string & Brand` becomes a
* structural record of `String`'s members that no longer satisfies `string`.
*/
export type Prettify<T> = T extends DoNotPrettify
? T
: { [K in keyof T]: T[K] } & {};
: T extends string | number | boolean | bigint | symbol | null | undefined
? T
: { [K in keyof T]: T[K] } & {};

/**
* Helper function to sets a field in an object
Expand Down