Files
Desktop/packages/types
2026-07-06 16:07:13 -07:00
..
2026-07-06 16:07:13 -07:00
2026-07-06 16:07:13 -07:00
2026-07-06 16:07:13 -07:00
2026-07-06 16:07:13 -07:00
2026-07-06 16:07:13 -07:00
2026-07-06 16:07:13 -07:00

@signalapp/types

This package provides shared opaque and utility types and related helper functions for use across different Signal libraries and desktop apps.

import { Aci, SentTimestampMs, DurationSecs } from '@signalapp/types';

export type PinnedMessage = Readonly<{
  targetSentTimestamp: SentTimestampMs;
  targetAuthorAci: Aci;
  pinDuration: DurationSecs;
}>;

API

import type { Tagged } from 'type-fest';

// Result
type Result<T, E = string> = Result.Ok<T> | Result.Err<E>;
type Result.Ok<T> = Readonly<{ ok: true; value: T; error?: never; }>;
type Result.Err<E = string> = Readonly<{ ok: false; value?: never; error: E; }>;

// Numbers
type Uint8 = Tagged<number, 'Uint8'>;
type Uint16 = Tagged<number, 'Uint16'>;
type Uint32 = Tagged<number, 'Uint32'>;
type BigUint64 = Tagged<bigint, 'BigUint64'>;
type BigUint128 = Tagged<bigint, 'BigUint128'>;
type Int8 = Tagged<number, 'Int8'>;
type Int16 = Tagged<number, 'Int16'>;
type Int32 = Tagged<number, 'Int32'>;
type BigInt64 = Tagged<bigint, 'BigInt64'>;
type BigInt128 = Tagged<bigint, 'BigInt128'>;
type Float16 = Tagged<number, 'Float16'>;
type Float32 = Tagged<number, 'Float32'>;
type Float64 = Tagged<number, 'Float64'>;

// Units
type UnitBytes = Tagged<Float64, 'UnitBytes'>;;
type UnitKilobytes = Tagged<Float64, 'UnitKilobytes'>;
type UnitKibibytes = Tagged<Float64, 'UnitKibibytes'>;

// Datetime
type TimestampMs = Tagged<number, 'TimestampMs'>;
type TimestampSecs = Tagged<number, 'TimestampSecs'>;
type DurationMs = Tagged<Uint32, 'DurationMs'>;
type DurationSecs = Tagged<Uint32, 'DurationSecs'>;
type DurationDays = Tagged<Uint32, 'DurationDays'>;
type PlainTimeHourMin = Tagged<Uint32, 'PlainTimeHourMin'>;

// Encodings
type Bytes = Uint8Array<ArrayBuffer>;
type Bytes.Of<T> = Tagged<Bytes, 'Bytes.Of', T>;
type Base64 = Tagged<string, 'Base64'>;
type Base64.Of<T> = Tagged<Base64, 'Base64.Of', T>;
type Base64Url = Tagged<string, 'Base64Url'>;
type Base64Url.Of<T> = Tagged<Base64Url, 'Base64URL.Of', T>;
type Hex = Tagged<string, 'Hex'>;
type Hex.Of<T> = Tagged<Hex, 'Hex.Of', T>;
type Json = Tagged<string, 'Json'>;
type Json.Of<T extends JsonValue> = Tagged<Json, 'Json.Of', T>;
type JsonPrimitive = null | boolean | number | string;
type JsonArray = Array<JsonValue> | ReadonlyArray<JsonValue>;
type JsonObject = { [Key in string]: JsonValue };
type JsonValue = JsonPrimitive | JsonArray | JsonObject;
type Utf8.Of<T extends string> = Tagged<T, 'Utf8.Of'>;

// Formats
type HexColor = Tagged<`#${string}`, 'HexColor'>;
type HttpUrl = Tagged<string, 'HttpUrl'>;
type LanguageTag = Tagged<string, 'LanguageTag'>;
type MimeType = Tagged<`${string}/${string}`, 'MimeType'>;
type Semver = Tagged<string, 'Semver'>;
type Uuid = Tagged<string, 'Uuid'>;

// Service
type E164 = E164.Decoded;
type E164.Opaque = Tagged<`+${string}`, 'E164'>;
type E164.Encoded = Utf8.Of<E164.Opaque>;
type E164.Decoded = Bytes.Of<E164.Opaque>;

type Aci = Aci.Decoded;
type Aci.Opaque = Tagged<Uuid, 'Aci'>;
type Aci.Decoded = Utf8.Of<Aci.Opaque>;
type Aci.Encoded = Bytes.Of<Aci.Opaque>;

type Pni = Utf8.Of<Pni.Opaque>;
type Pni.Opaque = Tagged<`PNI:${Uuid}`, 'Pni'>;
type Pni.Decoded = Utf8.Of<Pni.Opaque>;
type Pni.Encoded = Bytes.Of<Pni.Opaque>;

type UntaggedPni = UntaggedPni.Decoded;
type UntaggedPni.Opaque = Tagged<Uuid, 'UntaggedPni'>;
type UntaggedPni.Decoded = Utf8.Of<UntaggedPni.Opaque>;
type UntaggedPni.Encoded = Bytes.Of<UntaggedPni.Opaque>;

