Codecs
A PgCodec ("codec") represents a type (data type) in the database. There are
loads of built in codecs for dealing with the built-in types in Postgres made
available via the TYPES export, but you can also create your own
codecs for other types using the various helpers.
Every codec has a name, which is a handy identifier for you to use to
reference it (you can reference codecs from the registry via
registry.pgCodecs[name]). A codec also has an sqlType which is an SQL
expression giving the name
of the type in the database.
Codecs are responsible for performing coercion and validation; they should throw errors if the value supplied is invalid.
TYPES
import { TYPES } from "@dataplan/pg";
const intCodec = TYPES.int;
The TYPES object comprises a number of built-in codecs for common types in your
database; the following keys on TYPES represent the similarly named database
types (e.g. TYPES.boolean represents the bool type):
- boolean
- int2
- int
- bigint
- float4
- float
- money
- numeric
- char
- bpchar
- varchar
- text
- name
- json
- jsonb
- xml
- citext
- uuid
- timestamp
- timestamptz
- date
- time
- timetz
- inet
- regproc
- regprocedure
- regoper
- regoperator
- regclass
- regtype
- regrole
- regnamespace
- regconfig
- regdictionary
- cidr
- macaddr
- macaddr8
- interval
- bit
- varbit
- point
- line
- lseg
- box
- path
- polygon
- circle
- hstore
- void
recordCodec(config)
recordCodec is a helper function that returns a PgCodec representing a
"composite type" (or "record") - a structured type with attributes. This is
most commonly used when defining a table (the "attributes" in this case being
which columns it has), but is also useful in other cases.
The record codec config should contain:
name: string- the name to use for this codecidentifier: SQL- an SQL fragment detailing the name of the type in the databaseattributes: Record<string, PgCodecAttribute>- the attributes (columns) on this codec; the keys on this object are the attribute names, and the values are objects with the following options:codec: PgCodec- the PgCodec that details the type of this attributenotNull: boolean(optional) - if true, indicates that the column cannot be nullhasDefault: boolean(optional) - if true, indicates that the column has a default (and thus may be omitted fromINSERToperations)expression(alias: SQL): SQL(optional) - indicates that this attribute is not a real attribute, but instead a computed value that can be computed using the given expression
polymorphism(extremely optional) - see polymorphism
Example
const forumCodec = recordCodec({
name: "forums",
identifier: sql`app_public.forums`,
attributes: {
id: {
codec: TYPES.uuid,
notNull: true,
hasDefault: true,
},
name: {
codec: TYPES.citext,
notNull: true,
},
archived_at: {
codec: TYPES.timestamptz,
},
is_archived: {
codec: TYPES.boolean,
expression(alias) {
return sql`${alias}.archived_at is not null`;
},
},
},
});
listOfCodec(innerCodec, config = {})
listOfCodec returns a new codec that represents a list (array) of the given
innerCodec. Optionally you may provide details about this codec:
identifier- the (SQL) database name for this type
Example
For example, in this hypothetical E-commerce scenario, listOfCodec is used
in combination with the $pgSelect.placeholder() method to return a SQL
expression that allows the transformed list of $orderIds to be referenced
inside the step for selecting the associated order items.
const $customerId = context().get("customerId");
const $orders = orders.find({ customer_id: $customerId });
const $orderIds = applyTransforms(each($orders, ($order) => $order.get("id")));
const $orderItems = registry.pgResources.order_items.find();
$orderItems.where(
sql`${$orderItems}.order_id = ANY (${$orderItems.placeholder(
$orderIds,
listOfCodec(TYPES.uuid),
)})`,
);
rangeOfCodec(innerCodec, name, identifier)
rangeOfCodec returns a new codec that represents a range of the given
innerCodec. You must specify the name and identifier to use for this
codec.
Custom scalar codecs
Should you need to define more scalar codecs than those available via TYPES,
you may create a PgCodec object representing them. The object can have the
following properties:
/**
* A codec for a Postgres scalar type, tells us how to convert to-and-from
* Postgres (including changes to the SQL statement itself). Also includes
* metadata about the type.
*/
export interface PgScalarCodec<
TName extends string = string,
TFromPostgres = any,
TFromJavaScript = TFromPostgres,
> {
/**
* Unique name to identify this codec.
*/
name: TName;
/**
* When we have an expression of this type, we can safely cast it within
* Postgres using the cast `(${expression})::${sqlType}` to make the type
* explicit.
*/
sqlType: SQL;
/**
* If this codec came from a specific database, specify the executor here. If
* the codec is used with multiple databases (or if unsure), set this null.
*/
executor: PgExecutor | null;
/**
* Given a value of type TFromJavaScript, returns an `SQL` value to insert into an SQL
* statement.
*
* **IMPORTANT**: nulls must already be handled!
*/
toPg: PgEncode<TFromJavaScript>;
/**
* Given a text value from PostgreSQL, returns the value cast to TCanonical.
*
* **IMPORTANT**: nulls must already be handled!
*/
fromPg: PgDecode<TFromJavaScript, TFromPostgres>;
/**
* We'll append `::text` by default to each selection; however if this type
* needs something special (e.g. `money` should be converted to `numeric`
* before being converted to `text`) then you can provide this custom
* callback to provide your own casting - this could even include function
* calls if you want.
*/
castFromPg?: (fragment: SQL, guaranteedNotNull?: boolean) => SQL;
/**
* If you provide `castFromPg` you probably ought to also specify
* `listCastFromPg` so that a list of this type can be converted properly.
*/
listCastFromPg?: (fragment: SQL, guaranteedNotNull?: boolean) => SQL;
/**
* True if this type is a binary type (e.g. bytea)
*/
isBinary?: boolean;
/**
* True if doing an equality check for this value would have intuitive
* results for a human. E.g. `3.0` and `3.0000` when encoded as `float` are
* the same as a human would expect, so `float` has natural equality. On the
* other hand Postgres sees the `json` `{"a":1}` as different to
* `{ "a": 1 }`), whereas a human would see these as the same JSON objects,
* so `json` does not have natural equality.
*
* Typically true primitives will set this true.
*/
hasNaturalEquality?: boolean;
/**
* True if this type has a natural ordering that would be intuitive for a human.
* For example numbers and text have natural ordering, whereas `{"a":1}` and
* `{ "a": 2 }` are not so obvious. Similarly, a `point` could be ordered in many
* ways relative to another point (x-first, then y; y-first, then x; distance
* from origin first, then angle; etc) so do not have natural order.
*
* Typically true primitives will set this true.
*/
hasNaturalOrdering?: boolean;
/**
* Documentation for the type.
*/
description?: string;
/**
* Arbitrary metadata
*/
extensions?: Partial<PgCodecExtensions>;
}