@electric-sql/client
Version:
Postgres everywhere - your data, in sync, wherever you need it.
246 lines (216 loc) • 7.23 kB
text/typescript
/**
* Default types for SQL but can be extended with additional types when using a custom parser.
* @typeParam Extensions - Additional value types.
*/
export type Value<Extensions = never> =
| string
| number
| boolean
| bigint
| null
| Extensions
| Value<Extensions>[]
| { [key: string]: Value<Extensions> }
export type Row<Extensions = never> = Record<string, Value<Extensions>>
// Check if `T` extends the base Row type without extensions
// if yes, it has no extensions so we return `never`
// otherwise, we infer the extensions from the Row type
export type GetExtensions<T> = [T] extends [Row<never>]
? never
: [T] extends [Row<infer E>]
? E
: never
export type Offset =
| `-1`
| `now`
| `${number}_${number}`
| `${bigint}_${number}`
/** Information about transaction visibility for a snapshot. All fields are encoded as strings, but should be treated as uint64. */
export type PostgresSnapshot = {
xmin: `${bigint}`
xmax: `${bigint}`
xip_list: `${bigint}`[]
}
export type NormalizedPgSnapshot = {
xmin: bigint
xmax: bigint
xip_list: bigint[]
}
interface Header {
[key: Exclude<string, `operation` | `control` | `event`>]: Value
}
export type Operation = `insert` | `update` | `delete`
/**
* A tag is a string identifying a reason for this row to be part of the shape.
*
* Tags can be composite, but they are always sent as a single string. Compound tags
* are separated by `|`. It's up to the client to split the tag into its components
* in order to react to move-outs correctly. Tag parts are guaranteed to not contain an
* unescaped `|` character (escaped as `\\|`) or be a literal `*`.
*
* Composite tag width is guaranteed to be fixed for a given shape.
*/
export type MoveTag = string
/**
* A move-out pattern is a position and a value. The position is the index of the column
* that is being moved out. The value is the value of the column that is being moved out.
*
* Tag width and value order is fixed for a given shape, so the client can determine
* which tags match this pattern.
*/
export type MoveOutPattern = { pos: number; value: string }
/**
* Serialized expression types for structured subset queries.
* These allow Electric to properly apply columnMapper transformations
* before generating the final SQL.
*/
export type SerializedExpression =
| { type: `ref`; column: string } // Column reference
| { type: `val`; paramIndex: number } // Parameter placeholder ($1, $2, etc.)
| { type: `func`; name: string; args: SerializedExpression[] } // Operator/function
/**
* Serialized ORDER BY clause for structured subset queries.
*/
export type SerializedOrderByClause = {
column: string
direction?: `asc` | `desc` // omitted means 'asc'
nulls?: `first` | `last`
}
export type SubsetParams = {
/** Legacy string format WHERE clause */
where?: string
/** Positional parameter values for WHERE clause */
params?: Record<string, string | bigint | number>
/** Maximum number of rows to return */
limit?: number
/** Number of rows to skip */
offset?: number
/** Legacy string format ORDER BY clause */
orderBy?: string
/** Structured WHERE expression (preferred when available) */
whereExpr?: SerializedExpression
/** Structured ORDER BY clauses (preferred when available) */
orderByExpr?: SerializedOrderByClause[]
/**
* HTTP method to use for the request. Overrides `subsetMethod` from ShapeStreamOptions.
* - `GET` (default): Sends subset params as query parameters. May fail with 414 errors
* for large queries.
* - `POST`: Sends subset params in request body as JSON. Recommended to avoid URL
* length limits with large WHERE clauses or many parameters.
*
* In Electric 2.0, GET will be deprecated and only POST will be supported.
*/
method?: `GET` | `POST`
}
export type ControlMessage = {
headers:
| (Header & {
control: `up-to-date` | `must-refetch`
global_last_seen_lsn?: string
})
| (Header & { control: `snapshot-end` } & PostgresSnapshot)
| (Header & { control: `subset-end` } & SubsetParams)
}
export type EventMessage = {
headers: Header & { event: `move-out`; patterns: MoveOutPattern[] }
}
export type ChangeMessage<T extends Row<unknown> = Row> = {
key: string
value: T
old_value?: Partial<T> // Only provided for updates if `replica` is `full`
headers: Header & {
operation: Operation
txids?: number[]
/** Tags will always be present for changes if the shape has a subquery in its where clause, and are omitted otherwise.*/
tags?: MoveTag[]
removed_tags?: MoveTag[]
}
}
// Define the type for a record
export type Message<T extends Row<unknown> = Row> =
| ControlMessage
| EventMessage
| ChangeMessage<T>
/**
* Common properties for all columns.
* `dims` is the number of dimensions of the column. Only provided if the column is an array.
* `not_null` is true if the column has a `NOT NULL` constraint and is omitted otherwise.
*/
export type CommonColumnProps = {
dims?: number
not_null?: boolean
}
export type RegularColumn = {
type: string
} & CommonColumnProps
export type VarcharColumn = {
type: `varchar`
max_length?: number
} & CommonColumnProps
export type BpcharColumn = {
type: `bpchar`
length?: number
} & CommonColumnProps
export type TimeColumn = {
type: `time` | `timetz` | `timestamp` | `timestamptz`
precision?: number
} & CommonColumnProps
export type IntervalColumn = {
type: `interval`
fields?:
| `YEAR`
| `MONTH`
| `DAY`
| `HOUR`
| `MINUTE`
| `YEAR TO MONTH`
| `DAY TO HOUR`
| `DAY TO MINUTE`
| `DAY TO SECOND`
| `HOUR TO MINUTE`
| `HOUR TO SECOND`
| `MINUTE TO SECOND`
} & CommonColumnProps
export type IntervalColumnWithPrecision = {
type: `interval`
precision?: 0 | 1 | 2 | 3 | 4 | 5 | 6
fields?: `SECOND`
} & CommonColumnProps
export type BitColumn = {
type: `bit`
length: number
} & CommonColumnProps
export type NumericColumn = {
type: `numeric`
precision?: number
scale?: number
} & CommonColumnProps
export type ColumnInfo =
| RegularColumn
| VarcharColumn
| BpcharColumn
| TimeColumn
| IntervalColumn
| IntervalColumnWithPrecision
| BitColumn
| NumericColumn
export type Schema = { [key: string]: ColumnInfo }
export type TypedMessages<T extends Row<unknown> = Row> = {
messages: Array<Message<T>>
schema: ColumnInfo
}
export type MaybePromise<T> = T | Promise<T>
/**
* Metadata that allows the consumer to know which changes have been incorporated into this snapshot.
*
* For any data that has a known transaction ID `xid` (and e.g. a key that's part of the snapshot):
* - if `xid` < `xmin` - included, change can be skipped
* - if `xid` < `xmax` AND `xid` not in `xip` - included, change can be skipped
* - if `xid` < `xmax` AND `xid` in `xip` - parallel, not included, change must be processed
* - if `xid` >= `xmax` - not included, change must be processed, and we can stop filtering after we see this
*/
export type SnapshotMetadata = {
/** Random number that's reflected in the `snapshot_mark` header on the snapshot items. */
snapshot_mark: number
database_lsn: string
} & PostgresSnapshot