type ServiceId = ServiceId.Decoded;
type ServiceId.Decoded = Aci | Pni;
type ServiceId.Encoded = Bytes.Of<Decoded>;

type DeviceId = Tagged<Uint32, 'DeviceId'>;

type Address = Address.Decoded;
type Address.Opaque = Tagged<`${ServiceId}.${DeviceId}`, 'Address'>;
type Address.Decoded = Utf8.Of<Address.Opaque>;
type Address.Encoded = Bytes.Of<Address.Opaque>;

type AddressInfo = Tagged<AddressInfo.Params, 'AddressInfo'>;
type AddressInfo.Params = Readonly<{ serviceId: ServiceId; deviceId: DeviceId; }>;

type DistributionId = Utf8.Of<DistributionId.Opaque>;
type DistributionId.Opaque = Tagged<Uuid, 'DistributionId'>;

type DistributionId = DistributionId.Decoded;
type DistributionId.Opaque = Tagged<Uuid, 'DistributionId'>;
type DistributionId.Decoded = Utf8.Of<DistributionId.Opaque>;
type DistributionId.Encoded = Bytes.Of<DistributionId.Opaque>;

type GroupId = GroupId.Decoded;
type GroupId.Opaque = Tagged<unknown, 'GroupId'>;
type GroupId.Decoded = Base64.Of<GroupId.Opaque>;
type GroupId.Encoded = Bytes.Of<GroupId.Opaque>;

type DistributionListId = DistributionListId.Decoded;
type DistributionListId.Opaque = Tagged<Uuid, 'DistributionListId'>;
type DistributionListId.Decoded = Utf8.Of<DistributionListId.Opaque>;
type DistributionListId.Encoded = Bytes.Of<DistributionListId.Opaque>;

type SentTimestampMs = Tagged<TimestampMs, 'SentTimestampMs'>;
type ServerTimestampMs = Tagged<TimestampMs, 'ServerTimestampMs'>;
type ReceivedTimestampMs = Tagged<TimestampMs, 'ReceivedTimestampMs'>;

type StorageManifestVersion = Tagged<BigUint64, 'StorageManifestVersion'>;

type StorageItemKey = StorageItemKey.Decoded;
type StorageItemKey.Opaque = Tagged<unknown, 'StorageItemKey'>;
type StorageItemKey.Decoded = Base64.Of<Opaque>;
type StorageItemKey.Encoded = Bytes.Of<Opaque>;

Schemas

Almost all of these types provide Zod schemas:

import { Aci, SentTimestampMs, DurationSecs } from '@signalapp/types';

export const PinnedMessageSchema = z.object({
  targetSentTimestamp: SentTimestampMs.Schema,
  targetAuthorAci: Aci.Schema,
  pinDuration: DurationSecs.Schema,
});

Helpers

Types have helpers for casting loosely typed data to strict types:

Note: You should prefer to use the most specific from* method that you can, it will generally have less runtime cost checking the type is valid.

Aci.isValid(input: string): input is Aci; // boolean
Aci.fromUuid(input: Uuid): Aci; // throws if invalid
Aci.fromString(input: string): Aci; // throws if invalid

Encodings

Encodings have special <Encoding>.Of<T> types to represent internal opaque types:

// Maybe this just represents some random number generation that we should not be aware of
export type MyOpaqueType = Tagged<unknown, 'MyOpaqueType'>;

export function decode(input: Bytes.Of<MyOpaqueType>): Base64.Of<MyOpaqueType> {
  return Base64.fromBytes(input);
}

export function encode(input: Bytes.Of<MyOpaqueType>): Base64.Of<MyOpaqueType> {
  return Base64.toBytes(input);
}

Many of the "service" types use these <Encoding>.Of types to wrap their own internal Opaque type with expected encodings along with encode and decode helpers:

export type StorageItemKey = StorageItemKey.Decoded;

export namespace StorageItemKey {
  type Opaque = Tagged<unknown, 'StorageItemKey'>;

  export type Decoded = Base64.Of<Opaque>;
  export type Encoded = Bytes.Of<Opaque>;

  export function decode(input: Encoded): StorageItemKey {
    return Base64.fromBytes(input);
  }

  export function encode(input: StorageItemKey): Encoded {
    return Base64.toBytes(input);
  }
}

Future Ideas

  • General:
    • NonEmpty<T extends { length: number }>
    • NonZero<T extends number | bigint>
  • Units:
    • Dimension
    • Width
    • Height
  • Formats:
    • BlurHash
    • RegionId
    • CurrencyCode
    • Email
  • File System:
    • FileName
    • FilePath
    • FileExtension
  • Net:
    • BasicAuth
    • IpV4
    • IpV6
    • IpAddr
    • Domain/Hostname
    • Protocol
    • Port
  • Service:
    • RegistrationId
    • ServerGuid
    • ProfileKey
    • `ProfileKeyVersion
    • Username
    • ReportingToken
    • GroupVersion
    • MasterKey
    • PublicParams
    • SecretParams
    • GroupInviteLinkPassword
    • UserText / TrustedText